In Depth: Three Reasonable Decisions, One Critical Vulnerability

[In Depth #41] What do you get when you combine an API, SameSite=None, and a Session cookie?

Share
In Depth: Three Reasonable Decisions, One Critical Vulnerability
🕵️
Quick note: this is exactly the sort of thing I find during a Laravel Security Audit and Penetration Test, and I have a few openings over the next few months. Reach out and let's talk.

Let's take a break from looking at Supply Chain issues to explore a fun vulnerability I found a couple of months ago during a security audit, as there are a few lessons to be learned about how minor weaknesses can compound into a bigger vulnerability with significant impacts.

🙊
Note, I've changed names and details to protect my client's privacy, however the root cause and impacts of the vulnerability remain. (They also fixed this issue really quickly - but more on that later.)

Background

Let's set the scene - my client's project has a couple of key features we need to be aware of:

  1. Livewire app, with some legacy Vue components.
  2. Provides an API for their users, using API tokens.
  3. The Vue components use the API to communicate with the server.
  4. Session cookies authenticate into the API for the legacy Vue components.

This application is primarily a Livewire application, but it includes some legacy Vue components. To make things simple, the Vue components interact with the server using the same API that users have access to. However, rather than generating an API token for Vue to use on demand in the browser, they added Middleware before the API layer that translates the cookie-based session into an API key Authorization: Bearer <token> header injected directly into the Request object. When the API initiates, it sees the Bearer Token and authenticates the user.

On face value, it sounds like a clever solution: The Vue components just work because the session cookie is there, and bearer tokens work for the API for external users.

I can see the appeal - the Vue components are old and fragile, and until they complete the migration to Livewire, handling authorization elsewhere feels like a safe move.

However, the Session Cookie was also marked as SameSite=None...

Dangers of SameSite=None

Surprisingly I haven't really talked much about the SameSite cookie attribute on Securing Laravel (I will need to fix that), so let me quickly explain what it does: SameSite instructs the browser when cookies are allowed to be sent, both in terms of same-site vs cross-site requests and the type of requests.

  • Same-Site Requests are requests between the same domain (or subdomain). For example, when you have securinglaravel.com/tip-1 open in the browser and click on a link for securinglaravel.com/tip-2. The browser recognises the domain is the same, and applies the security context required. Subdomains are also included, so alpha.securinglaravel.com -> beta.securinglaravel.com is also considered the same site.
  • Cross-Site Requests are requests between different domains - such as going from securinglaravel.com through to laravel.com.

There are three values we can set for SameSite on our cookies:

  • SameSite=Strict blocks all Cross-Site requests. Not only does this include form requests via POST, Javascript calls via other methods, and any form of embedding content (such as <frame>), but it also blocks GET link clicks.
  • SameSite=None allows all Cross-Site requests. Any request, no matter how it is formed, includes the cookie. (SameSite=None also requires the Secure attribute and an https:// connection.)
  • SameSite=Lax is a combination of the two (and is also the default value applied by most browsers when you don't define it):
    • Safe Requests are basically just top level navigation using GET, such as clicking on links between sites. These are the safe scenarios where you expect an existing session on the target site to resume immediately.
    • Unsafe Requests are everything else. This includes non-GET requests (forms, Javascript, etc), embedded or loaded content (frames, scripts, images, etc). Cookies are completely blocked within these requests.
💡
Side note: SameSite=Lax is not the default in Firefox or Safari, so it's important to define SameSite=Lax to gain its full protection.

I'll write a dedicated article about SameSite cookies soon - all you need to know for now is that SameSite=None disables security controls and allows session cookies to be included on all requests.

As I said above, the session cookie that allowed API access was set with SameSite=None. This is also a concern, however there is one thing we haven't even talked about yet, that turned this from bad to critical...