This blog post is a high-level overview. If you are looking for the full technical details, please see this article Attacking PowerShell CLIXML Deserialization – Truesec.
PowerShell Remoting and PowerShell Direct are widely used solutions, used to manage enterprise IT environment. They both have a feature to share objects between computers and this is implemented with a serialization format called CLIXML. The issue is that they put trust the remote system to always provide legitimate CLIXML data. This is a vulnerability known as CWE-502: Deserialization of Untrusted Data. In this attack scenario we do not target the server, instead, the target is the client of the connection. Anyone who use these solutions to connect to a compromised computer is thus exposed.
Privilege Escalation and Lateral Movement
The PowerShell Remoting attack scenario is illustrated in the figure below. The unsuspecting administrator connects to a compromised server. However, as the server is compromised, the threat actor can control what data to return. This scenario would thus result in a compromise of the administrator’s computer. Note that this also affects many popular admin solutions because rely on PowerShell Remoting.
This scenario is very interesting because PowerShell Remoting is usually used by administrators with high privileges to remotely manage computers. If a threat actor manages to compromise for example a web server, they could use this technique to escalate their privileges as soon as an admin connects to the compromised server. This concept of internal escalation is a vital part of most cyber attacks.
In the video below, we show what this attack could look like in practice. In this case, an unsuspecting domain admin uses PowerShell Remoting to run a simple command on the compromised server. We see that as soon as the command is executed, the threat actor manages to steal the network password hash (Net-NTLMv2) of the domain admin.
Breaking out of a Hyper-V virtual machine
The PowerShell Direct scenario is very similar. The victim connect to a compromised virtual machine using PowerShell Direct. The threat actor can control the data returned to the hypervisor host. This leads to a compromise of the underlying hypervisor host. This scenario is very interesting because the hypervisor security barrier is critical for most organizations. For example, cloud hosting providers rely on this security barrier to provide separation between customers. If a threat actor manages to compromise the host-layer, they would go from compromising one victim into all victims hosted by the same provider.
In the video below, we see a scenario where a virtual machine is already compromised. An unsuspecting Hyper-V admin uses PowerShell Direct to run a simple command in the compromised machine. In this case, we do it manually, but note that for example backup solutions could be configured to run recurring PowerShell Direct commands automatically.
As soon as a PowerShell Direct command is executed, the threat actor is able to exploit this and break out of the virtual machine. In the demo we spawn a remote access malware on the underlying host.
What is the impact of these vulnerabilities?
This is not very straight forward as the impact of the vulnerability will depend on a variaty of factors. Essentially, it depends on what gadgets are available in the victim’s PowerShell session. By default, only a few gadgets are available. The concept of gadgets are fairly involved, we recommend that you read our technical article if you want to fully understand the impact.
GADGET | IMPACT | DEPENDENCIES |
Arbitrary DNS lookup | Threat actor can perform domain name lookup to any domain. Useful to find vulnerabilitites. | None |
Remote Code Execution | Execute any code to for example spawn a malware. | The victim must use the attribute ‘action’ on the result object, as an argument to a cmdlet. |
Steal Net-NTLMv2 hashes | Threat actor can steal the Net-NTLMv2 hash which contains the victim’s password. | None |
XXE (XML External Entity) | Not detailed here. | < .NET 4.5.2 |
Remote Code Execution | Execute any code to for example spawn a malware. | CVE-2017-8565 |
Remote Code Execution | Execute any code to for example spawn a malware. | Affects hundreds of modules, including three official Microsoft modules. One of these modules must be installed and available. |
Other impacts | Not detailed here. | Not detailed here. |
Note that these gadgets (impacts) are what I have found in my research. There are likely more gadgets to be discovered.
Reporting the vulnerability
I submitted my research on March 18th, 2024 to Microsoft Security Response Center (MSRC). MSRC closed the case as “fixed” on July 22nd and a month later my research was publicly acknowledged. However, it is still possible to perform this attack and therefore organizations need to take propriate precautions to mitigate the risks.
Time | Who | Description |
2024-03-18 23:57 | Alex to MSRC | Reported findings with working PoCs to Microsoft (MSRC) |
2024-03-21 17:33 | MSRC | Case opened |
2024-04-15 19:03 | MSRC to Alex | “We confirmed the behavior you reported” |
2024-05-06 17:53 | Alex to MSRC | Asked for status update |
2024-05-07 21:09 | MSRC | Closed the case |
2024-05-26 23:33 | Alex to MSRC | Asked for resolution details |
2024-05-30 | Alex | Started escalating via contacts at MS and MVP friends |
2024-06-04 | Microsoft to Alex | Asked for a copy of my SEC-T presentation |
2024-06-04 | Alex to Microsoft | Sent my SEC-T presentation |
2024-06-26 15:55 | MSRC | Opened the case |
2024-07-22 23:02 | MSRC to Alex | “Thank you[…] The issue has been fixed.” |
2024-07-22 23:04 | MSRC | Closed the case |
2024-07-22 | Alex to MSRC | Offered to help validate the fix and for resolution details. |
2024-08-14 | Alex to Microsoft | Sent reminder asking if they want to give feedback on the presentation |
2024-08-19 | Alex to PSFramework | Started reachout to PSFramework. |
2024-08-28 | PSFramework | First contact. |
2024-08-29 | MSRC | Public acknowledgment. |
2024-09-13 | Alex | Presented at SEC-T. |
2024-09-14 | Alex | Published blog post. |
To me, it is still unclear what MSRC means with “The issue has been fixed” as they have not shared any resolution details. While it is obvious that PSRP and PSDirect still deserializes untrusted data, it appears that they also did not fix the remote code execution (due to PSFramework dependency) in Microsoft’s own PowerShell modules, although they are covered under MSRC according to their security.md files (Azure/AzOps, Azure/AzOps-Accelerator, Azure/AVDSessionHostReplacer, PAWTools).
On 2024-08-19 I decided to contact the Microsoft employee behind PSFramework myself. He instantly understood the issue and did a great job starting to resolve it. Make sure to update to v1.12.345 in case you have PSFramework installed.
This research was publicly released 2024-09-14, which is 180 days after the initial private disclosure.
What can you do about it?
PowerShell Remoting (PSRP) is still a recommended method for managing your environment. You should not go back to RDP (Remote Desktop Protocol) or similar for lots of reasons. However, before using PSRP or PSDirect, there are a few things you need to keep in mind.
1. Ensure that the computer you are remoting from is fully patched. As you see in the table above, this will solve some of the problems, but not all.
2. Never use remoting from a computer that is littered with third-party PowerShell modules. In other words, you probably shouldn’t remote from your all-in-one admin PC.
3. Use a privileged access workstation that is dedicated for admin tasks.
4. Review your PowerShell modules
Check the modules loaded on startup by starting a fresh PowerShell prompt and run:
get-module
Note however that modules will be implicitly loaded as soon as you use one of their cmdlets. So you should also check the available modules on your system.
get-module -ListAvailable
5. Reduce your PowerShell Modules
When you install a PowerShell module, it may introduce a new deserialization gadget on your system and your system will be exposed as soon as you use PSRP, PSDirect, or use any script that imports untrusted CLIXML.
Being restrictive with PowerShell modules is good practice in general, as third-party modules comes with other risks as well (e.g. supply chain attacks).
This is however not as easy as it may sound. Lots of software ships with their own set of PowerShell modules that will be installed on your system. You need to ensure that these don’t introduce gadgets.
6. Mitigate impact and disable gadgets
As long as PSRP and PSDirect still relies on untrusted CLIXML deserialization, there will be a constant battle to find and defuse deserialization gadgets.
As an example, the “password stealing gadget” can be mitigated with a simple if
statement. Find the following code in C:\Windows\System32\WindowsPowerShell\v1.0\Registry.format.ps1xml
:
<ScriptBlock>
$result = (Get-ItemProperty -LiteralPath $_.PSPath | Select * -Exclude PSPath,PSParentPath,PSChildName,PSDrive,PsProvider | Format-List | Out-String | Sort).Trim()
$result = $result.Substring(0, [Math]::Min($result.Length, 5000) )
if($result.Length -eq 5000) { $result += "..." }
$result
</ScriptBlock>
Then add validation that ensures the PSPath property is legitimate. The updated formatter could look something like this:
<ScriptBlock>
$result = ""
if($_.PSPath.startswith("Microsoft.PowerShell.Core\Registry")){
$result = (Get-ItemProperty -LiteralPath $_.PSPath | Select * -Exclude PSPath,PSParentPath,PSChildName,PSDrive,PsProvider | Format-List | Out-String | Sort).Trim()
$result = $result.Substring(0, [Math]::Min($result.Length, 5000) )
if($result.Length -eq 5000) { $result += "..." }
}
$result
</ScriptBlock>