渗透技巧 —— Exchange Powershell 的 Python 实现
0x00 前言
远程执行 Exchange Powershell 命令可以通过 Powershell 建立 powershell session 实现。而在渗透测试中,我们需要尽可能避免使用 Powershell,而是通过程序去实现。本文将要介绍通过 Python 实现远程执行 Exchange Powershell 命令的细节,分享使用 Python 实现 TabShell 利用的心得。
0x01 简介
本文将要介绍以下内容:
- 执行 Exchange Powershell 命令的实现方法
- 开发细节
- TabShell 利用细节
0x02 执行 Exchange Powershell 命令的实现方法
1. 使用 Powershell 连接 Exchange 服务器,执行 Exchange Powershell 命令
命令示例:
$User = "test\administrator"
$Pass = ConvertTo-SecureString -AsPlainText Password123 -Force
$Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $User,$Pass
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://Exchange01.test.com/PowerShell/ -Authentication Kerberos -Credential $Credential
Invoke-Command -Session $session -ScriptBlock {Get-Mailbox -Identity administrator}
需要注意以下问题:
- 需要域内主机上执行
- 需要 fqdn,不支持 IP
- 连接 url 可以选择
http
或https
- 认证方式可以选择
Basic
或Kerberos
2. 使用 Python 连接 Exchange 服务器,执行 Exchange Powershell 命令
这里需要使用 pypsrp
命令示例:
from pypsrp.powershell import PowerShell, RunspacePool
from pypsrp.wsman import WSMan
host = 'Exchange01.test.com'
username='test\\administrator'
password='Password123'
wsman = WSMan(server=host, username=username, password=password, port=80, path="PowerShell", ssl=False, auth="kerberos", cert_validation=False)
with wsman, RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool:
ps = PowerShell(pool)
ps.add_cmdlet("Get-Mailbox").add_parameter("-Identity", "administrator")
output = ps.invoke()
print("[+] OUTPUT:\n%s" % "\n".join([str(s) for s in output]))
0x03 开发细节
这里需要了解具体的通信格式,我采用的方法是使用 pypsrp ,开启调试信息,查看具体发送的数据格式
1. 开启调试信息
将调试信息写到文件,代码如下:
import logging
logging.basicConfig(level=logging.DEBUG, filename="log.txt", filemode="a")
2. 增加调试输出内容
修改文件 pypsrp/wsman.py,在 def send(self, message: bytes)
中添加调试输出信息
具体代码位置:https://github.com/jborean93/pypsrp/blob/master/src/pypsrp/wsman.py#L834,添加代码:
log.debug("data: %s" % payload)
log.debug("headers: %s" % headers)
https://github.com/jborean93/pypsrp/blob/master/src/pypsrp/wsman.py#L841,添加代码:
log.debug("response.content: %s" % response.content)
输出结果示例如下图
3.数据包数据结构
可参考之前的文章《渗透技巧——远程访问 Exchange Powershell》
经过对比分析,在编写程序上还需要注意以下细节:
(1) Kerberos 认证的实现
示例代码:
from pypsrp.negotiate import HTTPNegotiateAuth
session.auth = HTTPNegotiateAuth(username=username, password=password, auth_provider="kerberos", wrap_required=True)
(2) 通信数据格式
类型为 POST
header
需要包括: 'Accept-Encoding': 'identity'
(3) 认证流程
需要先进行 Kerberos 认证,返回长度为 0
再次发送数据,进行通信,返回正常内容
(4) 数据编码
发送和接收的数据均做了编码,发送过程的编码示例代码:
hostname = "exchange01.test.com"
protocol = WinRMEncryption.KERBEROS
encryption = WinRMEncryption(session.auth.contexts[hostname], protocol)
content_type, payload = encryption.wrap_message(request_data.encode('utf-8'))
r = session.post(url, data=payload, headers=headers, verify=False)
注:hostname
必须为小写字符
接收过程的解码示例代码:
boundary = re.search("boundary=[" '|\\"](.*)[' '|\\"]', r.headers["content-type"]).group(1) # type: ignore[union-attr] # This should not happen
response_content = encryption.unwrap_message(r.content, to_unicode(boundary)) # type: ignore[union-attr] # This should not happen
response_text = to_string(response_content)
完整示例代码如下:
from pypsrp.encryption import WinRMEncryption
from pypsrp._utils import get_hostname, to_string, to_unicode
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36',
'Content-Type': 'multipart/encrypted;protocol="application/HTTP-Kerberos-session-encrypted";boundary="Encrypted Boundary"',
'Accept-Encoding': 'identity'
}
print("[*] 1.Sending http request")
r = session.post(url, data=request_data, headers=headers, verify=False)
if r.status_code == 200:
print("[+] Success")
else:
print("[!]")
print(r.status_code)
print(r.text)
sys.exit(0)
hostname = "exchange01.test.com"
protocol = WinRMEncryption.KERBEROS
encryption = WinRMEncryption(session.auth.contexts[hostname], protocol)
content_type, payload = encryption.wrap_message(request_data.encode('utf-8'))
print("[*] 2.Sending http request")
r = session.post(url, data=payload, headers=headers, verify=False)
if r.status_code == 200:
print("[+] Response length: " + str(len(r.text)))
boundary = re.search("boundary=[" '|\\"](.*)[' '|\\"]', r.headers["content-type"]).group(1) # type: ignore[union-attr] # This should not happen
response_content = encryption.unwrap_message(r.content, to_unicode(boundary)) # type: ignore[union-attr] # This should not happen
response_text = to_string(response_content)
完整代码的输出结果如下图
0x04 TabShell 利用细节
TabShell 的公开 POC 使用 Powershell 连接 Exchange 服务器,执行特殊构造的 Exchange Powershell 命令触发,为了便于分析中间的通信数据,可以采用以下方法抓取中间的明文数据:
1. 通过 Flask 建立本地代理服务器
方法可参考之前的文章 《ProxyShell 利用分析 3——添加用户和文件写入》
2. 通过 Flask 实现 SSRF
SSRF 漏洞可以选择 CVE-2022-41040 或者 CVE-2022-41080
3. 在 Flask 中输出中间的通信数据
关键代码示例:
while True:
r = session.post(powershell_url, data=data, headers=req_headers, verify=False)
print("post data:")
print(data)
if r.status_code == 200:
print("[+]" + r.headers["X-CalculatedBETarget"])
break
else:
print("[-]" + r.headers["X-CalculatedBETarget"])
print("recv data:")
print(r.content)
根据通信数据,我们可以很容易写出 TabShell 的 Python 实现代码,完整代码的输出结果如下图
0x05 小结
本文介绍了通过 Python 实现远程执行 Exchange Powershell 命令的细节,分享使用 Python 实现 TabShell 利用的心得。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 渗透基础 —— Fortigate 识别与版本探测
下一篇: Covenant 利用分析
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论