CVE-2023-26130 (C++, published 2023-05-30T05:15Z)
Description: value: 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](https://security.snyk.io/vuln/SNYK-UNMANAGED-YHIROSECPPHTTPLIB-2366507).
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
Repository: https://github.com/yhirose/cpp-httplib
Files changed:
httplib.h
test/test.cc
Commit message: Fix more CRLF injection problems.


Vulnerable code:
    res.headers.emplace("Content-Type",
                        "multipart/byteranges; boundary=" + boundary);
      req.headers.emplace("Connection", "close");
        req.headers.emplace("Host", host_);
        req.headers.emplace("Host", host_and_port_);
        req.headers.emplace("Host", host_);
        req.headers.emplace("Host", host_and_port_);
  if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
    req.headers.emplace("User-Agent", agent);
          req.headers.emplace("Content-Length", length);
        req.headers.emplace("Content-Length", "0");
      req.headers.emplace("Content-Type", "text/plain");
      req.headers.emplace("Content-Length", length);
  if (!content_type.empty()) {
    req.headers.emplace("Content-Type", content_type);
  }
  if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); }
      req.headers.emplace("Transfer-Encoding", "chunked");
  if (!content_type.empty()) {
    req.headers.emplace("Content-Type", content_type);
  }

Fixed code:
    res.set_header("Content-Type",
                   "multipart/byteranges; boundary=" + boundary);
      req.set_header("Connection", "close");
        req.set_header("Host", host_);
        req.set_header("Host", host_and_port_);
        req.set_header("Host", host_);
        req.set_header("Host", host_and_port_);
  if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
    req.set_header("User-Agent", agent);
          req.set_header("Content-Length", length);
        req.set_header("Content-Length", "0");
      req.set_header("Content-Type", "text/plain");
      req.set_header("Content-Length", length);
  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
  if (compress_) { req.set_header("Content-Encoding", "gzip"); }
      req.set_header("Transfer-Encoding", "chunked");
  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }

TEST(VulnerabilityTest, CRLFInjection) {
  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*/) {
    for (const auto &x : req.headers) {
      auto key = x.first;
      EXPECT_STRNE("evil", key.c_str());
    }
  });

  auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
  auto se = detail::scope_exit([&] {
    svr.stop();
    thread.join();
    ASSERT_FALSE(svr.is_running());
  });

  std::this_thread::sleep_for(std::chrono::seconds(1));

  {
    Client cli(HOST, PORT);

    cli.Post("/test1", "A=B",
             "application/x-www-form-urlencoded\r\nevil: hello1");
    cli.Delete("/test2", "A=B", "text/plain\r\nevil: hello2");
    cli.Put("/test3", "text", "text/plain\r\nevil: hello3");
    cli.Patch("/test4", "content", "text/plain\r\nevil: hello4");
  }
}