Просмотр исходного кода

Implement request smuggling protection for duplicate Content-Length headers and add corresponding tests

yhirose 2 недель назад
Родитель
Сommit
1e97c28e36
2 измененных файлов с 70 добавлено и 0 удалено
  1. 10 0
      httplib.h
  2. 60 0
      test/test.cc

+ 10 - 0
httplib.h

@@ -6813,6 +6813,16 @@ inline bool read_headers(Stream &strm, Headers &headers) {
     header_count++;
     header_count++;
   }
   }
 
 
+  // RFC 9110 Section 8.6: Reject requests with multiple Content-Length
+  // headers that have different values to prevent request smuggling.
+  auto cl_range = headers.equal_range("Content-Length");
+  if (cl_range.first != cl_range.second) {
+    const auto &first_val = cl_range.first->second;
+    for (auto it = std::next(cl_range.first); it != cl_range.second; ++it) {
+      if (it->second != first_val) { return false; }
+    }
+  }
+
   return true;
   return true;
 }
 }
 
 

+ 60 - 0
test/test.cc

@@ -13051,6 +13051,66 @@ TEST(ClientInThreadTest, Issue2068) {
   }
   }
 }
 }
 
 
+TEST(RequestSmugglingTest, DuplicateContentLengthDifferentValues) {
+  auto handled = false;
+
+  Server svr;
+  svr.Post("/test", [&](const Request &, Response &) { handled = true; });
+
+  thread t = thread([&]() { svr.listen(HOST, PORT); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    t.join();
+    ASSERT_FALSE(svr.is_running());
+    ASSERT_FALSE(handled);
+  });
+
+  svr.wait_until_ready();
+
+  // Two Content-Length headers with different values — must be rejected
+  auto req = "POST /test HTTP/1.1\r\n"
+             "Content-Length: 5\r\n"
+             "Content-Length: 10\r\n"
+             "\r\n"
+             "hello";
+
+  std::string response;
+  ASSERT_TRUE(send_request(1, req, &response));
+  ASSERT_EQ("HTTP/1.1 400 Bad Request",
+            response.substr(0, response.find("\r\n")));
+}
+
+TEST(RequestSmugglingTest, DuplicateContentLengthSameValues) {
+  auto handled = false;
+
+  Server svr;
+  svr.Post("/test", [&](const Request &, Response &res) {
+    handled = true;
+    res.set_content("ok", "text/plain");
+  });
+
+  thread t = thread([&]() { svr.listen(HOST, PORT); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    t.join();
+    ASSERT_FALSE(svr.is_running());
+    ASSERT_TRUE(handled);
+  });
+
+  svr.wait_until_ready();
+
+  // Two Content-Length headers with same value — should be accepted (RFC 9110)
+  auto req = "POST /test HTTP/1.1\r\n"
+             "Content-Length: 5\r\n"
+             "Content-Length: 5\r\n"
+             "\r\n"
+             "hello";
+
+  std::string response;
+  ASSERT_TRUE(send_request(1, req, &response));
+  ASSERT_EQ("HTTP/1.1 200 OK", response.substr(0, response.find("\r\n")));
+}
+
 TEST(HeaderSmugglingTest, ChunkedTrailerHeadersMerged) {
 TEST(HeaderSmugglingTest, ChunkedTrailerHeadersMerged) {
   Server svr;
   Server svr;