Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-66753-HTTP-Header-Injection-via-Unvalidated-CR-and-LF-in-Header-Values-tiny_http- — Security Advisory: HTTP Header Injection via Unvalidated CR and LF in Header Values (tiny_http) | Kitploit
Tools/GitHubGitHub/theopaid/cve-2026-66753-http-header-injection-via-unvalidated-cr-and-lf-in-header-values-tiny_http-
Vulnerability AnalysisCode AnalysisWeb SecurityLearning & EducationCurated Resources
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-

Security Advisory: HTTP Header Injection via Unvalidated CR and LF in Header Values (tiny_http)

View Repository
41 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

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

Affectedall released versions up to and including 0.12.0 (2022-10-06), the current release
Verified0.6.2, 0.6.3, 0.8.0, 0.9.0, 0.10.0, 0.11.0, 0.12.0
Fixed inno 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:

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 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:

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(())?;

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:

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, lines 99 to 104, writes the value with no escaping:

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      }

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.

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);
    }
}

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.

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

Result on stdout. The LF is still inside the parsed value:

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

Result on the wire. The response header split into two:

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

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.

Download Tool