humanitec / django-oauth-toolkit-jwt

django-oauth-toolkit extension that adds JWT support
MIT License
38 stars 26 forks source link

Potential security issue when using "Authorization Code" flow #14

Closed jdmwood closed 5 years ago

jdmwood commented 5 years ago

Hi there,

I think I have found a security problem.

In views.py in _get_access_token_jwt() it allows embedding the username in the JWT payload:

if request.POST.get('username'):
    extra_data['username'] = request.POST.get('username')

This username is then used in authentication.py to figure out who the user is.

But, when using the "Authorization Code" flow, I can't see how the username is verified in views.py to check that it actually is the same as the user who is requesting the access code.

I experimented and I found that I was able to get an access code with user A, but then enter a different user in the POST request to get the token and it just accepted this.

Here is a test case to prove this issue. Add the following code to test_views.py:

    def test_get_token_authorization_code_wrong_user(self):
        """
        Request an access token using Authorization Code Flow
        """
        Application.objects.create(
            client_id='user_app_id',
            client_secret='user_app_secret',
            client_type=Application.CLIENT_CONFIDENTIAL,
            authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
            name='user app',
            skip_authorization=True,
            redirect_uris='http://localhost:8002/callback',
        )

        self.client.force_login(self.test_user)

        response = self.client.get(reverse("oauth2_provider_jwt:authorize") +
                                   '?response_type=code&client_id=user_app_id')

        self.assertEqual(302, response.status_code)
        match = re.match(r'http://localhost:8002/callback\?code=(\w+)',
                         response.url)
        self.assertIsNotNone(match)
        code = match.group(1)

         # To simulate that the token call is normally made unauthenticated
        self.client.logout()
        data = {
            'client_id': 'user_app_id',
            'client_secret': 'user_app_secret',
            'code': code,
            'grant_type': 'authorization_code',
            'redirect_uri': 'http://localhost:8002/callback',
            'username': 'some_fake_user',  # Pass in wrong user
        }
        response = self.client.post(reverse("oauth2_provider_jwt:token"), data)
        self.assertNotEqual(200, response.status_code)

Near the end, we pass in the wrong username. This is bad because this username is baked into the JWT payload.

rafa-munoz commented 5 years ago

Hi John. Thanks a lot for reporting. I will be looking to it this week as soon as I can.