CVE-2023-36829: Incorrect Comparison (CWE-697)
value: Sentry is an error tracking and performance monitoring platform. Starting in version 23.6.0 and prior to version 23.6.2, the Sentry API incorrectly returns the `access-control-allow-credentials: true` HTTP header if the `Origin` request header ends with the `system.base-hostname` option of Sentry installation. This only affects installations that have `system.base-hostname` option explicitly set, as it is empty by default. Impact is limited since recent versions of major browsers have cross-site cookie blocking enabled by default. However, this flaw could allow other multi-step attacks. The patch has been released in Sentry 23.6.2.
CWE description: The product compares two entities in a security-relevant context, but the comparison is incorrect, which may lead to resultant weaknesses.
CVSS v3 base score: 5.4
Published: 2023-07-06T23:15Z
Repository: https://github.com/getsentry/sentry
Language: Python
Files changed: src/sentry/api/base.py, tests/sentry/api/test_base.py
Commit date: 2023-07-05 16:12:27 +0000
Commit message: Fix GHSA-4xqm-4p72-87h6 (#52276)


Diff: diff --git a/src/sentry/api/base.py b/src/sentry/api/base.py
index 462ef2abfa6..8d2a15b2225 100644
--- a/src/sentry/api/base.py
+++ b/src/sentry/api/base.py
@@ -119,11 +119,11 @@ def allow_cors_options(func):
         # If the requesting origin is a subdomain of
         # the application's base-hostname we should allow cookies
         # to be sent.
         basehost = options.get("system.base-hostname")
         if basehost and origin:
-            if origin.endswith(basehost):
+            if origin.endswith(("://" + basehost, "." + basehost)):
                 response["Access-Control-Allow-Credentials"] = "true"
 
         return response
 
     return allow_cors_options_wrapper
diff --git a/tests/sentry/api/test_base.py b/tests/sentry/api/test_base.py
index 90b96ea35ad..a2a12497e0a 100644
--- a/tests/sentry/api/test_base.py
+++ b/tests/sentry/api/test_base.py
@@ -160,19 +160,20 @@ class EndpointTest(APITestCase):
     @override_options({"system.base-hostname": "acme.com"})
     def test_allow_credentials_incorrect(self):
         org = self.create_organization()
         apikey = ApiKey.objects.create(organization_id=org.id, allowed_origins="*")
 
-        request = self.make_request(method="GET")
-        request.META["HTTP_ORIGIN"] = "http://acme.example.com"
-        request.META["HTTP_AUTHORIZATION"] = b"Basic " + base64.b64encode(
-            apikey.key.encode("utf-8")
-        )
+        for http_origin in ["http://acme.example.com", "http://fakeacme.com"]:
+            request = self.make_request(method="GET")
+            request.META["HTTP_ORIGIN"] = http_origin
+            request.META["HTTP_AUTHORIZATION"] = b"Basic " + base64.b64encode(
+                apikey.key.encode("utf-8")
+            )
 
-        response = _dummy_endpoint(request)
-        response.render()
-        assert "Access-Control-Allow-Credentials" not in response
+            response = _dummy_endpoint(request)
+            response.render()
+            assert "Access-Control-Allow-Credentials" not in response
 
     def test_invalid_cors_without_auth(self):
         request = self.make_request(method="GET")
         request.META["HTTP_ORIGIN"] = "http://example.com"
 

Vulnerable code:             if origin.endswith(basehost):
        request = self.make_request(method="GET")
        request.META["HTTP_ORIGIN"] = "http://acme.example.com"
        request.META["HTTP_AUTHORIZATION"] = b"Basic " + base64.b64encode(
            apikey.key.encode("utf-8")
        )
        response = _dummy_endpoint(request)
        response.render()
        assert "Access-Control-Allow-Credentials" not in response
Fixed code:             if origin.endswith(("://" + basehost, "." + basehost)):
        for http_origin in ["http://acme.example.com", "http://fakeacme.com"]:
            request = self.make_request(method="GET")
            request.META["HTTP_ORIGIN"] = http_origin
            request.META["HTTP_AUTHORIZATION"] = b"Basic " + base64.b64encode(
                apikey.key.encode("utf-8")
            )
            response = _dummy_endpoint(request)
            response.render()
            assert "Access-Control-Allow-Credentials" not in response