Skip to content

CRLF injection in WebServer.cpp

High
lucasssvaz published GHSA-5476-9jjq-563m Jun 26, 2025

Package

arduino-esp32

Affected versions

<= 3.3.0-alpha1, <= 3.2.0

Patched versions

>= 3.3.0-RC1, >= 3.2.1

Description

Security Vulnerability Report

Repository: espressif/arduino-esp32
File: libraries/WebServer/src/WebServer.cpp
Function: void WebServer::sendHeader(const String& name, const String& value, bool first)
Lines: 504–521, 577-582

Code Snippet

Headers are added to the server response here:

void WebServer::sendHeader(const String &name, const String &value, bool first) {
RequestArgument *header = new RequestArgument();
header->key = name;
header->value = value;
if (!_responseHeaders || first) {
header->next = _responseHeaders;
_responseHeaders = header;
} else {
RequestArgument *last = _responseHeaders;
while (last->next) {
last = last->next;
}
last->next = header;
}
_responseHeaderCount++;
}

 void WebServer::sendHeader(const String &name, const String &value, bool first) { 
   RequestArgument *header = new RequestArgument(); 
   header->key = name; 
   header->value = value; 
  
   if (!_responseHeaders || first) { 
     header->next = _responseHeaders; 
     _responseHeaders = header; 
   } else { 
     RequestArgument *last = _responseHeaders; 
     while (last->next) { 
       last = last->next; 
     } 
     last->next = header; 
   } 
  
   _responseHeaderCount++; 
 }

Those headers are concatenated into the HTTP response body here:

for (RequestArgument *header = _responseHeaders; header; header = header->next) {
response.concat(header->key);
response.concat(F(": "));
response.concat(header->value);
response.concat(F("\r\n"));
}

 for (RequestArgument *header = _responseHeaders; header; header = header->next) { 
   response.concat(header->key); 
   response.concat(F(": ")); 
   response.concat(header->value); 
   response.concat(F("\r\n")); 
 } 

Vulnerability Overview

Type: HTTP Response Splitting (CWE-113)

Description:

The sendHeader function takes arbitrary input for the HTTP header name and value, concatenates them into an HTTP header line, and appends this to the outgoing HTTP response headers. There is no validation or sanitization of the name or value parameters before they are included in the HTTP response.

If an attacker can control the input to sendHeader (either directly or indirectly), they could inject carriage return (\r) or line feed (\n) characters into either the header name or value. This could allow the attacker to:

  • Inject additional headers
  • Manipulate the structure of the HTTP response
  • Potentially inject an entire new HTTP response (HTTP Response Splitting)
  • Cause header confusion or other HTTP protocol attacks

Impact:

  • HTTP Response Splitting: If user-supplied input is passed to sendHeader without sanitization, an attacker could perform HTTP response splitting, leading to:

    • Cross-site scripting (XSS)
    • Cache poisoning
    • Session fixation
    • Bypassing CORS or security headers
  • General Protocol Violation: Invalid HTTP headers or malformed responses, potentially breaking clients or enabling additional attacks.


Example Attack Scenario

If an attacker can cause a value such as:

attacker_value = "injected\r\nSet-Cookie: session=evil"

to be passed as the value argument, the resulting HTTP response would contain:

Header-Name: injected
Set-Cookie: session=evil

This would let the attacker set arbitrary headers or even break the response into separate responses.


Recommendation

Input Validation:

  • Reject or sanitize any \r (carriage return) or \n (line feed) characters in both the header name and value.
  • Only allow printable ASCII characters for header names.
  • Consider providing a whitelisted set of allowed headers, or at least validating against a strict header name pattern.

Example Fix:

void WebServer::sendHeader(const String& name, const String& value, bool first) {
  if (name.indexOf('\r') != -1 || name.indexOf('\n') != -1 ||
      value.indexOf('\r') != -1 || value.indexOf('\n') != -1) {
    log_e("Invalid character in HTTP header name or value");
    return;
  }
   RequestArgument *header = new RequestArgument(); 
   header->key = name; 
   header->value = value; 
  
   if (!_responseHeaders || first) { 
     header->next = _responseHeaders; 
     _responseHeaders = header; 
   } else { 
     RequestArgument *last = _responseHeaders; 
     while (last->next) { 
       last = last->next; 
     } 
     last->next = header; 
   } 
  
   _responseHeaderCount++; 
}

Fix

This vulnerability is addressed in this commit: 21640ac
It has been patched in versions: >= 3.3.0-RC1, >= 3.2.1

References


Severity

High — If user input is ever passed to this function without validation, this can lead to serious web security issues, including XSS and session hijacking.

Severity

High

CVE ID

CVE-2025-53007

Weaknesses

Credits