📄 tiny_http 0.12.0 HTTP Header Injection
| Reporter | Title | Published | Views | Family All 12 |
|---|---|---|---|---|
| CVE-2026-66753 | 28 Jul 202615:46 | – | attackerkb | |
| CVE-2026-66753 | 28 Jul 202615:46 | – | cve | |
| CVE-2026-66753 tiny-http 0.12.0 HTTP Response Splitting via Header Injection | 28 Jul 202615:46 | – | cvelist | |
| CVE-2026-66753 | 28 Jul 202615:46 | – | debiancve | |
| EUVD-2026-49883 | 28 Jul 202615:46 | – | euvd | |
| CVE-2026-66753 | 28 Jul 202616:20 | – | nvd | |
| CVE-2026-66753 tiny-http 0.12.0 HTTP Response Splitting via Header Injection | 28 Jul 202615:46 | – | osv | |
| DEBIAN-CVE-2026-66753 | 28 Jul 202616:20 | – | osv | |
| UBUNTU-CVE-2026-66753 | 29 Jul 202600:00 | – | osv | |
| PT-2026-65523 | 28 Jul 202600:00 | – | ptsecurity |
10
# Security Advisory: HTTP Header Injection via Unvalidated CR and LF in Header Values (tiny_http)
**Assigned CVE ID:** CVE-2026-66753
## Summary
tiny_http does not reject carriage return or line feed inside HTTP header values,
in either direction.
On the request side, `read_next_line` ends a header line only on CRLF, so a lone
LF survives inside the parsed value and reaches the application. On the response
side, `Header::from_bytes` validates only that the bytes are ASCII, and the
response writer emits values verbatim, so a value containing CRLF splits the
response.
Applications that reflect a request header value into a response header, or that
re-serialise request headers onto another connection, therefore inherit an
injection primitive with no indication that anything is wrong.
## Affected versions
Repo URL: https://github.com/tiny-http/tiny-http
| | |
|---|---|
| Affected | all released versions up to and including 0.12.0 (2022-10-06), the current release |
| Verified | 0.6.2, 0.6.3, 0.8.0, 0.9.0, 0.10.0, 0.11.0, 0.12.0 |
| Fixed in | no fixed version at time of writing |
This is distinct from RUSTSEC-2020-0031 / CVE-2020-35884, which covered
`Transfer-Encoding` parsing and was fixed in 0.6.3 and 0.8.0. The behaviour
described here is present both before and after that fix.
## Severity
CWE-113 (Improper Neutralization of CRLF Sequences in HTTP Headers), a specific
case of CWE-93 (CRLF Injection).
CVSS 4.0 base score 6.3 (Medium)
`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`
## Threat model
A remote, unauthenticated client sending raw HTTP to any tiny_http server. No
credentials or user interaction.
Two consumer shapes turn this into a vulnerability:
Applications that copy a request header value into a response header. This is
the reflection pattern behind CORS origin echo, `Location` headers built from
request data, and cookie handling. A lone LF in the request value reaches the
response, and if the application constructs the response value itself it can
carry a full CRLF.
Applications that re-serialise request headers onto another connection, such as
a reverse proxy. The lone LF is written into the upstream request, and backends
that treat a bare LF as a header line terminator read one request as two.
RFC 9112 section 2.2 permits recipients to do this, so those backends are within
spec. Go `net/http` and Python `http.server` were both measured to accept it.
## Root cause
### Request side
`tiny_http-0.12.0/src/client.rs`, lines 80 to 102. The loop returns only when a
LF is preceded by a CR. Any other byte, including a lone LF or a lone CR, is
pushed into the line buffer at line 100:
```rust
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 is 0x0A and CR is 0x0D, both valid ASCII, so `AsciiString::from_ascii` on
line 94 accepts them.
`tiny_http-0.12.0/src/common.rs`, lines 184 to 191, then only trims the value.
`trim` removes leading and trailing whitespace but leaves interior bytes intact:
```rust
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(())?;
```
A CRLF pair cannot survive, since it terminates the line, but a lone LF, a lone
CR, and sequences such as `\n\r` all can.
### Response side
`tiny_http-0.12.0/src/common.rs`, lines 166 to 172, checks only for ASCII:
```rust
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`, lines 99 to 104, writes the value with no
escaping:
```rust
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 }
```
Note that `HeaderField::from_str` at line 226 does reject whitespace in a field
name, but `HeaderField::from_bytes` on line 171 does not, so response header
names are unchecked too.
## Proof of Concept
Step 1. Start a server that prints a parsed request header value and echoes a
value containing CRLF into a response header.
```rust
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);
}
}
```
Step 2. Send a request whose `X-Test` value contains a bare LF. In the command
below `\n` is a single line feed and `\r\n` is a CRLF. The distinction is the
point of the test, so do not let an editor normalise it.
```sh
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
```
Result on stdout. The LF is still inside the parsed value:
```
parsed X-Test value = "aaa\nbbb"
```
Result on the wire. The response header split into two:
```
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
```
## Impact
By itself tiny_http does not re-emit request headers, so the request-side
behaviour is a latent primitive rather than a direct compromise. It becomes
exploitable in any consumer that forwards or reflects header values, where it
yields request smuggling against LF-tolerant backends or header injection in the
response.
The response-side behaviour is directly exploitable in any application that
places attacker-influenced text into a header value: script injection in the
origin's context, cache poisoning, session fixation through an injected
`Set-Cookie`, and overriding security headers.
For comparison, the `http` crate rejects CR and LF in `HeaderValue::from_str`,
which is why stacks built on it do not expose either behaviour.
## Remediation
Reject control characters at both boundaries.
In `read_next_line` (`src/client.rs` line 80), treat a bare CR or a bare LF in a
header line as a protocol error and return 400, rather than folding it into the
value. Alternatively reject them in `Header::from_str` (`src/common.rs` line
184) after the split.
In `Header::from_bytes` (`src/common.rs` line 166), reject values containing
0x0D, 0x0A or 0x00, and reject field names containing anything outside the
RFC 9110 token set. Returning `Err` there is already handled by callers, since
the signature is fallible.Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
29 Jul 2026 00:00Current
5.9Medium risk
Vulners AI Score5.9
CVSS 3.13.7
CVSS 46.3
EPSS0.00218
SSVC