input() 和 request()->param() 默认过滤参数,无法用于审计日志,因其会转义、截断或类型转换原始输入;应通过 getRawContent() 或 server('QUERY_STRING') 在最早入口获取未加工数据,并配置 trust_proxies 防 IP 伪造。

ThinkPHP 的 input() 和 request()->param() 默认会对参数做自动过滤(如 trim、htmlspecialchars),原始输入在进业务逻辑前就已被“处理”——审计时看到的不是攻击者发的,而是框架转义/截断/类型转换后的结果。想还原真实请求,必须绕过这些默认过滤,在最早入口捕获未加工数据。
为什么 input() 不能用于审计日志
input() 的第三个参数(filter)会直接修改值,且不可逆。比如 input('content', '', 'htmlspecialchars') 把 <script></script> 变成 <script></script>,日志里只剩转义串,无法判断原始意图。更隐蔽的是:当 filter 是 intval 或 floatval 时,input('id', 0, 'intval') 会把 1' OR 1=1-- 强制变成 1,SQL 注入痕迹彻底消失。
- 所有带 filter 参数的
input()调用,都等于主动丢弃审计线索 -
request()->param()同样受default_filter配置影响,不是“原始” - 即使没显式传 filter,TP6 默认对 GET/POST 数据做了
trim和strip_tags(取决于配置),仍非原始字节流
如何在控制器开头安全抓取原始请求体
必须在任何 input() 或 request()->param() 调用之前,用底层方法获取未解析、未过滤的原始数据。
- POST/PUT/DELETE 请求:调用
$this->request->getRawContent(),返回 raw body 字节流;JSON 请求需手动json_decode($raw, true),别依赖input('json.key') - GET 请求:用
$this->request->server('QUERY_STRING')获取完整查询串,再parse_str()解析——它保留空格、+、%20等编码差异,而request()->get()已被 PHP 自动 urldecode 过 - 不要用
$this->request->get()或$this->request->post()替代:它们是 PHP$_GET/$_POST的封装,已触发自动解析和部分过滤
中间件统一捕获可疑参数的实操要点
靠每个控制器手写日志极易遗漏,中间件是唯一能稳定拦截所有请求、且早于业务逻辑的位置。
立即学习“PHP免费学习笔记(深入)”;
- 新建中间件
LogSuspiciousInputMiddleware,注册为全局中间件 - 在
handle()中优先调用$request->getRawContent(),非空则尝试 JSON 解析;同时用$request->get()和$request->post()补充 GET/POST 键值对(注意:它们比input()更接近原始,但仍有局限) - 可疑特征匹配建议用:
/<[a-z][^>]*>/i(HTML 标签)、/javascript:/i、/on\w+\s*=/i(内联事件)、/\$\{.*?\}/(EL 表达式)、长度 > 5000 字符 - 记录字段至少含:
ip、url、method、suspicious_keys、sample_value(截取前 100 字符)、timestamp
audit 日志里最容易被忽略的 IP 来源问题
日志里的 $request->ip() 如果没配 trust_proxies,大概率是伪造的。攻击者 curl -H "X-Forwarded-For: 127.0.0.1" 就能让所有审计日志的 IP 变成内网地址,封禁、限流、溯源全失效。
- 必须在
config/app.php显式设置'trust_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],只填最靠近 PHP 的可信代理段(如 Nginx 内网 IP 段) - Nginx 层必须主动覆盖 XFF:
proxy_set_header X-Forwarded-For $remote_addr;,否则后端白名单形同虚设 - 禁止在 audit 日志中直接读
$_SERVER['HTTP_X_FORWARDED_FOR'],这等于绕过 TP 所有校验逻辑



















