There are many different authentication primitives built into browsers. The most common include Web Forms authentication, HTTP authentication, client certificate authentication, and the new WebAuthN standard. Numerous different authentication frameworks build atop these, and many enterprise websites support more than one scheme.
Each of the underlying authentication primitives has different characteristics: client certificate authentication is the most secure but is hard to broadly deploy, HTTP authentication works great for Intranets but poorly for most other scenarios, and Web Forms authentication gives the website the most UI flexibility but suffers from phishing risk and other problems. WebAuthN is the newest standard and is not yet supported by most sites.
Many Enterprises will combine all of these schemes, using a flow something like:
In today’s post, I want to take a closer look at Step #6.
Unfortunately for our scenario, the HTTP Authentication scheme doesn’t support any sort of NoUI
attribute, meaning that a server has no way to demand “Authenticate using the user’s domain credentials if and only if you can do so without prompting.”
WWW-Authenticate: Negotiate
And browsers’ HTTP Authentication prompts tend to be pretty ugly:
Depending upon client configuration and privacy mode, HTTP Authentication using the Negotiate (wrapping Kerberos/NTLM) or NTLM schemes may happen silently, or it may trigger the manual HTTP Authentication prompt.
So, at step #6, we’re stuck. If automatic HTTP authentication would’ve worked, it would be great– the user would be signed into the application with zero clicks and everything would be convenient and secure.
Fortunately for our scenario (unfortunately for understandability), there’s a magic trick that authentication flows can use to try HTTP authentication silently. As far as I can tell, it was never designed for this purpose, but it’s now used extensively.
To help prevent phishing attacks, modern browsers will prevent1 an HTTP authentication prompt from appearing if the HTTP/401 authentication response was for a cross-site image resource. The reasoning here is that many public platforms will embed images from arbitrary URLs, and an attacker might successfully phish users by posting on a message board an image reference that demands authentication. An unwary user might inadvertently supply their credentials for the message board to the third party site.
As noted in Chromium:
if (resource_type == blink::mojom::ResourceType::kImage && IsBannedCrossSiteAuth(request.get(), passed_extra_data.get())) { // Prevent third-party image content from prompting for login, as this // is often a scam to extract credentials for another domain from the // user. Only block image loads, as the attack applies largely to the // "src" property of the <img> tag. It is common for web properties to // allow untrusted values for <img src>; this is considered a fair thing // for an HTML sanitizer to do. Conversely, any HTML sanitizer that didn't // filter sources for <script>, <link>, <embed>, <object>, <iframe> tags // would be considered vulnerable in and of itself. request->do_not_prompt_for_login = true; request->load_flags |= net::LOAD_DO_NOT_USE_EMBEDDED_IDENTITY; }
So, now we have the basis of our magic trick.
We use a cross-site image resource (e.g. https://tryhttpauth.corp-intranet.com
) into our login flow. If the image downloads successfully, we know the user’s browser has domain credentials and is willing to silently release them. If the image doesn’t download (because a HTTP/401 was returned and silently unanswered by the browser) then we know that we cannot use HTTP authentication and we must continue on to use the WebForms/WebAuthN authentication mechanism.
-Eric
1 Note that this magic trick is defeated if you enable the AllowCrossOriginAuthPrompt policy, because that policy permits the authentication prompt to be shown.
As an aside, the HTTP Authentication prompt shown in this flow is more annoying than it strictly needs to be. What it’s usually really asking is “May I release your credentials to this site?“:
…but for implementation simplicity and historical reasons the prompt instead forces the user to retype their username and password.