真正能定位攻击者的是User-Agent与可疑参数的组合行为,而非单纯匹配SQL关键字;需通过access_log中$remote_addr、$request、$http_user_agent三字段串联攻击链,重点检测同一UA短时间内高频携带'、sleep(、UNION SELECT等载荷访问多路径,并筛查UA、Referer、Cookie等头部字段内嵌的SQL片段。

直接从 access_log 提取 User-Agent 和可疑参数值
日志里真正能定位攻击者的,不是 SELECT 出现在哪一行,而是谁发的、怎么发的。Apache 或 Nginx 的 access_log 默认记录 $remote_addr、$request、$http_user_agent,这三个字段足够串起一次完整攻击行为。
关键不是“有没有 SQL 关键字”,而是“同一个 User-Agent 是否在短时间内高频携带单引号、sleep(、UNION SELECT 等载荷访问多个路径”。攻击者用 sqlmap 扫描时,User-Agent 很可能固定为 sqlmap/1.0.8.15#dev,而手工注入则常伪造成浏览器 UA,但请求体或 URL 中会暴露真实意图。
- 用
awk提取含高危字符的请求及对应 UA:awk '$0 ~ /%27|\'\''|sleep\(|UNION[[:space:]]+SELECT|AND[[:space:]]+[0-9]+=/{print $1, $12, $7}' /var/log/apache2/access.log | head -20其中$12是$http_user_agent(需确认 log_format 中字段顺序),$7是$request - Nginx 若未开启
log_format自定义,$http_user_agent可能为空——必须先检查实际日志格式:grep -o 'User-Agent:[^"]*' /var/log/nginx/access.log | head -3 - 注意:
sqlmap默认不带--random-agent时,UA 是稳定指纹;但加了该参数后,UA 就不可靠,此时必须回看$request_uri或$args中的编码载荷
识别伪装成合法 UA 的 HTTP 头注入行为
攻击者早就不只打 URL 参数了。User-Agent、Referer、Accept、Cookie 这些头字段如果被拼进 SQL 语句且未过滤,就是高危注入点。日志中看不到报错,但能看到异常 UA 值本身含 SQL 片段——比如 User-Agent: Mozilla/5.0' OR updatexml(1,concat(0x7e,user(),0x7e),1) OR '。
这类攻击不会触发 500 错误(因为是 INSERT 日志记录逻辑出问题),但会在 access_log 的 UA 字段里留下明显痕迹。重点筛查不是“UA 是什么”,而是“UA 里有没有不该出现的 SQL 结构”。
- 匹配 UA 中的注入特征(避免漏掉 URL 编码):
awk -F'"' '{for(i=2;i<=NF;i+=2) if($i ~ /%27|\'\''|sleep\(|database\(\)|user\(\)|updatexml\(/) print $1,$i}' /var/log/apache2/access.log——用双引号分隔,只扫引号内内容,避开日志其他字段干扰 - 常见误判点:某些 CDN 或 WAF 会在 UA 后追加标识如
(via cloudflare),正则要加[[:space:]]*容忍空格 - 若发现 UA 中含
python-requests+sleep(,基本可判定是时间盲注脚本,响应时间差需结合error_log或自定义$request_time字段验证
绕过简单字符串匹配的编码载荷怎么抓
攻击者用 %2527(即 %27 的二次编码)绕过 grep "'" 很常见。但日志本身不自动解码,所以不能只查原始字符串,得用工具做预处理或改用更鲁棒的匹配方式。
核心思路:不依赖“解码后是否含单引号”,而是盯住编码模式本身——连续出现的 %[0-9A-Fa-f]{2} 组合超过 3 次,且夹杂 UNION、SELECT 等关键字,大概率是混淆载荷。
- 快速筛双重编码(如
%2527):grep -E "%25[0-9A-Fa-f]{2}" /var/log/nginx/access.log | grep -i -E "(union|select|sleep)" - 用 Python 轻量解码再查(适合批量分析):
python3 -c "import sys,urllib.parse; [print(urllib.parse.unquote(l.strip())) for l in sys.stdin if any(k in urllib.parse.unquote(l) for k in ['sleep(','union select','database()'])]" < /var/log/apache2/access.log - 注意:
unquote对无效编码(如%zz)会抛异常,生产环境建议加 try/except 包裹,否则整条日志丢失
为什么只看 User-Agent 和参数还不够
真正难搞的是那些“看起来完全正常”的请求:UA 是 Chrome,URL 参数像 /search?q=test,但实际 payload 藏在 POST body 里。Nginx 默认 access_log 不记录请求体,Apache 也一样——除非你启用了 mod_security 或 ngx_http_lua_module 显式记录 $request_body。
这意味着,如果没提前配置好日志字段,光扫 access_log 会漏掉至少 40% 的 POST 类注入(尤其是登录框、表单提交场景)。而攻击者正越来越多地用 POST 避开 GET 日志检测。
- 确认是否记录了请求体:
grep "request_body" /etc/nginx/nginx.conf或查 Apache 的mod_security规则中是否有SecAuditLogParts ABIFHZ(I表示 request body) - 临时补救:用
tcpdump抓包过滤 POST 流量,再用strings提取可读内容:tcpdump -i any -s 0 -A 'tcp port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354' -w post.pcap
- 最易被忽略的一点:即使记录了
$request_body,默认client_max_body_size是 1MB,超大 payload(如 base64 编码的长 SQL)会被截断,导致关键部分丢失

















