diff --git a/components/webauthn/core/browser/BUILD.gn b/components/webauthn/core/browser/BUILD.gn index 34c19be671c19..5b2f4ccd8da3b 100644 --- a/components/webauthn/core/browser/BUILD.gn +++ b/components/webauthn/core/browser/BUILD.gn @@ -102,9 +102,22 @@ source_set("common_utils") { ] } +executable("remote_validation_poc") { + testonly = true + sources = [ "remote_validation_poc.cc" ] + deps = [ + ":common_utils", + "//base", + "//url", + ] +} + source_set("unit_tests") { testonly = true - sources = [ "webauthn_security_utils_unittest.cc" ] + sources = [ + "remote_validation_unittest.cc", + "webauthn_security_utils_unittest.cc", + ] deps = [ ":common_utils", "//testing/gtest", diff --git a/components/webauthn/core/browser/remote_validation_poc.cc b/components/webauthn/core/browser/remote_validation_poc.cc new file mode 100644 index 0000000000000..8358d9219d3bc --- /dev/null +++ b/components/webauthn/core/browser/remote_validation_poc.cc @@ -0,0 +1,60 @@ +// Standalone POC: demonstrates that RemoteValidation::ValidateWellKnownJSON +// returns kSuccess for an http:// (non-secure) caller_origin when the RP's +// .well-known/webauthn lists that http:// origin. On iOS, PasskeyTabHelper +// reaches this code without first calling OriginAllowedToMakeWebAuthnRequests, +// so this kSuccess directly enables a passkey assertion from a MITM'd http:// +// page. Desktop //content gates this behind a secure-context check; iOS does +// not. +// +// Build: autoninja -C out/asan remote_validation_poc +// Run: out/asan/remote_validation_poc + +#include + +#include "base/command_line.h" +#include "components/webauthn/core/browser/remote_validation.h" +#include "components/webauthn/core/browser/webauthn_security_utils.h" +#include "url/gurl.h" +#include "url/origin.h" + +int main(int argc, char** argv) { + base::CommandLine::Init(argc, argv); + + // Attacker-controlled page (delivered via plaintext-HTTP MITM). + const url::Origin caller = + url::Origin::Create(GURL("http://partner.example")); + + // Sanity: WebAuthn correctly classifies this origin as non-trustworthy. + const webauthn::ValidationStatus origin_status = + webauthn::OriginAllowedToMakeWebAuthnRequests(caller); + printf("OriginAllowedToMakeWebAuthnRequests(http://partner.example) = %d " + "(kSuccess=%d, kInvalidDomain=%d)\n", + static_cast(origin_status), + static_cast(webauthn::ValidationStatus::kSuccess), + static_cast(webauthn::ValidationStatus::kInvalidDomain)); + + // Body of https://victim-rp.com/.well-known/webauthn (RP misconfiguration: + // http scheme listed). + constexpr char kWellKnown[] = + R"({"origins":["http://partner.example"]})"; + + const webauthn::ValidationStatus result = + webauthn::RemoteValidation::ValidateWellKnownJSON(caller, kWellKnown); + + printf("ValidateWellKnownJSON(http://partner.example, " + "{\"origins\":[\"http://partner.example\"]}) = %d (kSuccess=%d)\n", + static_cast(result), + static_cast(webauthn::ValidationStatus::kSuccess)); + + if (result == webauthn::ValidationStatus::kSuccess) { + printf( + "\n[VULN CONFIRMED] http:// caller authorised for cross-site RP ID via " + ".well-known.\n" + "On iOS, PasskeyTabHelper::OnRemoteRpIdValidationCompleted now calls " + "HandleAssertion()\nand the victim's passkey is offered to the " + "MITM-controlled http:// page.\n"); + return 0; + } + printf("\n[FIXED] http:// caller was rejected.\n"); + return 1; +} diff --git a/components/webauthn/core/browser/remote_validation_unittest.cc b/components/webauthn/core/browser/remote_validation_unittest.cc new file mode 100644 index 0000000000000..5328baa6e66e0 --- /dev/null +++ b/components/webauthn/core/browser/remote_validation_unittest.cc @@ -0,0 +1,73 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// POC unit test: demonstrates that RemoteValidation::ValidateWellKnownJSON +// accepts an http:// (non-secure) caller_origin when the relying party's +// .well-known/webauthn lists the matching http:// origin. On iOS, +// PasskeyTabHelper invokes RemoteValidation without first calling +// OriginAllowedToMakeWebAuthnRequests, so this kSuccess result allows a +// network attacker MITMing http://partner.example to obtain a passkey +// assertion for victim-rp.com. + +#include "components/webauthn/core/browser/remote_validation.h" + +#include "components/webauthn/core/browser/webauthn_security_utils.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace webauthn { + +namespace { + +// VULN: The .well-known parser places no scheme restriction on listed origins, +// and no secure-context restriction on the caller. An http:// caller listed in +// a (misconfigured) .well-known is accepted. +TEST(RemoteValidationVulnTest, HttpCallerOriginAcceptedViaWellKnown) { + // Attacker-controlled page (via plaintext-HTTP MITM). + const url::Origin caller = + url::Origin::Create(GURL("http://partner.example")); + + // Sanity: WebAuthn correctly considers this origin non-trustworthy — desktop + // //content gates RemoteValidation behind this check, iOS does not. + ASSERT_EQ(OriginAllowedToMakeWebAuthnRequests(caller), + ValidationStatus::kInvalidDomain); + + // Body of https://victim-rp.com/.well-known/webauthn (RP misconfiguration: + // http scheme listed). + constexpr char kWellKnown[] = + R"({"origins":["http://partner.example"]})"; + + // *** Security-relevant assertion *** + // Expected (secure) behaviour: anything other than kSuccess. + // Actual behaviour: kSuccess — the http:// caller is authorized to use the + // RP ID. On iOS this flows directly into HandleAssertion(). + const ValidationStatus result = + RemoteValidation::ValidateWellKnownJSON(caller, kWellKnown); + + fprintf(stderr, + "[POC] ValidateWellKnownJSON(http://partner.example, " + "{\"origins\":[\"http://partner.example\"]}) -> %d (kSuccess=%d)\n", + static_cast(result), + static_cast(ValidationStatus::kSuccess)); + + // The bug: this passes. A correct implementation would reject http callers. + EXPECT_EQ(result, ValidationStatus::kSuccess) + << "If this stops passing, the http:// acceptance has been fixed."; +} + +// Control case: an https caller listed in .well-known is the legitimate +// related-origin-requests use case and must continue to pass. +TEST(RemoteValidationVulnTest, HttpsCallerOriginAcceptedViaWellKnown) { + const url::Origin caller = + url::Origin::Create(GURL("https://partner.example")); + constexpr char kWellKnown[] = + R"({"origins":["https://partner.example"]})"; + EXPECT_EQ(RemoteValidation::ValidateWellKnownJSON(caller, kWellKnown), + ValidationStatus::kSuccess); +} + +} // namespace + +} // namespace webauthn