1
0
mirror of synced 2025-07-17 17:40:57 +03:00

Merge commit from fork

* Fix Persistency of Unbounded Memory Allocation in Chunked/No-Length Requests Vulnerability

* Revert HTTP status code from 413 to 400
This commit is contained in:
yhirose
2025-07-08 17:11:13 -04:00
committed by GitHub
parent 52163ed982
commit 082acacd45
2 changed files with 326 additions and 23 deletions

View File

@ -7763,6 +7763,261 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) {
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST_F(PayloadMaxLengthTest, ChunkedEncodingSecurityTest) {
// Test chunked encoding with payload exceeding the 8-byte limit
std::string large_chunked_data(16, 'A'); // 16 bytes, exceeds 8-byte limit
auto res = cli_.Post("/test", large_chunked_data, "text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status);
}
TEST_F(PayloadMaxLengthTest, ChunkedEncodingWithinLimit) {
// Test chunked encoding with payload within the 8-byte limit
std::string small_chunked_data(4, 'B'); // 4 bytes, within 8-byte limit
auto res = cli_.Post("/test", small_chunked_data, "text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST_F(PayloadMaxLengthTest, RawSocketChunkedTest) {
// Test using send_request to send chunked data exceeding payload limit
std::string chunked_request = "POST /test HTTP/1.1\r\n"
"Host: " +
std::string(HOST) + ":" + std::to_string(PORT) +
"\r\n"
"Transfer-Encoding: chunked\r\n"
"Connection: close\r\n"
"\r\n"
"a\r\n" // 10 bytes chunk (exceeds 8-byte limit)
"0123456789\r\n"
"0\r\n" // End chunk
"\r\n";
std::string response;
bool result = send_request(1, chunked_request, &response);
if (!result) {
// If send_request fails, it might be because the server closed the
// connection due to payload limit enforcement, which is acceptable
SUCCEED()
<< "Server rejected oversized chunked request (connection closed)";
} else {
// If we got a response, check if it's an error response or connection was
// closed early Short response length indicates connection was closed due to
// payload limit
if (response.length() <= 10) {
SUCCEED() << "Server closed connection for oversized chunked request";
} else {
// Check for error status codes
EXPECT_TRUE(response.find("413") != std::string::npos ||
response.find("Payload Too Large") != std::string::npos ||
response.find("400") != std::string::npos);
}
}
}
TEST_F(PayloadMaxLengthTest, NoContentLengthPayloadLimit) {
// Test request without Content-Length header exceeding payload limit
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
"Host: " +
std::string(HOST) + ":" +
std::to_string(PORT) +
"\r\n"
"Connection: close\r\n"
"\r\n";
// Add payload exceeding the 8-byte limit
std::string large_payload(16, 'X'); // 16 bytes, exceeds 8-byte limit
request_without_content_length += large_payload;
std::string response;
bool result = send_request(1, request_without_content_length, &response);
if (!result) {
// If send_request fails, server likely closed connection due to payload
// limit
SUCCEED() << "Server rejected oversized request without Content-Length "
"(connection closed)";
} else {
// Check if server responded with error or closed connection early
if (response.length() <= 10) {
SUCCEED() << "Server closed connection for oversized request without "
"Content-Length";
} else {
// Check for error status codes
EXPECT_TRUE(response.find("413") != std::string::npos ||
response.find("Payload Too Large") != std::string::npos ||
response.find("400") != std::string::npos);
}
}
}
TEST_F(PayloadMaxLengthTest, NoContentLengthWithinLimit) {
// Test request without Content-Length header within payload limit
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
"Host: " +
std::string(HOST) + ":" +
std::to_string(PORT) +
"\r\n"
"Connection: close\r\n"
"\r\n";
// Add payload within the 8-byte limit
std::string small_payload(4, 'Y'); // 4 bytes, within 8-byte limit
request_without_content_length += small_payload;
std::string response;
bool result = send_request(1, request_without_content_length, &response);
// For requests without Content-Length, the server may have different behavior
// The key is that it should not reject due to payload limit for small
// payloads
if (result) {
// Check for any HTTP response (success or error, but not connection closed)
if (response.length() > 10) {
SUCCEED()
<< "Server processed request without Content-Length within limit";
} else {
// Short response might indicate connection closed, which is acceptable
SUCCEED() << "Server closed connection for request without "
"Content-Length (acceptable behavior)";
}
} else {
// Connection failure might be due to protocol requirements
SUCCEED() << "Connection issue with request without Content-Length "
"(environment-specific)";
}
}
class LargePayloadMaxLengthTest : public ::testing::Test {
protected:
LargePayloadMaxLengthTest()
: cli_(HOST, PORT)
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
,
svr_(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE)
#endif
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
cli_.enable_server_certificate_verification(false);
#endif
}
virtual void SetUp() {
// Set 10MB payload limit
const size_t LARGE_PAYLOAD_LIMIT = 10 * 1024 * 1024; // 10MB
svr_.set_payload_max_length(LARGE_PAYLOAD_LIMIT);
svr_.Post("/test", [&](const Request & /*req*/, Response &res) {
res.set_content("Large payload test", "text/plain");
});
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
svr_.wait_until_ready();
}
virtual void TearDown() {
svr_.stop();
t_.join();
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli_;
SSLServer svr_;
#else
Client cli_;
Server svr_;
#endif
thread t_;
};
TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingWithin10MB) {
// Test chunked encoding with payload within 10MB limit
std::string medium_payload(5 * 1024 * 1024,
'A'); // 5MB payload, within 10MB limit
auto res = cli_.Post("/test", medium_payload, "application/octet-stream");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingExceeds10MB) {
// Test chunked encoding with payload exceeding 10MB limit
std::string large_payload(12 * 1024 * 1024,
'B'); // 12MB payload, exceeds 10MB limit
auto res = cli_.Post("/test", large_payload, "application/octet-stream");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status);
}
TEST_F(LargePayloadMaxLengthTest, NoContentLengthWithin10MB) {
// Test request without Content-Length header within 10MB limit
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
"Host: " +
std::string(HOST) + ":" +
std::to_string(PORT) +
"\r\n"
"Connection: close\r\n"
"\r\n";
// Add 1MB payload (within 10MB limit)
std::string medium_payload(1024 * 1024, 'C'); // 1MB payload
request_without_content_length += medium_payload;
std::string response;
bool result = send_request(5, request_without_content_length, &response);
if (result) {
// Should get a proper HTTP response for payloads within limit
if (response.length() > 10) {
SUCCEED() << "Server processed 1MB request without Content-Length within "
"10MB limit";
} else {
SUCCEED() << "Server closed connection (acceptable behavior for no "
"Content-Length)";
}
} else {
SUCCEED() << "Connection issue with 1MB payload (environment-specific)";
}
}
TEST_F(LargePayloadMaxLengthTest, NoContentLengthExceeds10MB) {
// Test request without Content-Length header exceeding 10MB limit
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
"Host: " +
std::string(HOST) + ":" +
std::to_string(PORT) +
"\r\n"
"Connection: close\r\n"
"\r\n";
// Add 12MB payload (exceeds 10MB limit)
std::string large_payload(12 * 1024 * 1024, 'D'); // 12MB payload
request_without_content_length += large_payload;
std::string response;
bool result = send_request(10, request_without_content_length, &response);
if (!result) {
// Server should close connection due to payload limit
SUCCEED() << "Server rejected 12MB request without Content-Length "
"(connection closed)";
} else {
// Check for error response
if (response.length() <= 10) {
SUCCEED()
<< "Server closed connection for 12MB request exceeding 10MB limit";
} else {
EXPECT_TRUE(response.find("413") != std::string::npos ||
response.find("Payload Too Large") != std::string::npos ||
response.find("400") != std::string::npos);
}
}
}
TEST(HostAndPortPropertiesTest, NoSSL) {
httplib::Client cli("www.google.com", 1234);
ASSERT_EQ("www.google.com", cli.host());