🛡️ 1、简述
在日常开发中,Web 安全不仅仅是 SQL 注入、XSS 攻击那样“显眼”的问题,CRLF 注入(Carriage Return Line Feed Injection) 也是一种常被忽视但危险的安全漏洞。尤其在 HTTP 响应头处理中,若不加以防范,极有可能造成严重后果。
本文将深入分析 CRLF 注入的原理、在 Java 中的攻击方式,并结合实际代码示例给出预防方案。
2、什么是 CRLF 注入?
CRLF(Carriage Return Line Feed) 是 HTTP 协议中用于标识“换行”的两个字符,分别是:
🔹 \r
(Carriage Return, 回车)
🔹 \n
(Line Feed, 换行)
在 HTTP 协议中,响应头与响应体之间通过一行空行 \r\n
分隔:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: userId=admin
<html>...</html>
📌 CRLF 注入的本质
如果攻击者在参数中注入了 \r\n
字符,并且这些内容未经过滤就拼接进 HTTP 响应头,就可能造成如下后果:
🔹 插入恶意的响应头;
🔹 伪造多个响应;
🔹 诱导浏览器缓存敏感内容;
🔹 配合 XSS 实现 Cookie 劫持。
3、攻击示例(Java 场景)
下面是一个存在 CRLF 注入风险的代码片段:
@GetMapping("/download")
public void downloadFile(@RequestParam String filename, HttpServletResponse response) {
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
}
🧨 输入模拟攻击:
访问地址:
http://localhost:8080/download?filename=test.txt%0D%0ASet-Cookie:%20token=malicious
结果响应头被注入:
Content-Disposition: attachment; filename=test.txt
Set-Cookie: token=malicious
攻击者成功伪造了一个 Cookie,造成安全隐患。
Java 中常见的易受害场景
场景 | 说明 |
---|---|
response.setHeader() |
响应头拼接用户输入 |
重定向(如 sendRedirect() ) |
URL 参数拼接不当 |
设置 Cookie(如 addCookie() ) |
Cookie 名称、值未校验 |
日志记录/文件输出 | 文件名、内容中包含非法换行符 |
4、实际解决方案
✅ 方案一:使用工具类过滤 CRLF 字符
public static String sanitizeHeaderValue(String value) {
if (value == null) return null;
return value.replaceAll("[\\r\\n]", "");
}
使用示例:
String safeFilename = sanitizeHeaderValue(filename);
response.setHeader("Content-Disposition", "attachment; filename=" + safeFilename);
✅ 方案二:Apache Commons Text 工具推荐
Apache 提供了 StringEscapeUtils
工具进行安全转义:
import org.apache.commons.text.StringEscapeUtils;
String encoded = StringEscapeUtils.escapeJava(filename);
注意:该方法并不会直接去除 CRLF,而是对其编码,可以避免注入问题。
✅ 方案三:限制输入类型和格式(白名单)
if (!filename.matches("^[a-zA-Z0-9_.-]+$")) {
throw new IllegalArgumentException("文件名不合法");
}
✅ 方案四:使用标准 API 设置文件名(Spring 推荐)
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFilename + "\"");
虽然 URLEncoder.encode()
本意是转义 URL,但也能规避 CRLF。
✅ 方案五:统一参数过滤(可选)
你可以通过一个全局 Filter,对所有参数做一次性清洗:
@WebFilter("/*")
public class CRLFSanitizerFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) {
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return sanitize(value);
}
private String sanitize(String input) {
return input == null ? null : input.replaceAll("[\\r\\n]", "");
}
};
chain.doFilter(wrappedRequest, res);
}
}
这样你在任何 Controller 使用 request.getParameter("xxx") 时都已经是安全的。
✅ 方案六:通过 Nginx 防止 CRLF 注入(源头拦截)
🔹 拦截请求 URL、Header、参数中含有非法 \r
(%0D)或 \n
(%0A)的内容。
🔹 防止恶意注入响应头、绕过跨域、防止缓存污染等攻击。
🔥 使用 ngx_http_rewrite_module
进行 CRLF 拦截
核心思路:如果请求的 URI 或参数中出现 %0d
或 %0a
,直接返回 400 Bad Request
。
在 Nginx 配置里加上:
server {
listen 80;
server_name yourdomain.com;
location / {
# 拦截URL中的%0d或%0a
if ($request_uri ~* "%0d|%0a") {
return 400;
}
# 拦截Header中有CRLF(比较少见,防护第二层)
if ($http_cookie ~* "%0d|%0a") {
return 400;
}
# 拦截Referer、Origin等常被利用的头
if ($http_referer ~* "%0d|%0a") {
return 400;
}
if ($http_origin ~* "%0d|%0a") {
return 400;
}
proxy_pass http://backend_server;
}
}
说明:
🔹 $request_uri
:完整的请求 URI(包含 path 和 query string)。
🔹 $http_cookie
、$http_referer
、$http_origin
:都是可以直接访问的请求头字段。
🔹 ~*
是大小写不敏感正则匹配。
🔹 检测 %0d
或 %0a
出现,就直接拒绝请求。
🔥 升级版:使用 map
简洁拦截
如果想写得更优雅,可以这样:
map $request_uri $is_crlf_attack {
default 0;
~*"%0d|%0a" 1;
}
server {
listen 80;
server_name yourdomain.com;
location / {
if ($is_crlf_attack) {
return 400;
}
proxy_pass http://backend_server;
}
}
更好维护,后续加字段也方便,比如加 $http_referer
、$http_origin
。
🔥 防止响应头注入(防止后端带回异常 header)
如果后端返回头部中出现非法字符(很少见但更高阶),可以:
proxy_pass_request_headers off;
或者手动指定允许哪些 header:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 不转发其他 header
不过一般只在后端不可信时(比如第三方 API)才需要这么严格。
防护点 | 配置建议 |
---|---|
请求URI含CRLF | `$request_uri ~* "%0d |
Header参数含CRLF | $http_referer , $http_origin 检查 |
后端返回头混乱 | 关闭 proxy_pass_request_headers 或只允许白名单头 |
返回状态 | 直接 return 400 |
📌 最佳实践建议
🔹 Nginx 前端拦截非法请求。
🔹 后端 Java Controller 再做一次输入清洗(安全双保险)。
🔹 定期通过扫描器(如 Burp、Nuclei)测试是否存在 CRLF 注入点。
✍️ 5、总结
虽然 CRLF 注入的出现频率不如 SQL 注入那样高,但它的破坏力不容小觑。开发者必须养成 “输入即验证” 的安全意识,特别是在处理 HTTP 响应头、重定向等接口时,务必小心。
内容 | 建议 |
---|---|
风险 | 可注入响应头、篡改 Cookie、诱发缓存问题等 |
原因 | 未过滤或未转义 CRLF 控制字符 |
最佳实践 | 参数校验 + CRLF 清理 + 编码输出 |
如你有其他关于 Web 安全或 Java 安全框架相关问题,欢迎留言讨论 😄
是否需要我补充 Spring Boot 项目中集成全局防御、统一拦截策略、或者添加单元测试样例?可以继续帮你完善。