Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JWT header and signature ignored? #261

Closed
maratumba opened this issue Sep 6, 2020 · 4 comments
Closed

JWT header and signature ignored? #261

maratumba opened this issue Sep 6, 2020 · 4 comments

Comments

@maratumba
Copy link

It is possible that this is the intended behaviour or it is not caused by gotrue. I thought that it is odd and potentially a security issue and since gotrue is the service that is probably causing this, so I'm posting it here.

I'm using Netlify CMS (^2.0.0) on Netlify and Netlify Functions. This happens both with netlify dev and on production.

- Do you want to request a feature or report a bug?
Bug?

- What is the current behavior?
Changing the header and signature parts of a JWT seem to have no effect on authorization with gotrue check in Netlify Functions.

- If the current behavior is a bug, please provide the steps to reproduce.
Create a netlify function that checks identity at /functions/idTest.js:

exports.handler =  function(event, context, callback) {
  const {identity, user} = context.clientContext;
  callback(null, {
    statusCode: 200,
    headers: {
      'content-type': 'application/json',
    },
    body: JSON.stringify({event, context})
  });
}

In Netlify CMS, register to a publish hook and call the Netlify Function with the JWT token in localStorage as authorization token.

   <!-- following https://www.netlifycms.org/docs/nuxt/ -->
    <script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
    <script>
      console.log({CMS})
      const token = JSON.parse(window.localStorage.getItem('gotrue.user')).token.access_token
      CMS.registerEventListener({
        name: 'prePublish',
        handler: (ctx) => {
          console.log({ctx})
          const headers = {Authorization: `Bearer ${token}`}
          fetch('/.netlify/functions/idTest', {headers}).then(resp => resp.json()).then(data => console.log(data))
        }
      });
    </script>

(Assuming that you use firefox) Open devtools, login to the Netlify CMS admin, create a new post and publish. Check the response from /.netlify/functions/idTest in Network tab. You should have a non-empty context.clientContext object, meaning the netlify function authorized you.

Now edit the request to /.netlify/functions/idTest, change the Authorization: Bearer <header>.<token>.<signature> to Authorization: Bearer A.<token>.A (basically replace header and signature parts with any random characters) Now check the response for context.clientContext again.

- What is the expected behavior?
context.clientContext should be empty b/c we just sent an invalid JWT. But it is not. The JWT somehow evaluated to be valid in Netlify Function.

- Please mention your Go version, and operating system version.
Using Javascript on Ubuntu 20.04

@jon-sully
Copy link

Hey there 👋🏻 I know this is a couple months old but given that it's implying a major security vulnerability, I wanted to address it. I also just walked through this process on CLI and can confirm that changing a valid token in any way invalidates it and does not register in the Function as a valid user. This part:

You should have a non-empty context.clientContext object, meaning the netlify function authorized you.

Isn't necessarily valid. Any Function running on a site that has Identity enabled will have a non-empty context.clientContext object. For every function on that site, on every invocation, regardless of what that Function is made to do. It's automatic, and it's there because if any of your functions need to make admin-level changes to your GoTrue instance (change a user's info / roles / etc.), the context.clientContext is what contains the admin token to do that with. Here's the difference between a Function invocation on an Identity-enabled Site when the Authorization header comes in valid:

  "clientContext": {
    "custom": {
      "netlify": "eyJpreallywiOiJodHRwclong5zYXJnZXNptoken8ubmV0bGlmeS9bunchsInRva2VuIjoiZXlKaGJHYpfU5pSXNJblI1Y0NJNklrrandomV5SmxlSEFpT2pFMcharactersyTkRZc0luTjFZaUk2blahJGT2tLd2tZS0xwcUJYd1ZDN1hzUlE2T1VsOC1"
    },
    "identity": {
      "url": "https://sun.sargesites.com/.netlify/identity",
      "token": "<this-is-the-admin-token>eyJhbGafwfwR5cCI6IkpXVCJ9.eyJleHfawfsafwafwafsaInN1YiI6IjAifQ.RFOfawfwafwasRQ6OUl8-Q76esfawfwafwafDEwsmjkPaU"
    },
    "user": {
      "app_metadata": {
        "provider": "email",
        "roles": [
          "member",
          "admin"
        ]
      },
      "email": "hey@jon.fm",
      "exp": 962141183,
      "sub": "e6afsa500c-b1afe-4asff-8safc-8afafwf89614",
      "user_metadata": {
        "full_name": "Jons Test Person"
      }
    }
  },

And when it comes in having been messed with a little bit:

"clientContext": {
    "custom": {
      "netlify": "eyJpreallywiOiJodHRwclong5zYXJnZXNptoken8ubmV0bGlmeS9bunchsInRva2VuIjoiZXlKaGJHYpfU5pSXNJblI1Y0NJNklrrandomV5SmxlSEFpT2pFMcharactersyTkRZc0luTjFZaUk2blahJGT2tLd2tZS0xwcUJYd1ZDN1hzUlE2T1VsOC1"
    },
    "identity": {
      "url": "https://jons-domain.com/.netlify/identity",
      "token": "<this-is-the-admin-token>eyJhbGafwfwR5cCI6IkpXVCJ9.eyJleHfawfsafwafwafsaInN1YiI6IjAifQ.RFOfawfwafwasRQ6OUl8-Q76esfawfwafwafDEwsmjkPaU"
    }
  },

You'll notice a distinct lack of the user object. That is ultimately what defines whether or not an inbound request to a Function gets correctly connected to a legitimate User. If the JWT comes in bad (expired, altered, otherwise wrong), this user object will not exist.

Of course, even when the user object is wrong, you still get your standard admin token and such 👍🏻

Hope that helps.

--
Jon

@maratumba
Copy link
Author

Makes sense. Thanks for the detailed answer.

@depadiernos
Copy link

thanks for that response @jon-sully. @maratumba, is this ok to close?

@maratumba
Copy link
Author

Yes @depadiernos, I'm closing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants