原文链接:http://blog.portswigger.net/2016/12/bypassing-csp-using-polyglot-jpegs.html

原作者: Gareth Heyes

译:Holic (知道创宇404安全实验室)

James 曾请我看看是否能创建一个多语言的 JavaScript/JPEG (注:此处即将 JPEG 当做 JS 执行)。这么做的话,我将可以通过在同一域名用户可上传的图片,来绕过 CSP 的保护。我欣然接受挑战,然后开始剖析图片格式。前四个字节是非 ASCII 的 JavaScript 变量 0xFF 0xD8 0xFF 0xE0 。然后,接下来的两个字节指定了 JPEG 头部的长度。如果我们使用字节 0x2F 2A设置文件头的长度为 0x2F2A,你会猜到,我们有一个非 ASCII 字符的变量,后面更早多行 JavaScript 注释。然后,我们必须将 JPEG 头填充为 0x2F2A 长度的 null。它看起来是这样的:

FF D8 FF E0 2F 2A 4A 46 49 46 00 01 01 01 00 48 00 48 00 00 00 00 00 00 00 00 00 00...  

在 JPEG 注释中,我们可以闭合 JavaScript 注释,并在 payload 后面为我们的非 ASCII 字符的 JavaScript 变量赋初值,随后在 JPEG 注释的末尾创建另一个多行注释。

FF FE 00 1C 2A 2F 3D 61 6C 65 72 74 28 22 42 75 72 70 20 72 6F 63 6B 73 2E 22 29 3B 2F 2A  

0xFF 0xFE 是注释头的长度,0x00 0x1C 指定了注释的长度,其余的便是我们的 JavaScript payload,当然此处为 */=alert("Burp rocks.")/*

接下来需要闭合 JavaScript 注释,我在图像结束标记之前编辑了图片数据的最后四个字节。文件的结尾看起来是这样子的:

2A 2F 2F 2F FF D9  

0xFF 0xD9是图片结束的标志。很好,我们的多语义 JPEG 就有了,不过这还不够。如果你不指定字符编码,这很有效,但是 FireFox 使用 UTF-8 作为文档编码,包含脚本代码的时候,它破坏了其多语义性。在 MDN 里,它没有说明脚本支持 charset 属性,但实际上是支持的。所有要让脚本正常运行,你需要在 script 标签里指定 ISO-8859-1 编码,这就能运行良好了。

值得注意的是,多语义的 JPEG 可以在 Safari,Firefox,Edge 和 IE 11 上使用。Chrome 机智地没有将图像作为 JavaScript 执行。

多语义 JPEG PoC 在此:

Polyglot JPEG

将图像作为 JavaScript 执行的代码如下:

<script charset="ISO-8859-1" src="http://portswigger-labs.net/polyglot/jpeg/xss.jpg"></script>  

文件大小的限制

我试着将这张图片作为 phpBB 个人资料照片上传,但是文件大小限制为 6k,最大的尺寸为 90x90。我通过裁剪缩小了 logo 的大小,并考虑减少 JPEG 数据的方法。在 JPEG 文件头中,我使用 /* (对应十六进制 0x2F 和 0x2A,合成 0x2F2A)造成了 12074 的长度,这需要大量填充,将导致图形太大无法作为配置文件的长度。查阅 ASCII 表,我试着找出一个字符组合,这将会是有效的 JavaScript 并减少 JPEG 头中的填充量,同时还会被识别为有效的 JPEG 文件。

我可以找到的最小的起始字节便是 0x9(制表符),后面跟着 0x3A(冒号),这最后组合成十六进制 0x093A (2362),为我们的文件节省了不少字节,并创建了一个有效的非 ASCII JavaScript 标签语句,后面便是 JFIF 标识符的变量。然后,我将斜杠 0x2F 而不是 NULL 放在 JFIF 标识符的结尾,将星号放在对应版本号的位置。十六进制是这样的:

FF D8 FF E0 09 3A 4A 46 49 46 2F 2A  

现在我们继续用 NULL 注入其余的 JPEG 头,并注入 JavaScript payload:

FF D8 FF E0 09 3A 4A 46 49 46 2F 2A 01 01 00 48 00 48 00 00 00 00 00 00 00 ... (padding more nulls) 2A 2F 3D 61 6C 65 72 74 28 22 42 75 72 70 20 72 6F 63 6B 73 2E 22 29 3B 2F 2A  

这是更小的图片:

Polyglot JPEG smaller

影响

如果你允许用户上传 JPEG 图片,且这些上传的图片与你的应用程序在同一域下,你的 CSP 还允许来自 "self" 的脚本,你便可以通过注入脚本将其指向图像的方法绕过 CSP 。

结论

总而言之,如果你在你的站点上允许上传 JPEG 图片或者任何形式的文件,这值得将它们放在一个不同的域下。当验证一个 JPEG 时,你应该重写 JPEG 头,以确保其中没有偷偷放置的代码,并且删除 JPEG 注释。显然,你的 CSP 也不必将脚本的图片资源列入白名单。

如果没有Ange Albertini的出色工作,这篇文章就不会出现。我使用它的 JPEG 格式图片创建了通用的多语义 JPEG 图片。Jasvir Nagra 也在 多语义 GIF 上启发了我。

PoC

Enjoy - @garethheyes