Procházet zdrojové kódy

Prevent forwarding of authentication credentials during cross-host redirects as per RFC 9110. Add tests for basic auth and bearer token scenarios.

yhirose před 1 týdnem
rodič
revize
703abbb53b
2 změnil soubory, kde provedl 64 přidání a 12 odebrání
  1. 5 12
      httplib.h
  2. 59 0
      test/test.cc

+ 5 - 12
httplib.h

@@ -13048,18 +13048,11 @@ inline void ClientImpl::setup_redirect_client(ClientType &client) {
   client.set_compress(compress_);
   client.set_decompress(decompress_);
 
-  // Copy authentication settings BEFORE proxy setup
-  if (!basic_auth_username_.empty()) {
-    client.set_basic_auth(basic_auth_username_, basic_auth_password_);
-  }
-  if (!bearer_token_auth_token_.empty()) {
-    client.set_bearer_token_auth(bearer_token_auth_token_);
-  }
-#ifdef CPPHTTPLIB_SSL_ENABLED
-  if (!digest_auth_username_.empty()) {
-    client.set_digest_auth(digest_auth_username_, digest_auth_password_);
-  }
-#endif
+  // NOTE: Authentication credentials (basic auth, bearer token, digest auth)
+  // are intentionally NOT copied to the redirect client. Per RFC 9110 Section
+  // 15.4, credentials must not be forwarded when redirecting to a different
+  // host. This function is only called for cross-host redirects; same-host
+  // redirects are handled directly in ClientImpl::redirect().
 
   // Setup proxy configuration (CRITICAL ORDER - proxy must be set
   // before proxy auth)

+ 59 - 0
test/test.cc

@@ -2382,6 +2382,65 @@ TEST(RedirectToDifferentPort, Redirect) {
   EXPECT_EQ("Hello World!", res->body);
 }
 
+static void
+TestDoNotForwardCredentialsOnRedirect(std::function<void(Client &)> set_auth) {
+  Server svr1;
+  std::string captured_authorization;
+  svr1.Get("/target", [&](const Request &req, Response &res) {
+    captured_authorization = req.get_header_value("Authorization");
+    res.set_content("OK", "text/plain");
+  });
+
+  int svr1_port = 0;
+  auto thread1 = std::thread([&]() {
+    svr1_port = svr1.bind_to_any_port(HOST);
+    svr1.listen_after_bind();
+  });
+
+  Server svr2;
+  svr2.Get("/redir", [&](const Request & /*req*/, Response &res) {
+    res.set_redirect(
+        "http://localhost:" + std::to_string(svr1_port) + "/target", 302);
+  });
+
+  int svr2_port = 0;
+  auto thread2 = std::thread([&]() {
+    svr2_port = svr2.bind_to_any_port(HOST);
+    svr2.listen_after_bind();
+  });
+  auto se = detail::scope_exit([&] {
+    svr2.stop();
+    thread2.join();
+    svr1.stop();
+    thread1.join();
+    ASSERT_FALSE(svr2.is_running());
+    ASSERT_FALSE(svr1.is_running());
+  });
+
+  svr1.wait_until_ready();
+  svr2.wait_until_ready();
+
+  Client cli("localhost", svr2_port);
+  cli.set_follow_location(true);
+  set_auth(cli);
+
+  auto res = cli.Get("/redir");
+  ASSERT_TRUE(res);
+  EXPECT_EQ(StatusCode::OK_200, res->status);
+  // RFC 9110: credentials MUST NOT be forwarded to a different host
+  EXPECT_TRUE(captured_authorization.empty());
+}
+
+TEST(RedirectToDifferentPort, DoNotForwardCredentialsBasicAuth) {
+  TestDoNotForwardCredentialsOnRedirect(
+      [](Client &cli) { cli.set_basic_auth("admin", "secret"); });
+}
+
+TEST(RedirectToDifferentPort, DoNotForwardCredentialsBearerToken) {
+  TestDoNotForwardCredentialsOnRedirect(
+      [](Client &cli) { cli.set_bearer_token_auth("my-secret-token"); });
+}
+
 TEST(RedirectToDifferentPort, OverflowPortNumber) {
   Server svr;
   svr.Get("/redir", [&](const Request & /*req*/, Response &res) {