scan4all 实战指南:使用 Smuggler 检测 HTTP 请求走私(CL.TE / TE.CL Desync)漏洞
【免费下载链接】scan4allOfficial repository vuls Scan: 15000+PoCs; 23 kinds of application password crack; 7000+Web fingerprints; 146 protocols and 90000+ rules Port scanning; Fuzz, HW, awesome BugBounty( ͡° ͜ʖ ͡°)...项目地址: https://gitcode.com/GitHub_Trending/sca/scan4all
本文以 scan4all 仓库中集成的 Smuggler 工具为线索,系统讲解 HTTP Request Smuggling(请求走私 / Desync)检测工具 Smuggler 的原理、安装、命令行用法、变异配置机制与检测判定逻辑。读完本文,你将掌握如何用单条命令对目标发起 TECL / CLTE 两类走私测试、如何读懂测试输出、如何自定义 payload 变异配置,以及 scan4all 如何用 Go 原生实现同一套走私检测能力。
Smuggler 是什么:面向 HTTP Desync 的变异测试器
Smuggler 是一个用 Python 3 编写的 HTTP Request Smuggling / Desync 测试工具(作者 @defparam,即 Evan Custodio),完整源码位于仓库的 tools/Smuggler/smuggler.py,其配套的变异配置、payload 输出目录与许可证文件均收在 tools/Smuggler 目录下。
Smuggler 的核心思路并不复杂:通过构造大量畸形的Transfer-Encoding头变异体,配合精心设计的Content-Length数值,探测前端代理(如负载均衡、CDN、WAF)与后端服务器之间对请求边界解析的不一致,从而发现 CL.TE(Content-Length 优先、后端却按 Transfer-Encoding 解析)与 TE.CL(Transfer-Encoding 优先、后端却按 Content-Length 解析)两类典型的走私通道。其检测方法源自 PortSwigger 研究院 James Kettle 发表的《HTTP Desync Attacks: Request Smuggling Reborn》研究,并经由 Nahamsec(Ben Sadeghipour)在 NahamCon 2020 上的实战化验证。
需要说明的是,Smuggler 属于通用开源安全测试工具,scan4all 将其整体收纳进
tools/目录供安全研究者直接调用,同时 scan4all 自身也内置了一套 Go 语言实现的原生走私检测模块(见下文"scan4all 的原生走私检测实现"一节)。
重要提示:误报与漏报边界
工具作者在 README 的IMPORTANT一节中明确划定了检测边界,使用者应始终牢记:
- 该工具不保证零误报(false-positive)与零漏报(false-negative);
- 某个变异体报告 OK,并不代表目标不存在 desync 问题;
- 工具提示存在潜在 desync 问题,也不代表目标上必然存在可利用的走私漏洞;
- Google、AWS、Yahoo、Akamai 等大型实体的请求处理器可能产生误报结果。
因此,Smuggler 的定位是初步筛查,任何告警都必须结合人工复核与后续的利用验证(见后文"Helper Scripts"一节)才能定性。
安装与运行环境
Smuggler 依赖 Python 3.x,源码入口在 tools/Smuggler/smuggler.py。在 scan4all 仓库根目录下即可直接运行:
# 查看帮助(确认 Python 3 环境就绪) python3 tools/Smuggler/smuggler.py -h需要联网安装原版时,官方流程为:git clone原仓库 → 进入目录 →python3 smuggler.py -h。由于本仓库已完整收纳该工具,直接基于 tools/Smuggler 目录使用即可,无需额外下载依赖。程序启动时会打印 ASCII banner 与版本号(源码中定义为Version = "v1.1",见 smuggler.py),并在检测到 Python 版本低于 3.0 时直接退出。
基本用法:单目标与批量目标
Smuggler 至少需要两种输入方式之一:通过-u/--url指定单个 URL,或通过stdin 管道传入 URL 列表。
单主机检测
python3 smuggler.py -u <URL><URL>需携带完整协议与端点,例如https://example.com/login.php。工具会先对该端点发起一次"探路"请求收集 Cookie(见下文源码解析),随后对端点逐一测试所有变异。
批量主机检测
cat list_of_hosts.txt | python3 smuggler.py管道模式下,每行一个目标。Smuggler 对每行做 tokenize 处理(server.split(" "),见 smuggler.py):如果某行只有 URL 而没有指定 HTTP 方法,则自动补充命令行中-m指定的方法(默认GET,见 smuggler.py)。
URL 的协议与端口解析规则
源码中的process_uri()函数(smuggler.py)定义了严格的协议判定逻辑:
| URL 形式 | 判定结果 |
|---|---|
以https://开头 | 使用 SSL/TLS 连接,默认端口 443 |
以http://开头 | 不使用 SSL/TLS,默认端口 80 |
| 未指定协议(仅主机名) | 自动补全为https://(见 smuggler.py) |
| URL 中显式带端口 | 以显式端口为准,忽略默认端口 |
| 其他畸形 scheme | 报错Error malformed URL not supported并退出 |
命令行参数全解
Smuggler 的命令行参数如下(与 README 及源码argparse定义一致,见 smuggler.py):
usage: smuggler.py [-h] [-u URL] [-v VHOST] [-x] [-m METHOD] [-l LOG] [-q] [-t TIMEOUT] [--no-color] [-c CONFIGFILE]| 参数 | 默认值 | 说明 |
|---|---|---|
-u, --url <URL> | 无 | 目标 URL(需含端点路径),与 stdin 管道二选一 |
-v, --vhost <host> | 空 | 指定与服务器地址不同的Host头值,用于绕过基于 Host 的虚拟主机路由 |
-x, --exit_early | 关 | 在某个目标发现首个潜在问题时立即结束该目标扫描;管道模式下自动跳到下一个主机 |
-m, --method <method> | POST | 指定 HTTP 方法,如 GET/PUT/PATCH/OPTIONS/CONNECT/TRACE/DELETE/HEAD 等 |
-l, --log <file> | 无 | 将输出同时写入日志文件(日志默认不含颜色码) |
-q, --quiet | 关 | 安静模式,仅输出发现的问题,减少冗余信息 |
-t, --timeout <value> | 5 | Socket 超时秒数,见下方取值权衡 |
--no-color | 关 | 抑制输出到 stdout 的颜色码 |
-c, --configfile <file> | default.py | 指定 payload 变异配置文件(见下一节) |
关于-t/--timeout的取值:该值应大到足以判断 socket 是否挂起(hang),又小到足以加快测试速度。其原理是走私检测依赖"连接被服务端挂起等待"这一信号,超时值设定是否合理直接影响判定准确性与扫描耗时。
配置系统:Python 驱动的变异定义
配置文件是位于./configs目录下的Python 文件(default.py、doubles.py、exhaustive.py)。这些文件描述了 HTTP 请求的内容与要测试的 Transfer-Encoding 变异。运行时,smuggler.py 会定位配置文件(若未以/开头则自动拼接至configs/目录)、读取脚本并通过exec(script)动态执行,脚本内通过向全局字典mutations写入"变异名 → payload 模板"来注册测试项。
模板渲染函数与占位符
三个配置文件的模板渲染函数完全一致,以 default.py 为例:
def render_template(gadget): RN = "\r\n" p = Payload() p.header = "__METHOD__ __ENDPOINT__?cb=__RANDOM__ HTTP/1.1" + RN # p.header += "Transfer-Encoding: chunked" +RN p.header += gadget + RN p.header += "Host: __HOST__" + RN p.header += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" + RN p.header += "Content-type: application/x-www-form-urlencoded; charset=UTF-8" + RN p.header += "Content-Length: __REPLACE_CL__" + RN return p模板中使用了 6 个占位符,在检测执行阶段被替换为实际值:
| 占位符 | 含义 | 替换来源 |
|---|---|---|
__METHOD__ | HTTP 方法 | -m参数 |
__ENDPOINT__ | 目标路径 | URL 中的 path |
__RANDOM__ | 随机数(query 参数cb) | 随机生成,用于绕过缓存/去重 |
__HOST__ | Host 头值 | -v参数或目标主机名 |
__REPLACE_CL__ | Content-Length 数值 | 由检测函数按场景覆盖(见下文判定逻辑) |
Cookie: ... | 会话 Cookie | 自动收集并追加(见_get_cookies) |
default.py:快速变异集
default.py 是速度最快的配置,因为它包含的变异最少。其变异分两类:
手工精选的 4 个基础变异:
mutations["nameprefix1"] = render_template(" Transfer-Encoding: chunked") mutations["tabprefix1"] = render_template("Transfer-Encoding:\tchunked") mutations["tabprefix2"] = render_template("Transfer-Encoding\t:\tchunked") mutations["space1"] = render_template("Transfer-Encoding : chunked")基于控制字符/空白字符轮转的批量变异:对0x1, 0x4, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0x1F, 0x20, 0x7f, 0xA0, 0xFF这 13 个字节值,在每个值上生成 10 种位置变体(命名中%02x为字符的十六进制):
for i in [0x1,0x4,0x8,0x9,0xa,0xb,0xc,0xd,0x1F,0x20,0x7f,0xA0,0xFF]: mutations["midspace-%02x"%i] = render_template("Transfer-Encoding:%cchunked"%(i)) # 冒号后、chunked 前 mutations["postspace-%02x"%i] = render_template("Transfer-Encoding%c: chunked"%(i)) # 连字符后、冒号前 mutations["prespace-%02x"%i] = render_template("%cTransfer-Encoding: chunked"%(i)) # 整行最前 mutations["endspace-%02x"%i] = render_template("Transfer-Encoding: chunked%c"%(i)) # 值末尾 mutations["xprespace-%02x"%i] = render_template("X: X%cTransfer-Encoding: chunked"%(i)) # 前置 X 头后 mutations["endspacex-%02x"%i] = render_template("Transfer-Encoding: chunked%cX: X"%(i)) # 后置 X 头 mutations["rxprespace-%02x"%i] = render_template("X: X\r%cTransfer-Encoding: chunked"%(i)) # CR + 字节 + TE mutations["xnprespace-%02x"%i] = render_template("X: X%c\nTransfer-Encoding: chunked"%(i)) # 字节 + LF + TE mutations["endspacerx-%02x"%i] = render_template("Transfer-Encoding: chunked\r%cX: X"%(i)) mutations["endspacexn-%02x"%i] = render_template("Transfer-Encoding: chunked%c\nX: X"%(i))这些变异的共同目的是绕过前端代理对标准Transfer-Encoding: chunked的精确字符串匹配:通过插入制表符、空格、CR、LF、0x7F、0xA0、0xFF 等字节,使得畸形头仍能被后端服务器解析为 chunked,却逃逸前端的检测规则。命名中midspace(中插)、postspace(后插)、prespace(前置)、endspace(末尾)、xprespace/endspacex(借助伪造X: X头包裹)直观描述了字符插入位置。
doubles.py:双字节组合变异(慢速、垂直场景)
doubles.py 定位为 niche(垂直细分场景)、速度慢。它把变异升级为两个字节同时插入的组合,覆盖两个字节范围:
- 低字节区:
0x1 ~ 0x20; - 高字节区:
0x7F ~ 0xFF。
对每个字节对(i, i)(两字节取相同值)生成 6 种位置组合:
mutations["%02x-%02x-XX-XX"%(i,i)] = render_template("%cTransfer-Encoding%c: chunked"%(i,i)) # 前+冒号前 mutations["%02x-XX-%02x-XX"%(i,i)] = render_template("%cTransfer-Encoding:%cchunked"%(i,i)) # 前+值内 mutations["%02x-XX-XX-%02x"%(i,i)] = render_template("%cTransfer-Encoding: chunked%c"%(i,i)) # 前+末尾 mutations["XX-%02x-%02x-XX"%(i,i)] = render_template("Transfer-Encoding%c:%cchunked"%(i,i)) # 冒号前+值内 mutations["XX-%02x-XX-%02x"%(i,i)] = render_template("Transfer-Encoding%c: chunked%c"%(i,i)) # 冒号前+末尾 mutations["XX-XX-%02x-%02x"%(i,i)] = render_template("Transfer-Encoding:%cchunked%c"%(i,i)) # 值内+末尾XX表示正常字节、%02x表示插入的变异字节。当单一字节变异被前端过滤后,双字节组合往往能突破部分解析器的匹配逻辑。
exhaustive.py:穷举全量变异(极慢)
exhaustive.py 是最慢的配置,它叠加了 default.py 的全部手工变异与单字节轮转,并额外引入大量"技巧型"变异:
mutations["spacejoin1"] = render_template("Transfer Encoding: chunked") # 空格替代连字符 mutations["underjoin1"] = render_template("Transfer_Encoding: chunked") # 下划线替代连字符 mutations["smashed"] = render_template("Transfer Encoding:chunked") mutations["valueprefix1"] = render_template("Transfer-Encoding: chunked") # 双空格 mutations["vertprefix1"] = render_template("Transfer-Encoding:\u000Bchunked") # 垂直制表符 mutations["commaCow"] = render_template("Transfer-Encoding: chunked, cow") # 逗号附加未知值 mutations["cowComma"] = render_template("Transfer-Encoding: cow, chunked") mutations["contentEnc"] = render_template("Content-Encoding: chunked") # 混淆为 Content-Encoding mutations["linewrapped1"] = render_template("Transfer-Encoding:\n chunked") # 换行续行 mutations["quoted"] = render_template("Transfer-Encoding: \"chunked\"") # 引号包裹 mutations["aposed"] = render_template("Transfer-Encoding: 'chunked'") mutations["lazygrep"] = render_template("Transfer-Encoding: chunk") # 截断值 mutations["sarcasm"] = render_template("TrAnSFer-EnCODinG: cHuNkeD") # 大小写混淆 mutations["yelling"] = render_template("TRANSFER-ENCODING: CHUNKED") mutations["0dsuffix"] = render_template("Transfer-Encoding: chunked\r") # CR 后缀 mutations["tabsuffix"] = render_template("Transfer-Encoding: chunked\t") mutations["revdualchunk"] = render_template("Transfer-Encoding: cow\r\nTransfer-Encoding: chunked") # 双 TE 头 mutations["0dspam"] = render_template("Transfer\r-Encoding: chunked") # CR 注入连字符 mutations["nested"] = render_template("Transfer-Encoding: cow chunked bar") # 多值 mutations["spaceFF"] = render_template("Transfer-Encoding:\xFFchunked") mutations["accentCH"] = render_template("Transfer-Encoding: ch\x96nked") # 高位字节伪造 mutations["accentTE"] = render_template("Transf\x82r-Encoding: chunked") mutations["x-rout"] = render_template("X:X\rTransfer-Encoding: chunked") # CR 前导 mutations["x-nout"] = render_template("X:X\nTransfer-Encoding: chunked") # LF 前导随后对0x1 ~ 0x1F与0x7F ~ 0xFF两个区间做单字节的midspace/postspace/prespace/endspace四种轮转。整体覆盖了大小写混淆、多 TE 头、值内垃圾字符、续行折叠、高位字节伪装等攻击面。
自定义配置文件
Smuggler 目前没有提供自定义请求头与 User-Agent 的输入参数,官方建议以 default.py 为蓝本复制一份自己的配置文件再按需修改。修改要点包括:
- 在
render_template中调整User-Agent、Content-type等静态头; - 在
mutations[...] = render_template(...)处增删变异项; - 通过
-c/--configfile <file>指定自建配置(默认default.py)。
检测判定逻辑:TECL 与 CLTE 双通道验证
Smuggler 的检测循环位于_create_exec_test()(smuggler.py),对每个变异体依次执行两类探测:
- TECL 探测(
_check_tecl,smuggler.py):假设目标存在 TE 优先于 CL 的解析分歧。攻击载荷将Content-Length设为6(大于实际 body),body 为EndChunk + "X",使按 TE 解析的一侧立即读完 body,而按 CL 解析的一侧永远等待剩余的 1 字节。 - CLTE 探测(
_check_clte,smuggler.py):假设目标存在 CL 优先于 TE 的解析分歧。攻击载荷将Content-Length设为4(小于实际 body),body 为Chunked("Z") + EndChunk,使按 CL 解析的一侧完整接收,而按 TE 解析的一侧在 chunked 数据结束后还有残留字节等待解析。
探测结果由_test()(smuggler.py)以 4 个返回码标记:
| 返回码 | 含义 | 判定走向 |
|---|---|---|
0 | 收到正常响应 | 未发现该变异下的 desync |
1 | 连接超时(socket hang) | 潜在 desync 信号,进入边界复核 |
2 | 超时前断开连接 | 标记DISCONNECTED |
-1 | 异常 | 标记SOCKET ERROR |
判定与去噪流程(_create_exec_test)为:
- 先跑 TECL 探测、再跑 CLTE 探测,分别计时并记录响应状态码(输出形如
(TECL: 1.20 - 200)); - 若 CLTE 探测超时(返回码 1),立即用边界载荷复核(
_check_clte的ptype=1,将 CL 设为正常值11重新发送):若边界载荷返回正常响应(返回码 0),说明此前超时确由走私触发而非网络抖动; - 复核通过后触发同变异重试机制:连续 3 次(
self._attempts < 3)均命中才最终判定Potential CLTE Issue Found/Potential TECL Issue Found,最大限度压缩误报; - 若超时载荷与边界载荷都超时(如
CLTE TIMEOUT ON BOTH LENGTH 4 AND 11),则判定为无边界行为,不告警。
在正式探测前,run()还会调用_get_cookies()(smuggler.py)对目标发一次正常 GET 请求,自动提取Set-Cookie并在后续所有攻击载荷中附加Cookie头——这是保证带会话校验的端点也能被正确测试的关键细节。
payloads 目录:自动导出攻击载荷
在 Smuggler 目录内设有 payloads 目录(仓库内为 tools/Smuggler/payloads)。当 Smuggler 发现潜在的 CLTE 或 TECL desync 问题时,会自动将有问题的载荷以二进制 txt 文件dump 到该目录。文件名按协议_主机_漏洞类型_变异名.txt的规则标注(协议前缀为http_/https_,主机名中的.替换为_,见 smuggler.py),例如:
payloads/https_www_example_com_CLTE_nameprefix1.txt这些文件可以直接用nc(netcat)重放到目标服务器,或导入其他分析工具做进一步验证与利用,是复现问题与编写 PoC 的重要资产。
Helper Scripts:从检测到利用
Smuggler 本身只负责探测。找到 desync 问题后,作者推荐使用其配套的 Turbo Intruder desync 脚本集(DesyncAttack_CLTE.py与DesyncAttack_TECL.py)来编排完整的走私攻击。这两个脚本分别针对 CLTE 与 TECL 场景,负责在确认漏洞后构造"前缀请求 + 走私请求"的完整攻击链。将 Smuggler 检测阶段 dump 的 payload 与 Turbo Intruder 利用脚本配合,即可完成从发现到利用的闭环。
scan4all 的原生走私检测实现(Go 版)
除收纳 Python 版 Smuggler 外,scan4all 还内置了一套Go 语言实现的走私检测引擎,位于 lib/Smuggling。它通过一个统一接口抽象各类走私类型(CheckSmuggling.go):
type Smuggling interface { CheckResponse(body string, payload string) bool GetPayloads(t *socket.CheckTarget) *[]string GetTimes() int GetVulType() string }引擎注册了 6 类检测器(CheckSmuggling.go):NewClCl()、NewCLTE()、NewCLTE2()、NewTECL()、NewTETE()、NewErr(),对应实现文件为同目录下的 CL_CL.go、CL_TE.go、CL_TE2.go、TE_CL.go、TE_TE.go 与 Err.go。相较 Python 版仅覆盖 TECL/CLTE,Go 版额外覆盖了 CL.CL、TE.TE 与错误解析(Err)等更多变体。
DoCheckSmuggling()(CheckSmuggling.go)对每个检测器并发起 goroutine 执行:对目标用socket.NewCheckTarget建立 TCP 连接,循环发送该检测器的全部 payload,命中即通过util.SendAnyData上报漏洞。该实现同样采用"每个目标登录页只测一次、相同上下文页面只测一次"的收敛策略(见文件头部注释),适合在扫描流程中大规模铺开。
因此,scan4all 用户拥有两条走私检测路径:对单个目标做精细化人工验证时,用 tools/Smuggler/smuggler.py;在全量资产扫描中自动发现走私类问题时,由 lib/Smuggling 的 Go 引擎完成。
在 scan4all 中编排 Smuggler 的推荐流程
综合以上能力,一个完整的请求走私检测流程可以这样组织:
- 初筛:通过 scan4all 的全量扫描,让 lib/Smuggling 的 Go 引擎对资产中的登录页、核心接口做并发走私探测,快速圈定可疑目标;
- 精测:对可疑目标使用
python3 tools/Smuggler/smuggler.py -u https://target/endpoint,先用默认配置(default.py)快速过一遍,再视结果切换-c exhaustive.py做全量变异; - 复核:在
payloads/目录获取工具自动 dump 的攻击载荷,用 netcat 直连服务端复现,确认超时与请求悬挂现象; - 利用:借助 TECL/CLTE 利用脚本构造走私攻击链,验证是否可越权访问被前置代理屏蔽的路径(如 WebLogic 管理页面等受 ACL 保护的资源)。
License
Smuggler 及其脚本以 MIT 许可证发布,许可证文本见仓库中的 tools/Smuggler/LICENSE,源码头部亦声明了版权归属(smuggler.py)。
【免费下载链接】scan4allOfficial repository vuls Scan: 15000+PoCs; 23 kinds of application password crack; 7000+Web fingerprints; 146 protocols and 90000+ rules Port scanning; Fuzz, HW, awesome BugBounty( ͡° ͜ʖ ͡°)...项目地址: https://gitcode.com/GitHub_Trending/sca/scan4all
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考