CRLF注入是一类容易被忽视但危害不小的Web安全漏洞,其中CRLF分别是回车符CR(ASCII码13,写作\r)和换行符LF(ASCII码10,写作\n)的缩写。当外部输入中包含这两个特殊字符,且服务端未做过滤就直接拼接到HTTP响应头或日志记录中时,攻击者就能借此插入额外的头信息,甚至在Apache日志中制造出一条以全新行开头的伪造记录。日志一旦被伪造,安全审计和攻击溯源都会受到严重干扰,因此理解其原理并做好预防十分必要。

CRLF注入的原理与日志伪造过程
HTTP协议本身就是一个基于行的文本协议,头部字段之间以\r\n分隔,头部与正文之间用一个空行(同样是\r\n)分隔。正是这种设计,使得\r\n在HTTP上下文中具有结构性含义。如果应用代码把用户输入直接拼进Location、Set-Cookie这类响应头的值里,攻击者只需在请求参数中注入%0d%0a(即\r\n的URL编码形式),就能在响应中截断原有头部,插入任意新头部。
日志伪造的逻辑与此类似。Apache的访问日志默认以一行一条请求的格式记录,例如常见的combined格式会记录请求行、状态码、Referer、User-Agent等信息。当攻击者构造的User-Agent或URL参数中含有未转义的\r\n时,这条日志记录在写入文件后会被拆成两行,后半部分看起来就像一条独立的日志。更有甚者,攻击者可以刻意把后半段伪造成来自127.0.0.1的正常请求,或者插入一段看似合法的审计记录,达到迷惑分析人员的目的。
一个典型的攻击请求如下,攻击者把伪造内容藏在User-Agent中:
curl -A "Mozilla/5.0\r\n127.0.0.1 - - [01/Jan/2025:10:00:00 +0800] \"GET /admin HTTP/1.1\" 200 512" http://www.ipipp.com/index.php
如果应用层没有过滤,这条请求写入日志后就会产生两行记录,第二行完全是攻击者编造的内容。配合日志分析系统按行解析,伪造记录就会混入正常数据,掩盖真实攻击路径。
Apache层面的排查与配置加固
排查的第一步是确认日志中是否存在异常断行。可以直接用文本编辑器打开access_log,观察是否存在莫名的行中断、缩进异常或来源IP与请求内容明显不符的记录。也可以借助grep配合十六进制查看工具,定位日志行中隐藏的控制字符。
在Apache配置层面,最直接的预防手段是使用mod_security模块对请求进行过滤,拒绝包含%0d、%0a等可疑编码的输入。如果暂时无法部署WAF,也可以通过mod_rewrite对明显的注入特征做拦截:
RewriteEngine On
# 拦截URL和查询字符串中携带CRLF编码的请求
RewriteCond %{THE_REQUEST} %0d [NC,OR]
RewriteCond %{THE_REQUEST} %0a [NC,OR]
RewriteCond %{QUERY_STRING} %0d [NC,OR]
RewriteCond %{QUERY_STRING} %0a [NC]
RewriteRule .* - [F,L]此外,建议自定义日志格式时尽量避免直接记录未经处理的原始请求头。Apache的%{User-Agent}i变量记录的是原始值,而通过mod_headers模块先行清洗或使用自定义脚本过滤后再落盘,可以大幅降低日志被污染的风险。同时,为日志目录设置严格的权限(例如仅root和特定服务账号可写),防止攻击者篡改日志文件本身。
应用层的防御与最佳实践
CRLF注入的根源在于把不可信输入拼接进具有结构含义的输出场景,因此应用层的过滤才是治本之策。在PHP中,写入响应头前应剔除控制字符:
$input = $_GET['redirect'] ?? '';
// 移除回车换行等控制字符,杜绝头注入
$safe = str_replace(["\r", "\n", "%0d", "%0a"], '', $input);
header('Location: ' . $safe);
// 记录日志时同样对输入做转义
error_log('redirect to: ' . $safe);在Java环境中,如果使用 HttpServletResponse 的 setHeader 方法,新版本的Servlet容器已经会对头值中的换行符抛出异常,但为了兼容性和日志安全,仍建议在业务层统一调用清洗函数。Python的Flask等框架在设置响应头时会自动校验非法字符,开发人员不应绕过框架直接操作WSGI层。
从架构角度看,防御应遵循纵深原则:入口处由WAF或mod_security做第一道拦截,应用层在拼接前做字符白名单过滤,日志采集环节再对二进制控制字符做转义处理。三者配合,即使某一层被绕过,伪造内容也难以进入最终的日志系统。同时定期审计日志格式、开启日志的完整性校验(如集中式日志同步与哈希比对),能让异常断行第一时间暴露出来,为应急响应争取时间。