mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-28 12:40:54 +00:00
Reject Digest challenges missing realm or nonce (RFC 7616 §3.3)
parse_www_authenticate() accepted any WWW-Authenticate: Digest
challenge that carried at least one auth-param, so a server sending
e.g. Digest qop="auth" with no realm/nonce would make it through.
make_digest_authentication_header() then dereferences auth.at("realm")
and auth.at("nonce") unconditionally, throwing std::out_of_range with
no try/catch on the retry path, which terminates the client process.
Now require both realm and nonce before treating a Digest challenge as
usable, same as if no Digest challenge were present at all.
This commit is contained in:
1 parent
9185fdb6ae
commit
9ce15a14e5
2 files changed
+44
-3
No files matched your search
@@ -9961,9 +9961,12 @@ inline bool parse_www_authenticate(const Response &res,
|
|||||||
auth[std::move(key_part)] = std::move(unquoted);
|
auth[std::move(key_part)] = std::move(unquoted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A challenge with no auth-param can't produce a usable Authorization
|
// RFC 7616 Section 3.3 requires realm and nonce on every Digest challenge;
|
||||||
// header, so treat it the same as no Digest challenge at all.
|
// make_digest_authentication_header() dereferences both unconditionally, so
|
||||||
return found_digest && !auth.empty();
|
// a challenge missing either can't produce a usable Authorization header.
|
||||||
|
// Treat it the same as no Digest challenge at all.
|
||||||
|
return found_digest && auth.find("realm") != auth.end() &&
|
||||||
|
auth.find("nonce") != auth.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContentProviderAdapter {
|
class ContentProviderAdapter {
|
||||||
|
|||||||
@@ -3180,6 +3180,44 @@ TEST(DigestAuthTest, RealmContainingCommaIsNotSplit) {
|
|||||||
"test,realm");
|
"test,realm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RFC 7616 Section 3.3 requires realm and nonce on every Digest challenge.
|
||||||
|
// make_digest_authentication_header() dereferences both unconditionally, so
|
||||||
|
// a server sending a challenge missing either one must not be treated as
|
||||||
|
// usable -- doing so used to crash the client with std::out_of_range.
|
||||||
|
static void run_digest_challenge_missing_field_test(const char *challenge) {
|
||||||
|
Server svr;
|
||||||
|
svr.Get("/x", [&](const Request & /*req*/, Response &res) {
|
||||||
|
res.status = StatusCode::Unauthorized_401;
|
||||||
|
res.set_header("WWW-Authenticate", challenge);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto port = svr.bind_to_any_port(HOST);
|
||||||
|
std::thread t([&]() { svr.listen_after_bind(); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
});
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, port);
|
||||||
|
cli.set_digest_auth("hello", "world");
|
||||||
|
auto res = cli.Get("/x");
|
||||||
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
|
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DigestAuthTest, ChallengeMissingRealmAndNonceDoesNotCrash) {
|
||||||
|
run_digest_challenge_missing_field_test("Digest qop=\"auth\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DigestAuthTest, ChallengeMissingNonceDoesNotCrash) {
|
||||||
|
run_digest_challenge_missing_field_test("Digest realm=\"r\", qop=\"auth\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DigestAuthTest, ChallengeMissingRealmDoesNotCrash) {
|
||||||
|
run_digest_challenge_missing_field_test("Digest nonce=\"n\", qop=\"auth\"");
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
|
TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
|
||||||
|
|||||||
Reference in new issue
Block a user