Replies: 6 comments 8 replies
-
Nice explanation. I was just looking at some code and I do a |
Beta Was this translation helpful? Give feedback.
-
@vickkhera yes, that would mitigate it as well. setSession calls getUser under-the-hood, and then saves a subset of data to /* Reference: https://github.com/supabase/auth-js/blob/master/src/GoTrueClient.ts#L1328-L1334 */
{
access_token, /* Whatever was passed-in to setSession() */
refresh_token, /* Whatever was passed-in to setSession() */
user, /* Based on the getUser() call */
token_type, /* Always "bearer" */
expires_at, /* Calculated from the decoded access_token */
expires_in /* From the decoded access_token */
} |
Beta Was this translation helpful? Give feedback.
-
Thank you for writing this, it's really helpful.
|
Beta Was this translation helpful? Give feedback.
-
Thank you for this explanation @j4w8n. The team is actively working on new features within auth-js that makes it easier to verify the JWT (access token) soon. This should alleviate some of the problems with latency if you rely on
A leaked JWT secret opens up not only your users under attack (as they can be easily impersonated) but more severely your database (the secret can be used to mint your own JWTs with any role that can bypass RLS). |
Beta Was this translation helpful? Give feedback.
-
@j4w8n I have a simple question on this.
Is this only an issue though if there are "trusted" operations performed server side or server component side? If one performs queries for example in a server component and implements @supabase/ssr the mal formed attacker cookie would be passed to the server side supabase instance for example in a middleware in next.js or passed to an edgefunction that generate a server based client with it. For the middle ware In the old version in of the supabase docs (from a couple weeks ago) this still used If we stick to the old version
in an edge function same by reading the auth header from the request and passing that into the create client
this would use the mal formed cookie now and not verify it. But if one now performs a database query with this mal formed supabase client the query should fail on the backend as the backend would try to validate the malformed jwt that is send with the request? |
Beta Was this translation helpful? Give feedback.
-
Can I ask, other than |
Beta Was this translation helpful? Give feedback.
-
As briefly described on PR supabase/auth-helpers#722, an attacker can retrieve sensitive user data through an attack vector within auth-js and a developer's code, when storing a session in cookies and using a framework's server-side rendering feature. In this discussion, I'd like to clarify this topic, from my perspective, and point out some solutions and pitfalls. I welcome your thoughts.
getSession()
If we look at how this method works, we see that it runs minimal checks on the potential session it grabs from storage. When I say "minimal checks", I'm not casting any blame; Supabase can't do much more than what it already is.
null
.object
, and that it has the propertiesaccess_token
,refresh_token
, andexpires_at
.expires_at
is some point in the future, it returns the session to you.This seems reasonable, but if we craft a cookie with the following values, it would pass all of those checks:
Developer code
Say you're server-side rendering a user's page that's been requested. You check that the user is logged-in by calling
getSession()
- perhaps in middleware. Data from that call comes back to you, so it seems we have an authenticated user for this request. So, now you fetch the user's sensitive data based on the value ofsession.user.id
, then the page contents are returned and rendered in the browser. Everything is working great.Attack!
For this attack to work, we need to assume the attacker was somehow able to acquire a victim-user's Supabase user id. We also assume the attacker does not have the user's access token or refresh token - otherwise they could just use those to get the victim's data. Now imagine the attacker sends a page request to the server, using a properly-named cookie with this data:
Oh snap! All of the
getSession()
checks have passed and you've now sent the victim-user's sensitive data back to the attacker - not the shown user's id per se, but whatever data you fetched and rendered on the page using that id.Mitigation
!!!!!
Only use your JWT secret on the server-side, and only via a private env var. If there are other secure methods, I don't know what they are.
!!!!!
Here are a few things we can do to prevent this attack.
session
to render sensitive data. The only exception is verifying and decoding theaccess_token
, with a library such as jsonwebtoken, and then use information within the decoded JWT to load sensitive user data. So, from our example, instead of usingsession.user.id
, grab the value of thesub
property from a verified and decoded JWT - which is the user id of whoever the JWT was created for.getUser()
to retrieve the user's data and use it to fetch further data for the page. Yes, the getUser call grabs theaccess_token
from getSession and verifies it, but it's worth mentioning that you still can't trust anything else from thesession
info returned bygetSession()
. Anyway, if step 1 is not feasible or desired, calling getUser is the easiest solution. The downside is that it requires an extra network request every time you call it - either to Supabase's cloud instance or your self-hosted Supabase instance.False Security
When I came across this attack vector, my natural solution was to simply call
getSession()
and verify the JWT, akasession.access_token
. I assumed that as long as the JWT checks out, I can use data withinsession.user
to render the page.However, after thinking about it more, I realized this is a mistake. To understand why, imagine an attacker signs up for an actual account with your app and signs-in. They can then take their legit access token and send a separate request with the below cookie data:
As you can see, this would pass the
getSession()
checks and your JWT verification, but would still return the victim-user's data if you're usingsession.user.id
. So don't assume everything inside ofsession
can be trusted just because you were able to verify the access_token.If, instead, we were to grab the legit attacker's token and use the decoded JWT info from it, this would render the attacker's data instead of the would-be victim's. Just take precautions to only use your project's JWT secret, to verify/decode, on the server-side.
Beta Was this translation helpful? Give feedback.
All reactions