一、从漏洞机理反推:IPS 究竟能看见什么
|
漏洞机理(官方公告) |
网络侧可观测特征 |
可检测位置 |
|
multipart 请求没有可用 Content-Length,绕过 post_max_size 初检 |
POST + Content-Type: multipart/form-data,但无 Content-Length 头,或使用 chunked |
请求头 |
|
单个 part 内塞入不限数量的 header |
请求体内出现异常密集的 header 行(\\r\\nxxx: yyy 连续多行) |
请求体 |
|
空文件控件不计入 max_file_uploads,仍生成元数据数组 |
请求体内大量 filename="" 的 part |
请求体 |
|
嵌套字段名成倍放大数组开销 |
name="a[b][c][d][e]…" 深度嵌套 |
请求体 |
|
应用侧 OOM:攻击者可控输入导致超大分配 |
请求体/参数体量异常,或无特征(这条 IPS 基本测不到) |
请求体(弱) |
|
CVE-2026-109619 的 session 上传进度路径 |
请求体中出现 PHP_SESSION_UPLOAD_PROGRESS 字段名 |
请求体 |
|
已知攻击者工具/探针 |
上传文件名 MARKER_[8位hex].txt;session 前缀 fprint、dp;工具串 p2shell |
请求体 |
|
已知攻击者基础设施 |
源 IP 206.188.197.105;外连端口 8888 / 8899 / 9999 |
网络层 / 请求体 |
|
内存泄露路径(PHPSESSID 泄漏堆内存) |
响应头 Set-Cookie: PHPSESSID= 值异常长或畸形 |
响应头(很多 IPS 默认不检测响应,需显式开启) |
|
利用后果 |
同一源 IP 的异常 multipart 之后紧随大量 500 |
需 SIEM 关联,IPS 单条规则做不了 |
结论:IPS 能覆盖的是请求构造特征和已知 IOC这两块;应用侧 OOM 路径和"是否真的被打成功"这两件事,IPS 管不了,要靠主机侧(PHP Worker 派生子进程)和日志关联。
二、规则字段结构(通用 IPS 图形化界面对照表)
不管你用的是哪一款 IPS/WAF,自定义规则基本都是下面这些字段,照着填即可:
|
规则字段 |
本场景取值 |
说明 |
|
规则名称 |
PHP2Shell-multipart-no-content-length 等 |
建议带 CVE 与特征名,便于后续审计 |
|
规则类型 / 协议 |
HTTP |
需勾选"HTTP 协议解码" |
|
方向 / 区域 |
外网区/互联网 → DMZ 或服务区 |
内网横向场景可再建一条 any→any |
|
源地址 |
any(或 $EXTERNAL_NET) |
IOC 规则填具体 IP |
|
目的地址 |
Web 服务器地址组 |
精确到资产组,减少误报面 |
|
目的端口 |
80, 443, 8080 等 |
443 必须已开启解密 |
|
匹配项 1:HTTP 方法 |
等于 / 包含 POST |
部分产品字段名 http.method |
|
匹配项 2:请求头 Content-Type |
包含 multipart/form-data(不区分大小写) |
字段名 http.header / http_request_header |
|
匹配项 3:请求头 Content-Length |
不存在,或值为 0 |
字段名同上;不存在用"取反/不存在"运算符 |
|
匹配项 4:请求体关键字 |
包含 PHP_SESSION_UPLOAD_PROGRESS |
字段名 http.request_body / http_client_body |
|
匹配项 5:请求体正则 |
MARKER_[0-9a-fA-F]{8}\\.txt |
开 PCRE,不区分大小写 |
|
匹配项 6:请求体正则 |
name\\s*=\\s*"[^"]{0,120}(?:\\[[^\\]]{0,40}\\]){8,} |
嵌套 ≥8 层 |
|
匹配项 7:请求体正则 |
(?:\\r\\n[^\\r\\n]{1,160}:[ ]){25,} |
单 part 内 25+ 个 header 行 |
|
匹配项 8:请求体大小 |
> 业务基线(建议先按 post_max_size 设 8M/32M) |
字段名 request_body_size / Content-Length |
|
匹配项 9:响应头(需开启响应检测) |
正则 Set-Cookie\\s*:\\s*PHPSESSID=[^;\\r\\n]{48,} |
字段名 http.response_header |
|
匹配项 10:URI 例外 |
不等于 业务真实上传接口清单 |
用白名单降低"非上传接口出现 multipart"的误报 |
|
动作 |
分级:观察期=仅告警 → 稳定后=阻断;IOC 类=直接阻断 |
见第五节分级表 |
|
阈值 / 频率 |
track by_src, count 5, seconds 60 一类 |
用计数替代"正则计数",更稳 |
|
日志 |
留存原始请求头 + 请求体前 N 字节 |
出事回溯要用;注意别把完整含敏感数据的 body 长期留存 |
|
例外 / 白名单 |
业务上传接口、已知扫描器、内网监控探针 |
必须先跑观察期再定 |
|
规则元数据 |
cve: CVE-2026-109618, CVE-2026-109619;reference 指向官方公告 |
便于后续检索与合规说明 |
三、逐条规则(Suricata 6/7 语法 + 字段说明 + 误报处理)
语法以 Suricata 为例。Snort 3 字段名:http_method / http_header / http_client_body / http_uri / http_stat_code;商业 IPS 按第二节字段表对照填写。SID 使用 9100000 段,请与你现有编号体系对齐。
规则 1:multipart POST 缺失 Content-Length(高价值)
- 匹配字段:http.method(POST)、http.header(Content-Type 含 multipart)、http.header(不存在 Content-Length:)
- 对应机理:官方列出的第一条 OOM 绕过手法——没有可用 Content-Length 的 multipart 请求可绕过初始 post_max_size 检查
- 误报:HTTP/1.0 客户端、部分老旧代理、chunked 上传。若你的版本不支持 sticky buffer 内取反,改为 http.header; pcre:"/^Content-Length\\s*:\\s*(0)?\\s*$/mi";
- 动作建议:观察期告警,稳定后阻断
规则 2:请求体出现 PHP_SESSION_UPLOAD_PROGRESS( hunting 首选)
- 匹配字段:http.request_body,关键字 PHP_SESSION_UPLOAD_PROGRESS
- 价值:这是 CVE-2026-109619 已确认利用链(session 上传进度作为 producer/consumer)的直接信号,也是蜜罐观测到的两条利用手法之一
- 误报:⚠️ 业务本身使用 PHP 上传进度功能会触发。因此建议先设为"仅告警",跑满一个业务周期后按 URI 白名单收敛
- 注意:该功能默认开启,且不需要应用调用 session_start()
规则 3:已知 IOC 文件名 MARKER_
- 匹配字段:http.request_body 正则 MARKER_[0-9a-fA-F]{8}\\.txt
- 动作:直接阻断,误报概率极低
规则 4:字段名深度嵌套(数组开销放大)
- 匹配字段:http.request_body 正则,统计 name="…" 内方括号嵌套层数 ≥ 8
- 机理:官方明确"嵌套字段名会成倍放大数组生成开销"
- 调参:{8,} 是嵌套层数阈值。PHP max_input_nesting_level 默认值较高,建议先用 810 观察,别一上来设 23(正常表单也可能有数组)
规则 5:单个 part 内异常密集的 header(启发式)
- 匹配字段:http.request_body 正则,25 个以上形如 \\r\\nxxx: yyy 的连续行
- 机理:官方列出"单个 part 可包含不限数量的 header,其在 PHP 内部的内存表示远大于线上表示"
- ⚠️ 这是启发式规则,性能开销较大,务必加 threshold 限速,并优先只在高价值资产上开
规则 6:大量空文件控件(用阈值计数替代正则计数)
- 匹配字段:http.request_body 正则 filename="";计数交给 threshold(同源 60 秒内 20 次)
- 机理:官方明确"空文件控件不计入 max_file_uploads,但仍生成正常的文件元数据数组"
- 为什么不用正则计数:PCRE 不支持"出现 N 次"这种计数语义,用规则引擎的 threshold 做计数既准确又省性能——这是写 IPS 规则的一个通用技巧
规则 7:已知攻击者 IOC(网络层,直接阻断)
- 匹配字段:源 IP;以及出向目的端口 8888 / 8899 / 9999
- 说明:8888 为带外交互监听器、8899 为轮换代理池端点、9999 为暴露的操作者工作目录
- 动作:直接阻断。出向那条建议先告警——因为 8888/9999 也是常见业务端口,需先确认基线
规则 8:响应头 PHPSESSID 异常(内存泄露路径特征)
- 匹配字段:http.response_header(必须显式开启响应方向检测,很多 IPS 默认只检请求)
- 机理:Wiz 指出内存泄露路径会把进程内存通过 PHPSESSID 响应 Cookie 泄漏出来,表现为异常大或畸形的 Cookie 值
- 基线:PHP 默认 session id 为 26 位十六进制。阈值 48 需按你应用的实际 session id 长度校准
- 处置:命中即视为可能发生内存泄露,按凭据泄露处理并轮换
四、规则清单汇总(给领导/变更单用)
|
SID |
名称 |
置信度 |
误报风险 |
建议动作 |
|
9100001 |
multipart 缺失 Content-Length |
中高 |
中(HTTP/1.0、chunked) |
观察→阻断 |
|
9100002 |
请求体含 PHP_SESSION_UPLOAD_PROGRESS |
高 |
中高(业务可能自用) |
仅告警(先) |
|
9100003 |
MARKER_[8hex].txt IOC |
极高 |
极低 |
直接阻断 |
|
9100004 |
字段名深度嵌套 ≥8 |
中 |
中 |
告警 |
|
9100005 |
part 内 header 行 ≥25 |
中(启发式) |
中 |
告警 + 限速 |
|
9100006 |
空文件控件高频 |
中 |
中 |
告警(阈值计数) |
|
9100007 |
已知攻击者 IP |
极高 |
无 |
直接阻断 |
|
9100008 |
出向 8888/8899/9999 |
中 |
中(常见端口) |
告警(先) |
|
9100009 |
PHPSESSID 值异常 |
中高 |
中(需校基线) |
告警(触发取证) |
五、动作与阈值分级建议
|
阶段 |
时间 |
动作 |
目的 |
|
第一阶段 |
上线后 24~48 小时 |
全部仅告警 + 记录原始请求头/请求体前 N 字节 |
摸清误报来源 |
|
第二阶段 |
误报收敛后 |
IOC 类(9100003/9100007)改阻断;其余保持告警 + 阈值限速 |
挡住已知威胁 |
|
第三阶段 |
打完 PHP 补丁后 |
保留告警,不删除 |
补丁前的历史攻击回溯 + 后续变种监测 |
六、上线步骤(按顺序做,别跳)
七、检测盲区(写进报告里,别让领导以为上了规则就安全)
|
盲区 |
说明 |
弥补手段 |
|
HTTPS 未解密 |
看不到任何 body 特征 |
开启解密或卸载 |
|
body 检测深度不足 |
特征在检测窗口之外 |
调大深度 |
|
分片 / chunked 绕过 |
特征被切到多个 TCP 段或 chunk |
确认 IPS 做了 HTTP 重组(多数商业 IPS 有此开关) |
|
无 Content-Length + 超长流式 body |
IPS 无法预先判定大小 |
在 Nginx/Apache 层做硬性 client_max_body_size / LimitRequestBody |
|
应用侧 OOM 路径 |
与 multipart 无关,攻击者可控输入导致大分配 |
IPS 测不到,只能靠 memory_limit=-1 缓解 + 应用层输入约束 |
|
是否已被打成功 |
利用发生在应用层之下,无 WebShell、进程命令行正常 |
主机侧:监测 PHP/Web 进程派生 /bin/sh、curl、wget |
|
响应侧 Cookie 异常 |
默认不检响应 |
显式开启响应检测 |



