CVE-2023-36826 (Python, published 2023-07-25T19:15Z)
Description: value: Sentry is an error tracking and performance monitoring platform. Starting in version 8.21.0 and prior to version 23.5.2, an authenticated user can download a debug or artifact bundle from arbitrary organizations and projects with a known bundle ID. The user does not need to be a member of the organization or have permissions on the project. A patch was issued in version 23.5.2 to ensure authorization checks are properly scoped on requests to retrieve debug or artifact bundles. Authenticated users who do not have the necessary permissions on the particular project are no longer able to download them. Sentry SaaS users do not need to take any action. Self-Hosted Sentry users should upgrade to version 23.5.2 or higher.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-863 \u2014 Incorrect Authorization
The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Repository: https://github.com/getsentry/sentry
Files changed:
src/sentry/api/endpoints/artifact_lookup.py
src/sentry/api/endpoints/debug_files.py
tests/sentry/api/endpoints/test_project_artifact_lookup.py
Commit message: Restrict file downloads to Project (#49680)

The `debug_files` and `artifact_lookup` endpoint are now restricting
file downloads to the specific project according to the route
parameters.

Vulnerable code:
    File,
    def download_file(self, file_id, project: Project):
            key=f"rl:ArtifactLookupEndpoint:download:{file_id}:{project.id}",
                extra={"project_id": project.id, "file_id": file_id},
        file = File.objects.filter(id=file_id).first()
        if request.GET.get("download") is not None:
                return self.download_file(request.GET.get("download"), project)
                bundle_file_ids.add(file_id)
                bundle_file_ids |= get_legacy_release_bundles(release, dist)
        for file_id in bundle_file_ids:
                    "url": url_constructor.url_for_file_id(file_id),
                    "url": url_constructor.url_for_file_id(release_file.file.id),
def get_legacy_release_bundles(release: Release, dist: Optional[Distribution]):
        ReleaseFile.objects.select_related("file")
        .filter(
        .values_list("file_id", flat=True)
    def url_for_file_id(self, file_id: int) -> str:
        return f"{self.base_url}?download={file_id}"
            return HttpResponse({"Too many download requests"}, status=403)
        debug_file = ProjectDebugFile.objects.filter(id=debug_file_id).first()
                    "content": b"foo",
                    "content": b"bar",

                    "content": b"baz",

                    "content": b"foo",
                    "content": b"foo",
                    "content": b"bar",

                "foo": "foo",
                "bar": "bar",
                    "sha1": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
                    "size": 3,
                    "sha1": "62cdb7020ff920e5aa642c3d4066950dd1f01f4d",
                    "size": 3,
                "bar": "BAR",
                    "sha1": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
                    "size": 3,
                    "sha1": "a5d5c1bba91fdb6c669e1ae0413820885bbfc455",
                    "size": 3,
                "foo": "foo",
                "bar": "bar",
                    "sha1": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
                    "size": 3,
                    "sha1": "62cdb7020ff920e5aa642c3d4066950dd1f01f4d",
                    "size": 3,
                "bar": "BAR",
                    "sha1": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
                    "size": 3,
                    "sha1": "a5d5c1bba91fdb6c669e1ae0413820885bbfc455",
                    "size": 3,
                            "content": b"baz",
                        "content": b"baz",

Fixed code:
    def download_file(self, download_id, project: Project):
        ty, ty_id = download_id.split("/")

            key=f"rl:ArtifactLookupEndpoint:download:{download_id}:{project.id}",
                extra={"project_id": project.id, "file_id": download_id},
        file = None
        if ty == "artifact_bundle":
            file = (
                ArtifactBundle.objects.filter(
                    id=ty_id,
                    projectartifactbundle__project_id=project.id,
                )
                .select_related("file")
                .first()
            )
        elif ty == "release_file":
            # NOTE: `ReleaseFile` does have a `project_id`, but that seems to
            # be always empty, so using the `organization_id` instead.
            file = (
                ReleaseFile.objects.filter(id=ty_id, organization_id=project.organization.id)
                .select_related("file")
                .first()
            )
        file = file.file
        if (download_id := request.GET.get("download")) is not None:
                return self.download_file(download_id, project)
                bundle_file_ids.add(("artifact_bundle", bundle_id, file_id))
                for (releasefile_id, file_id) in get_legacy_release_bundles(release, dist):
                    bundle_file_ids.add(("release_file", releasefile_id, file_id))
        # NOTE: the reason we use the `file_id` as the `id` we return is because
        # downstream symbolicator relies on that for its internal caching.
        # We do not want to hard-refresh those caches quite yet, and the `id`
        # should also be as unique as possible, which the `file_id` is.
        for (ty, ty_id, file_id) in bundle_file_ids:
                    "url": url_constructor.url_for_file_id(ty, ty_id),
                    "url": url_constructor.url_for_file_id("release_file", release_file.id),
        # make sure we have a stable sort order for tests
        found_artifacts.sort(key=lambda x: int(x["id"]))

def get_legacy_release_bundles(
    release: Release, dist: Optional[Distribution]
) -> Set[Tuple[int, int]]:
        ReleaseFile.objects.filter(
        .select_related("file")
        .values_list("id", "file_id")
    def url_for_file_id(self, ty: str, file_id: int) -> str:
        return f"{self.base_url}?download={ty}/{file_id}"
            return HttpResponse({"Too many download requests"}, status=429)
        debug_file = ProjectDebugFile.objects.filter(
            id=debug_file_id, project_id=project.id
        ).first()
                    "content": b"foo_id",
                    "content": b"bar_id",
        ProjectArtifactBundle.objects.create(
            organization_id=self.organization.id,
            project_id=self.project.id,
            artifact_bundle=artifact_bundle_ab,
        )
                    "content": b"baz_id",
        ProjectArtifactBundle.objects.create(
            organization_id=self.organization.id,
            project_id=self.project.id,
            artifact_bundle=artifact_bundle_c,
        )
                    "content": b"foo_url",
                    "content": b"foo_url",
                    "content": b"bar_url",
                "foo": "foo1",
                "bar": "bar1",
                    "sha1": "18a16d4530763ef43321d306c9f6c59ffed33072",
                    "size": 4,
                    "sha1": "763675d6a1d8d0a3a28deca62bb68abd8baf86f3",
                    "size": 4,
                "bar": "BAR1",
                    "sha1": "18a16d4530763ef43321d306c9f6c59ffed33072",
                    "size": 4,
                    "sha1": "7f9353c7b307875542883ba558a1692706fcad33",
                    "size": 4,
                "foo": "foo2",
                "bar": "bar2",
                    "sha1": "aaadd94977b8fbf3f6fb09fc3bbbc9edbdfa8427",
                    "size": 4,
                    "sha1": "033c4846b506a4a48e32cdf54515c91d3499adb3",
                    "size": 4,
                "bar": "BAR2",
                    "sha1": "aaadd94977b8fbf3f6fb09fc3bbbc9edbdfa8427",
                    "size": 4,
                    "sha1": "528c5563f06a1e98954d17d365a219b68dd93baf",
                    "size": 4,
                            "content": b"baz_renew",
                        "content": b"baz_renew",