Tornado 文件读取漏洞

Tornado 文件读取漏洞

漏洞框架描述:

Tornado是一个全异步的框架,它有有一个专门处理静态文件的控制器,名字叫StaticFileHandler。

漏洞影响:

影响3.1及以上版本

漏洞复现:

增加一个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这样的静态文件了:

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

 

如上图,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://.../s/1dD6cLWH 密码: gal2),如下是目录结构:

有一个数据库叫static_private.sqlite3,有个目录叫static_private,里面放着敏感信息私钥private.key。

我们通过如下请求把他们都读取了:

 

所以在特殊情况下,如果开发者指定的静态目录为xxx,那么我们就可以读取所有xxx上层的目录下的名字开头为xxx的文件、目录。

这个其实和设置open_basedir的时候没有带最后一个“/”产生的效果类似。

 

可以通过这种方式读取到文件内容了 

 漏洞防护:

官方暂未对该漏洞提供解决方案。

不过,在web.py的StaticFileHandler类的docstring中提到:

StaticFileHandler的docstring

Python

This handler is intended primarily for use in development and light-duty

file serving; for heavy traffic it will be more efficient to use

a dedicated static file server (such as nginx or Apache).

 StaticFileHandler类主要用于开发环境和轻型的文件服务,建议用户在线上的大流量环境中使用更加专注的静态文件服务器(如nginx/Apache)。

Guess you like

Origin blog.csdn.net/qq_48985780/article/details/121355371