一、CRLF注入概述
1.1 什么是CRLF
CRLF漏洞(Carriage Return Line Feed Injection,回车换行注入)是一种网络安全漏洞,主要出现在Web应用程序中。攻击者利用这一漏洞可以通过在HTTP请求中注入特定的回车符(r)和换行符(n)来操控HTTP响应头。这种攻击的危害包括会话劫持、缓存投毒、跨站点脚本(XSS)等。
1.2 CRLF漏洞危害
CRLF漏洞可能导致多种安全问题,包括:
-
会话劫持:通过注入新的Set-Cookie头,攻击者可以窃取用户的会话信息。 -
缓存投毒:攻击者可以操纵缓存机制,使得恶意内容被缓存并返回给其他用户。 -
跨站点脚本(XSS):如果攻击者能够注入JavaScript代码,通过操控HTTP头,可以在用户的浏览器中执行恶意脚本。
二、CRLF攻击示例与防护措施
CRLF字符在HTTP协议中用于分隔头部字段和头部字段之间的行。在HTTP请求或响应中,每个头部字段都以CRLF结束。因此,如果攻击者能够在头部字段的值中插入CRLF字符,他们就可以创建新的头部字段或破坏现有的头部格式。
2.1 攻击示例
假设一个Web应用程序允许用户输入文件名并将其作为HTTP响应头的一部分返回。如果应用程序没有对用户输入进行适当的验证和清理,攻击者可能会输入如下内容:
filename=test.txt%0D%0ASet-Cookie: test=malicious
在这个例子中,%0D
代表回车字符(r
),%0A
代表换行字符(n
)。如果应用程序直接将这个输入放入HTTP响应头中,响应可能看起来像这样:
Content-Disposition: attachment; filename="test.txt"
Set-Cookie: test=malicious
这将导致浏览器接受一个新的Set-Cookie头,从而可能使攻击者能够劫持用户的会话或执行其他恶意行为。
2.2 防护措施
为了防止CRLF漏洞,开发人员可以采用以下措施:
-
输入验证:对用户输入进行严格的验证,确保文件名或其他头部字段不包含CRLF字符。 -
输入清理:在使用用户输入之前,清理所有无效字符,例如回车和换行。 -
使用安全的库:使用经过审查的库和框架,这些库和框架会自动处理HTTP请求和响应的安全性问题。 -
编码输出:在输出时对所有动态生成的内容进行适当的编码,以防止恶意代码被执行。
三、为什么CRLF越来越小众?
在早期的 web 开发中,许多应用程序没有对用户输入进行严格的验证,导致了 CRLF 注入和其他类型的注入攻击(如 SQL 注入、XSS 等)的普遍存在。
以CRLF注入为例,其常见于如下场景:
-
HTTP响应头处理:当Web应用程序直接将用户输入用于设置HTTP响应头时,尤其是未经过滤的输入,可能导致CRLF注入。例如,设置 Content-Disposition
、Set-Cookie
等响应头时:
func downloadFile(w http.ResponseWriter, r *http.Request) {
filename := r.FormValue("filename")
w.Header().Set("Content-Disposition", "attachment; filename=""+filename+""")
// ...
}
-
重定向与转发:在某些情况下,Web应用程序可能会根据用户输入进行重定向。如果输入未经处理,攻击者可以利用CRLF注入在重定向响应中插入恶意内容。
3.1 响应头处理场景
这里是个文件下载接口,用户传入的文件名没有校验,直接输出,一些常见的白盒工具就会检测到该场景会有CRLF注入漏洞。
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
@RequestMapping("/api")
public class FileDownloadController {
@PostMapping("/download")
public void downloadFile(@RequestParam String filename, HttpServletResponse response) throws IOException {
// 验证并清理文件名,防止 CRLF 注入
// String sanitizedFilename = sanitizeFilename(filename);
System.out.println("Downloading file: " + filename);
// 设置响应头
String contentDisposition = String.format("attachment; filename="%s"", filename);
response.addHeader("Content-Disposition", contentDisposition);
// response.setContentType("application/octet-stream");
// 这里可以模拟文件内容的输出,或从实际文件中读取
response.getWriter().write("This is a dummy file content."); // 模拟文件内容
}
// 简单的文件名清理方法
private String sanitizeFilename(String filename) {
// 替换 CRLF 字符
return filename.replaceAll("[\r\n]", "");
}
}
curl -X POST http://127.0.0.1:8000/api/download -d "filename=test.txt%0D%0ASet-Cookie: test=malicious" -i
但是直接请求时,发现CRLF注入并没有生效,Set-Cookie并没有在新的一行,而是一起被当作字符串放在fielname里了。
经过排查,原来在Tomcat 6.0.36 和7.0.29版本之后,就已经做了修复。Tomcat 输出堆栈将响应标头值写入输出流时,它会将 ASCII 控制字符(TAB 除外)和 DEL 转换为空格。
而类似的,Go语言内置的net/http包,也对特殊字符进行了处理。
3.2 重定向与转发场景
在这个重定向示例中,若用户传入的 URL 包含 CRLF 字符,Tomcat 和大多数现代服务器会将这些字符过滤掉,因此即使攻击者尝试注入额外的 HTTP 头部,实际响应中也不会包含这些恶意内容。
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RestController
public class RedirectController {
@GetMapping("/redirect")
public void redirect(@RequestParam String url, HttpServletResponse response) {
// 直接将 URL 设置到 Location 头中,不进行任何校验
response.setHeader(HttpHeaders.LOCATION, url);
response.setStatus(HttpStatus.FOUND.value()); // 302 Found
}
}
四、结论
虽然 CRLF 注入仍然是一个值得关注的安全问题,但由于现代框架和服务器在这方面的改进,实际攻击的机会和成功率大大降低。因此,这种攻击方式在实际应用中变得相对小众。不过,作为开发者,了解这些安全问题及其历史仍然是非常重要的,因为安全性是任何应用程序设计中的核心部分。
开发人员应该始终遵循最佳实践,确保对用户输入进行严格的验证和清理,并定期审查应用程序的安全性。随着网络安全威胁的演变,保持对新漏洞和攻击方式的敏感性是至关重要的。
参考链接:
-
https://stackoverflow.com/questions/63668598/how-can-i-change-this-code-to-be-vulnerable-of-crlf-injection
-
https://owasp.org/www-community/attacks/CRLF_Injection
-
https://tomcat.apache.org/security.html
原文始发于微信公众号(SDL安全):CRLF 漏洞的演变:为何它在今天显得小众
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论