diff --git a/httplib.h b/httplib.h index 055bed0f..f529734c 100644 --- a/httplib.h +++ b/httplib.h @@ -15942,8 +15942,11 @@ inline ClientImpl::StreamHandle ClientImpl::open_stream_(Request &req) { { detail::BufferStream bstrm; - if (detail::write_request_line(bstrm, req.method, - proxy_target_prefix() + req.path) < 0) { + // The path follows the authority in absolute-form, so one that does not + // begin with '/' (e.g. "@evil.example/x") would name another host. + auto prefix = proxy_target_prefix(); + if ((!prefix.empty() && req.path[0] != '/') || + detail::write_request_line(bstrm, req.method, prefix + req.path) < 0) { handle.error = Error::Write; handle.response.reset(); return handle; @@ -16248,6 +16251,13 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req, auto prefix = proxy_target_prefix(); if (!prefix.empty()) { + // The path follows the authority in absolute-form, so one that does not + // begin with '/' (e.g. "@evil.example/x") would name another host. + if (req.path[0] != '/') { + error = Error::Write; + return false; + } + auto req2 = req; req2.path = prefix + req.path; ret = process_request(strm, req2, res, close_connection, error); diff --git a/test/test.cc b/test/test.cc index 0ceb6829..e53aa809 100644 --- a/test/test.cc +++ b/test/test.cc @@ -30415,6 +30415,36 @@ TEST(StreamRequestHeadTest, GoesThroughAPlainHttpProxy) { EXPECT_EQ("Basic cHVzZXI6cHBhc3M=", proxy_authz); } +// In absolute-form the path follows the authority, so a path that does not +// begin with '/' would make the proxy connect to another host +TEST(ProxyAbsoluteFormTest, PathMustNotExtendTheAuthority) { + std::vector targets; + no_proxy_test::ScopedServer proxy; + proxy.svr().Get(".*", [&](const Request &req, Response &res) { + targets.push_back(req.target); + res.set_content("ok", "text/plain"); + }); + proxy.listen(); + + Client cli("origin.example", 8080); + cli.set_proxy("127.0.0.1", proxy.port()); + cli.set_basic_auth("user", "pass"); + + for (const auto &path : {"@evil.example/x", ".evil.example/x", ":81/x"}) { + auto res = cli.Get(path); + EXPECT_FALSE(res) << path; + + auto handle = cli.open_stream("GET", path); + EXPECT_FALSE(handle.is_valid()) << path; + } + EXPECT_TRUE(targets.empty()); + + auto res = cli.Get("/x"); + ASSERT_TRUE(res); + ASSERT_EQ(1u, targets.size()); + EXPECT_EQ("http://origin.example:8080/x", targets[0]); +} + TEST(NoProxyTest, ExactHostnameBypasses) { ProxyAndTargetServers s; auto cli = make_client("example.com", s);