From f2ce95b8ef205084f0a0016dd1d365d425fde810 Mon Sep 17 00:00:00 2001 From: hippi345 <13539685+hippi345@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:15:19 +0000 Subject: [PATCH 1/2] Fix S3CrtClient to use origin-form request-target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HttpRequest::ToCrtHttpRequest() was constructing an absolute-form request-target (e.g., "PUT http://host/path HTTP/1.1") for every request. This violates RFC 9112 §3.2.1, which requires origin-form (path + query only) when a client connects directly to an origin server. aws-c-http already handles proxy rewriting via aws_http_rewrite_uri_for_proxy_request, converting to absolute-form only when necessary. By passing origin-form here, we prevent security tools like CrowdSec's http-open-proxy scenario from flagging legitimate S3-compatible storage requests as proxy abuse. Fixes #3956 --- ...fix-cursorfix-s3crt-absolute-url-cd0a.json | 6 ++ .../source/http/HttpRequest.cpp | 16 ++--- .../http/HttpClientTest.cpp | 72 +++++++++++++++++++ 3 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 .changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json diff --git a/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json b/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json new file mode 100644 index 000000000000..7864e199b43e --- /dev/null +++ b/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "aws-cpp-sdk-core", + "contributor": "hippi345", + "description": "Fix S3CrtClient to send origin-form request-target (path + query only) instead of absolute-form, preventing false-positive open-proxy detection" +} diff --git a/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp b/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp index 55ca99c64661..512d94bce2e9 100644 --- a/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp +++ b/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp @@ -70,15 +70,6 @@ namespace Aws // CRT sigv4 don't any encoding if double encoding is off, need to encode the path before passing to CRT. const URI& uri = m_uri; Aws::StringStream ss; - Aws::StringStream port; - if (uri.GetScheme() == Scheme::HTTP && uri.GetPort() != HTTP_DEFAULT_PORT) - { - port << ":" << uri.GetPort(); - } - else if (uri.GetScheme() == Scheme::HTTPS && uri.GetPort() != HTTPS_DEFAULT_PORT) - { - port << ":" << uri.GetPort(); - } // Note: GetURLEncodedPathRFC3986 does legacy mode by default, which // leaves some reserved delimeter characters unencoded (things like @@ -88,8 +79,11 @@ namespace Aws // practice however CRT will never support legacy mode and will // always need modern encoding, so be explicit here about which mode // we use. - ss << SchemeMapper::ToString(uri.GetScheme()) << SEPARATOR << uri.GetAuthority() << port.str() - << ((uri.GetPath() == "/") ? "" : uri.GetURLEncodedPath()) + + // Set path to origin-form (path + query only) per RFC 9112 §3.2.1. + // aws-c-http converts to absolute-form only when sending through a + // forward proxy via aws_http_rewrite_uri_for_proxy_request. + ss << (uri.GetPath().empty() ? "/" : uri.GetURLEncodedPath()) << uri.GetQueryString(); request->SetPath(Aws::Crt::ByteCursorFromCString(ss.str().c_str())); diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp index 822df5e5859a..28ffd0a552b6 100644 --- a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -385,4 +386,75 @@ TEST_F(CURLHttpClientTest, TestHttpRequestWorksFine) #endif // ENABLE_CURL_CLIENT #endif // ENABLE_HTTP_CLIENT_TESTING #endif // NO_HTTP_CLIENT + +// Test that ToCrtHttpRequest produces origin-form request-target (path + query only) +TEST_F(HttpClientTest, TestToCrtHttpRequestOriginForm) +{ + // Test with HTTP scheme and non-default port + { + Aws::Http::URI uri("http://example.com:9000/bucket/key?uploadId=abc&partNumber=1"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_PUT); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + // The path should be origin-form: /path?query + // NOT absolute-form: http://example.com:9000/path?query + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + EXPECT_EQ("/bucket/key?uploadId=abc&partNumber=1", path); + EXPECT_FALSE(path.find("http://") == 0); + EXPECT_FALSE(path.find("https://") == 0); + } + + // Test with HTTPS scheme and default port + { + Aws::Http::URI uri("https://s3.amazonaws.com/my-bucket/my-key"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_GET); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + EXPECT_EQ("/my-bucket/my-key", path); + EXPECT_FALSE(path.find("http://") == 0); + EXPECT_FALSE(path.find("https://") == 0); + } + + // Test with query string + { + Aws::Http::URI uri("https://storage.example.com:443/path/to/file?query=value&foo=bar"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_POST); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + EXPECT_EQ("/path/to/file?query=value&foo=bar", path); + EXPECT_FALSE(path.find("http://") == 0); + EXPECT_FALSE(path.find("https://") == 0); + } + + // Test with root path + { + Aws::Http::URI uri("http://localhost:8080/"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_HEAD); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + // Root path should be "/" + EXPECT_EQ("/", path); + EXPECT_FALSE(path.find("http://") == 0); + } +} + #endif // DISABLE_DNS_REQUIRED_TESTS From 5679182fcded602e4faaa0bdc549cafd64d7814e Mon Sep 17 00:00:00 2001 From: hippi345 <13539685+hippi345@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:37:50 +0000 Subject: [PATCH 2/2] Review fixes: remove changelog, move test outside DNS guard - Remove .changelog file (upstream has no such directory) - Move ToCrtHttpRequestOriginForm test outside DISABLE_DNS_REQUIRED_TESTS guard as it requires no network access --- .../bugfix-cursorfix-s3crt-absolute-url-cd0a.json | 6 ------ .../http/HttpClientTest.cpp | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) delete mode 100644 .changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json diff --git a/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json b/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json deleted file mode 100644 index 7864e199b43e..000000000000 --- a/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "bugfix", - "category": "aws-cpp-sdk-core", - "contributor": "hippi345", - "description": "Fix S3CrtClient to send origin-form request-target (path + query only) instead of absolute-form, preventing false-positive open-proxy detection" -} diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp index 28ffd0a552b6..e9b558a23fb8 100644 --- a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp @@ -387,8 +387,19 @@ TEST_F(CURLHttpClientTest, TestHttpRequestWorksFine) #endif // ENABLE_HTTP_CLIENT_TESTING #endif // NO_HTTP_CLIENT +#endif // DISABLE_DNS_REQUIRED_TESTS + +// Test ToCrtHttpRequest conversion (no network required) +#include +#include +#include + +class HttpRequestConversionTest : public Aws::Testing::AwsCppSdkGTestSuite +{ +}; + // Test that ToCrtHttpRequest produces origin-form request-target (path + query only) -TEST_F(HttpClientTest, TestToCrtHttpRequestOriginForm) +TEST_F(HttpRequestConversionTest, ToCrtHttpRequestOriginForm) { // Test with HTTP scheme and non-default port { @@ -456,5 +467,3 @@ TEST_F(HttpClientTest, TestToCrtHttpRequestOriginForm) EXPECT_FALSE(path.find("http://") == 0); } } - -#endif // DISABLE_DNS_REQUIRED_TESTS