当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-098978

漏洞标题:Python开源框架Tornado某缺陷可能造成文件读取漏洞

相关厂商:Tornado开源框架

漏洞作者: phith0n

提交时间:2015-03-01 21:54

修复时间:2015-06-04 08:38

公开时间:2015-06-04 08:38

漏洞类型:设计错误/逻辑缺陷

危害等级:中

自评Rank:8

漏洞状态:已交由第三方合作机构(cncert国家互联网应急中心)处理

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-03-01: 细节已通知厂商并且等待厂商处理中
2015-03-06: 厂商已经确认,细节仅向厂商公开
2015-03-09: 细节向第三方安全合作伙伴开放
2015-04-30: 细节向核心白帽子及相关领域专家公开
2015-05-10: 细节向普通白帽子公开
2015-05-20: 细节向实习白帽子公开
2015-06-04: 细节向公众公开

简要描述:

python开源框架Tornado的一个缺陷,可能造成一些不能被读取的文件被读取。

详细说明:

最近在筹备写一篇关于“任意文件读取”的文章。。。没想到写文章的过程中发现tornado的一个小问题,提出来希望引起大家的重视。
首先,tornado是一个全异步的框架,它有有一个专门处理静态文件的控制器,名字叫StaticFileHandler,在文档(http://www.tornadoweb.org/en/stable/web.html#tornado.web.Application)中可以得知:

QQ20150301-8@2x.png


我们只要指定一个目录对应到这个控制器中,即可在HTTP请求中直接请求到这个目录下的文件。
而且,tornado在setting中,也可以直接指定一个static_path,来说明静态文件放在哪个目录下:

QQ20150301-9@2x.png


那么我们直接吧example拿来,增加一个static_path的设置项:

#!/usr/bin/python
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
if __name__ == "__main__":
setting = {
"debug": True,
"static_path": "/Users/phithon/pro/python/wooyun/tornado-file-read/static/",
}
application = tornado.web.Application([
(r"/", MainHandler),
], **setting)
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()


这时候我们就可以直接请求到/static/01.txt这样的静态文件了:

QQ20150301-10@2x.png


正常情况下,我们是不能够请求到这个目录以外的文件的,如/etc/passwd:

QQ20150301-11@2x.png


如上图,tornado对请求的路径进行了一定判断,抛出了这个错误。然后我们去他源码中看看是怎么判断的:

def validate_absolute_path(self, root, absolute_path):
"""Validate and return the absolute path.
``root`` is the configured path for the `StaticFileHandler`,
and ``path`` is the result of `get_absolute_path`
This is an instance method called during request processing,
so it may raise `HTTPError` or use methods like
`RequestHandler.redirect` (return None after redirecting to
halt further processing). This is where 404 errors for missing files
are generated.
This method may modify the path before returning it, but note that
any such modifications will not be understood by `make_static_url`.
In instance methods, this method's result is available as
``self.absolute_path``.
.. versionadded:: 3.1
"""
root = os.path.abspath(root)
# os.path.abspath strips a trailing /
# it needs to be temporarily added back for requests to root/
if not (absolute_path + os.path.sep).startswith(root):
raise HTTPError(403, "%s is not in root static directory",
self.path)
if (os.path.isdir(absolute_path) and
self.default_filename is not None):
# need to look at the request.path here for when path is empty
# but there is some prefix to the path that was already
# trimmed by the routing
if not self.request.path.endswith("/"):
self.redirect(self.request.path + "/", permanent=True)
return
absolute_path = os.path.join(absolute_path, self.default_filename)
if not os.path.exists(absolute_path):
raise HTTPError(404)
if not os.path.isfile(absolute_path):
raise HTTPError(403, "%s is not a file", self.path)
return absolute_path


关键代码是
root = os.path.abspath(root),
if not (absolute_path + os.path.sep).startswith(root):
os.path.abspath获取root变量指定的绝对路径。
root实际上就是我们之前传入的setting中的“static_path”,这里获得静态文件目录的绝对路径。但在python中,os.path.abspath这个函数获得的路径,是没有结尾处的"/"的。
也就是说,我传入的路径是“/Users/phithon/pro/python/wooyun/tornado-file-read/static/”,经过这个函数处理,变成了“/Users/phithon/pro/python/wooyun/tornado-file-read/static”。
好,接下来这个if语句,absolute_path + os.path.sep就是我请求的静态文件路径,一旦它不是以root开头的话,就会爆403错误。
我们想想可能会出现什么漏洞?
因为root这个变量是没有最后那个“/“的,那么如果我有一个文件是“/Users/phithon/pro/python/wooyun/tornado-file-read/static.db”,或一个目录是“/Users/phithon/pro/python/wooyun/tornado-file-read/static_private/”,那是不是都以“/Users/phithon/pro/python/wooyun/tornado-file-read/static”开头的?但这些文件并不在static这个目录下,所以并没有触发403错误。
这种情况下造成了一个文件读取漏洞,我们读取到了本不应该被读取的某些文件。
举个例子吧,有这么一个应用(下载链接: http://pan.baidu.com/s/1dD6cLWH 密码: gal2),如下是目录结构:

QQ20150301-12@2x.png


有一个数据库叫static_private.sqlite3,有个目录叫static_private,里面放着敏感信息私钥private.key。
我们通过如下请求把他们都读取了:

QQ20150301-13@2x.png


QQ20150301-14@2x.png


所以在特殊情况下,如果开发者指定的静态目录为xxx,那么我们就可以读取所有xxx上层的目录下的名字开头为xxx的文件、目录。
这个其实和设置open_basedir的时候没有带最后一个“/”产生的效果类似。

漏洞证明:

QQ20150301-13@2x.png


QQ20150301-14@2x.png

修复方案:

root = os.path.abspath(root) + os.path.sep

版权声明:转载请注明来源 phith0n@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2015-03-06 08:36

厂商回复:

先行确认,暂未能通过实例完整复现,同时也未能建立与软件生产厂商的联系渠道.

最新状态:

暂无