CVE-2023-39349 (published 2023-08-07T19:15Z)

Sentry is an error tracking and performance monitoring platform. Starting in version 22.1.0 and prior to version 23.7.2, an attacker with access to a token with few or no scopes can query `/api/0/api-tokens/` for a list of all tokens created by a user, including tokens with greater scopes, and use those tokens in other requests. There is no evidence that the issue was exploited on `sentry.io`. For self-hosted users, it is advised to rotate user auth tokens. A fix is available in version 23.7.2 of `sentry` and `self-hosted`. There are no known workarounds.

CVSS v3 base score: 8.1, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         
cwe_id: CWE-284
cwe_name: Improper Access Control
description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
extended_description: 
            Access control involves the use of several protection mechanisms such as:
            
               Authentication (proving the identity of an actor)
               Authorization (ensuring that a given actor can access a resource), and
               Accountability (tracking of activities that were performed)
            
            When any mechanism is not applied or otherwise fails, attackers can compromise the security of the product by gaining privileges, reading sensitive information, executing commands, evading detection, etc.
            There are two distinct behaviors that can introduce access control weaknesses:
               
                  Specification: incorrect privileges, permissions, ownership, etc. are explicitly specified for either the user or the resource (for example, setting a password file to be world-writable, or giving administrator capabilities to a guest user). This action could be performed by the program or the administrator.
                  Enforcement: the mechanism contains errors that prevent it from properly enforcing the specified access control requirements (e.g., allowing the user to specify their own privileges, or allowing a syntactically-incorrect ACL to produce insecure settings). This problem occurs within the program itself, in that it does not actually enforce the intended security policy that the administrator specifies.
               
         
		 

Fix information:
repo_url: https://github.com/getsentry/sentry
commit_details: repo_url: https://github.com/getsentry/sentry
author: Alexander Tarasov
author_date: 2023-07-31 19:04:52+02:00
msg: SessionNoAuthTokenAuthentication custom authenticator (#53850)

<!-- Describe your PR here. -->



<!--

  Sentry employees and contractors can delete or ignore the following.

-->

### Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated
in the State of Delaware in 2015 as Functional Software, Inc. and is
gonna need some rights from me in order to utilize my contributions in
this here PR. So here's the deal: I retain all rights, title and
interest in and to my contributions, and by keeping this boilerplate
intact I confirm that Sentry can use, modify, copy, and redistribute my
contributions, under Sentry's choice of terms.
repository: repo_url: https://github.com/getsentry/sentry
repo_name: getsentry/sentry
description: Developer-first error tracking and performance monitoring
repo_language: Python
owner: getsentry
file_changes: filename: authentication.py
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,11 @@ from django.conf import settings
 from django.contrib.auth.models import AnonymousUser
 from django.utils.crypto import constant_time_compare
 from django.utils.encoding import force_str
-from rest_framework.authentication import BasicAuthentication, get_authorization_header
+from rest_framework.authentication import (
+    BasicAuthentication,
+    SessionAuthentication,
+    get_authorization_header,
+)
 from rest_framework.exceptions import AuthenticationFailed
 from rest_framework.request import Request
 from sentry_relay import UnpackError
@@ -157,6 +161,14 @@ class ApiKeyAuthentication(QuietBasicAuthentication):
         return (AnonymousUser(), key)
 
 
+class SessionNoAuthTokenAuthentication(SessionAuthentication):
+    def authenticate(self, request: Request):
+        auth = get_authorization_header(request)
+        if auth:
+            return None
+        return super().authenticate(request)
+
+
 class ClientIdSecretAuthentication(QuietBasicAuthentication):
     """
     Authenticates a Sentry Application using its Client ID and Secret

programming_language: Python
filename: api_tokens.py
change_type: ModificationType.MODIFY
diff: @@ -2,12 +2,12 @@ from django.conf import settings
 from django.utils.decorators import method_decorator
 from django.views.decorators.cache import never_cache
 from rest_framework import serializers
-from rest_framework.authentication import SessionAuthentication
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.request import Request
 from rest_framework.response import Response
 
 from sentry import analytics
+from sentry.api.authentication import SessionNoAuthTokenAuthentication
 from sentry.api.base import Endpoint, control_silo_endpoint
 from sentry.api.fields import MultipleChoiceField
 from sentry.api.serializers import serialize
@@ -22,7 +22,7 @@ class ApiTokenSerializer(serializers.Serializer):
 
 @control_silo_endpoint
 class ApiTokensEndpoint(Endpoint):
-    authentication_classes = (SessionAuthentication,)
+    authentication_classes = (SessionNoAuthTokenAuthentication,)
     permission_classes = (IsAuthenticated,)
 
     @method_decorator(never_cache)

programming_language: Python
filename: test_api_tokens.py
change_type: ModificationType.MODIFY
diff: @@ -31,6 +31,13 @@ class ApiTokensListTest(APITestCase):
             == "max-age=0, no-cache, no-store, must-revalidate, private"
         )
 
+    def test_deny_token_access(self):
+        token = ApiToken.objects.create(user=self.user, scope_list=[])
+
+        url = reverse("sentry-api-0-api-tokens")
+        response = self.client.get(url, format="json", HTTP_AUTHORIZATION=f"Bearer {token.token}")
+        assert response.status_code == 403, response.content
+
 
 @control_silo_test(stable=True)
 class ApiTokensCreateTest(APITestCase):

programming_language: Python