What are Authentication Tokens
Tokens are the most common way to hold user authentication information.
A token can be anything from a random string of characters to a fully formed JSON Web Token (JWT). Their primary purpose is to prove that the user navigating to a particular site has previously verified their identity at some point. This is generally achieved either by verifying the signature on the token or by pinging the identity provider to confirm the validity and authenticity of a token.
Tokens typically have an expiration date set by the server that issued them. Usually, the expiration date of tokens created for B2C purposes is 30 to 90 days, while in enterprise settings it can be anywhere between a few hours to several weeks.
Why are Authentication Tokens valuable?
Attackers who obtain authentication tokens can impersonate a user on a website or application. Once they exploit tokens to breach the system, they can move laterally to compromise the rest of the infrastructure in enterprise scenarios, or commit various forms of theft and fraud in the consumer world.
Before the introduction of multifactor authentication (MFA), credential stuffing attacks were significantly more frequent than token-stealing attacks, since it is generally easier to guess a password than to compromise a website, a browser, or a device.
However, today botnets and cybercriminals increasingly target cookies since they allow MFA bypass without resorting to social engineering.
The Genesis marketplace, one of the biggest darknet markets used by attackers, contains hundreds of thousands of stolen cookies, including the one used to initiate the recent attack against the game developer Electronic Arts which resulted in 780GB of proprietary data exfiltrated.
Other examples of recent attacks involving compromised cookies/tokens include the last generation of the EMOTET malware, possibly the most dangerous botnet of the last decade, and financially motivated phishing campaigns against popular YouTube influencers.
How are Auth Tokens stolen?
Auth Tokens can be stolen through a variety of methods, including:
-
Social Engineering: In this scenario, the attacker creates a phishing campaign where the victim successfully logs into a remote service but their credentials are intercepted. Microsoft calls these attacks ‘Adversary in the middle’ (AiTM). Frameworks to carry out credential phishing attacks are readily available on GitHub and in various forums.
-
Man in the middle (MITM) attacks: If the server doesn’t employ https or if an attacker can forge a SSL certificate, they can intercept the victim’s network traffic and steal cookies/tokens.
-
Web Vulnerability (XSS, Session Hijacking): Non-HttpOnly cookies are at risk of being stolen through various web exploitation techniques.
-
Browser Compromise: As discussed previously, any violation of the browser security model can result in stolen cookies.
-
Device Compromise: Countless malware and botnets are capable of stealing cookies once a user device is compromised. Notable examples include: Raccoon Stealer, Sorano and RedLine.
Most recently, more and more enterprise customers have been attacked with various forms of reverse proxies to bypass MFA.
Once the attacker has stolen cookies/tokens, they generally breach and move laterally in the system to elevate their privileges within an enterprise setting or to identity theft or credit card information theft within a B2C scenario.
WebAuthn, in a nutshell
WebAuthn enables safe public-key cryptography inside the browser directly from JavaScript through an interface exposed via navigator.credentials
.
This is what happens under the hood when your user registers a new account on a website with WebAuthn:
- The browser interacts with an Authenticator, which can either be a Platform Authenticator (such as Windows Hello, TouchID, FaceID and similar) or an external/Roaming Authenticator such as a Yubikey or a Titan Key.
- The Authenticator generates a key pair, stores the private key in a secure location and returns the public key to the server via the browser API.
Any time a user wants to log-in, they sign a blob with the private key stored on their device. Then, the remote server verifies the authenticity of the blob by checking its signature through the public key obtained in the registration process.
WebAuthn Anti-Phishing Properties
WebAuthn provides strong protection against phishing attacks. In fact, all compliant browsers enforce a number of security checks.
First of all, WebAuthn doesn’t work without TLS. Furthermore, browsers enforce origin checks and most will prevent access to the platform authenticator unless the window is in focus or, in the case of Safari, the user triggers an action.
In other words, an attacker trying to compromise user credentials will need to either find a cross-site scripting (XSS) bug in the target website or a vulnerability in the browser, both of which are very high barriers to overcome. This is the only way in which they can bypass the WebAuthn checks.
In either scenario, a successful attack still won’t give the attacker access to the private key itself, but only to a session token/cookie which will expire once the browsing session is over.
Most importantly, due to the origin checks, most of the recent attacks involving domain squatting and phishing would fall flat because the reverse proxy wouldn’t be able to initiate the WebAuthn authentication process.
In comparison to all other authentication methods — where an attacker only has to register a seemingly related domain and have a similar looking webpage to fool the victim through phishing — it is clear that WebAuthn is a vastly superior authentication method.
The technical details
For the technically curious, here’s a code snippet taken from the Chrome web browser. It shows the set of checks Chrome performs on a WebAuthn authentication assertion request to verify its origin:
...
status = security_checker_->ValidateDomainAndRelyingPartyID(
caller_origin, options->relying_party_id, request_type,
options->remote_desktop_client_override);
if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
CompleteGetAssertionRequest(status);
return;
}
…
When an authentication request reaches the browser, Chrome will call ValidateDomainAndRelyingPartyID
.
blink::mojom::AuthenticatorStatus
WebAuthRequestSecurityChecker::ValidateDomainAndRelyingPartyID(
const url::Origin& caller_origin,
const std::string& relying_party_id,
RequestType request_type,
const blink::mojom::RemoteDesktopClientOverridePtr&
remote_desktop_client_override) {
…
blink::mojom::AuthenticatorStatus domain_validation =
OriginAllowedToMakeWebAuthnRequests(caller_origin);
if (domain_validation != blink::mojom::AuthenticatorStatus::SUCCESS) {
return domain_validation;
}
…
if (!OriginIsAllowedToClaimRelyingPartyId(relying_party_id,
relying_party_origin)) {
return blink::mojom::AuthenticatorStatus::BAD_RELYING_PARTY_ID;
}
return blink::mojom::AuthenticatorStatus::SUCCESS;
}
Then it calls OriginAllowedToMakeWebAuthnRequests
to check whether the origin is localhost or https. It then proceeds to call OriginIsAllowedToClaimRelyingPartyId
to verify that the current domain matches the domain associated with the key, thus preventing phishing attempts where a domain other than the original tries to retrieve a valid user credential.
Let’s look at the two functions one by one.
blink::mojom::AuthenticatorStatus OriginAllowedToMakeWebAuthnRequests(
url::Origin caller_origin) {
…
if (caller_origin.opaque()) {
return blink::mojom::AuthenticatorStatus::OPAQUE_DOMAIN;
}
// The scheme is required to be HTTP(S). Given the
// |network::IsUrlPotentiallyTrustworthy| check below, HTTP is effectively
// restricted to just "localhost".
if (caller_origin.scheme() != url::kHttpScheme &&
caller_origin.scheme() != url::kHttpsScheme) {
return blink::mojom::AuthenticatorStatus::INVALID_PROTOCOL;
}
// TODO(https://crbug.com/1158302): Use IsOriginPotentiallyTrustworthy?
if (url::HostIsIPAddress(caller_origin.host()) ||
!network::IsUrlPotentiallyTrustworthy(caller_origin.GetURL())) {
return blink::mojom::AuthenticatorStatus::INVALID_DOMAIN;
}
return blink::mojom::AuthenticatorStatus::SUCCESS;
}
OriginAllowedToMakeWebAuthnRequests
will only allow localhost or https origins.
// Returns whether a caller origin is allowed to claim a given Relying Party ID.
// It's valid for the requested RP ID to be a registrable domain suffix of, or
// be equal to, the origin's effective domain. Reference:
// https://html.spec.whatwg.org/multipage/origin.html#is-a-registrable-domain-suffix-of-or-is-equal-to.
bool OriginIsAllowedToClaimRelyingPartyId(
const std::string& claimed_relying_party_id,
const url::Origin& caller_origin) {
// `OriginAllowedToMakeWebAuthnRequests()` must have been called before.
DCHECK_EQ(OriginAllowedToMakeWebAuthnRequests(caller_origin),
blink::mojom::AuthenticatorStatus::SUCCESS);
…
if (claimed_relying_party_id.empty()) {
return false;
}
if (caller_origin.host() == claimed_relying_party_id) {
return true;
}
if (!caller_origin.DomainIs(claimed_relying_party_id)) {
return false;
}
…
return true;
}
OriginIsAllowedToClaimRelyingPartyId
verifies that the request comes from the same domain associated with the relying party, which is stored in the authentication assertion request.
The function first checks whether the relying party ID in the WebAuthn request is empty; if so, the request is denied. If the relying party ID matches the domain where the request originates, or is a registrable domain suffix, the request is allowed.
The enforcement of https, combined with the condition that the relying party ID has to match the origin domain of the request, can prevent reverse-proxy phishing attacks.
For example, in the scenario illustrated below the attacker would need to craft a payload of this kind to call navigation.credentials.get()
and begin an authentication assertion process.
var options = {
// The challenge is produced by the server; see the Security Considerations
challenge: new Uint8Array(/* 32 more random bytes generated by the server */]),
rpId: “target-website.com”
timeout: 120000, // 2 minutes
};
navigator.credentials.get({ "publicKey": options })
...
However, because the rpID
and the origin do not match, OriginIsAllowedToClaimRelyingPartyId
would prevent the call from succeeding, thus preventing the attack.
Conclusion
In summary, WebAuthn can significantly reduce the risk of phishing as well as the usefulness of a stolen session token.
In fact, WebAuthn goes a step further than other authentication methods, and prevents phishing attacks while also reducing UX friction. The seamless user experience allows for more frequent authentication prompts, thus offsetting the need for long-lived authentication tokens.
Like most security measures, Webauthn is not a panacea in and of itself. In particular, weak key recovery mechanisms and the presence of weaker authentication schemes(e.g., SMS OTP) could render WebAuthn moot.
However, its adoption in conjunction with short-lived session cookies and other mitigations (e.g., risk-based MFA, IP address checks/geofencing, browser fingerprinting) can be an invaluable combination to curb phishing and eliminate credential stuffing attacks.