{"id":"GHSA-5j9r-fx4x-rpvg","summary":"rucio has an Authorization Bypass on REST API endpoint for signing URLs","details":"## Summary\nThe `/credentials/signurl` endpoint decides authorization from the client-supplied `X-Rucio-Account` HTTP header instead of the authenticated token's account. Any client holding **any** valid auth token (even a low-privilege account they legitimately own) can send `X-Rucio-Account: root` to pass the root-only permission check and have the Rucio server mint a presigned **read / write / delete** URL for an **arbitrary** object, signed with the RSE's stored S3 / GCS / Swift credentials.\n\n## Details\n`SignURL.get()` takes the authorization principal from a request header and\nnever uses the token-authenticated identity:\n\n```python\n# lib/rucio/web/rest/flaskapi/v1/credentials.py\naccount = request.headers.get('X-Rucio-Account', default=None)     # L165  (attacker-controlled)\n...\nresult = get_signed_url(account, rse=rse, service=service,         # L194\n                        operation=operation, url=url, lifetime=lifetime, vo=vo)\n```\n\nThe gateway then uses that header value as the `issuer` for the permission check:\n\n```python\n# lib/rucio/gateway/credential.py\nauth_result = permission.has_permission(issuer=account, vo=vo,     # L57\n                                        action='get_signed_url', kwargs=kwargs, ...)\n```\n\nand the permission function is root-only:\n\n```python\n# lib/rucio/core/permission/generic.py\ndef perm_get_signed_url(issuer, kwargs, session):\n    return _is_root(issuer)        # _is_root(x) -\u003e x.external == 'root'\n```\n\n`InternalAccount('root', vo).external == 'root'` unconditionally, so the gate passes whenever the header is `root`. The endpoint is on `AuthenticatedBlueprint`, whose `before_request` hook validates the token and sets `request.environ['issuer']` **from the token** — but nothing ever compares `X-Rucio-Account` to it:\n\n```python\n# lib/rucio/web/rest/flaskapi/v1/common.py  (request_auth_env)\nflask.request.environ['issuer'] = auth.get('account')             # L168  (the real account; unused by SignURL)\n```\n\nThis is anomalous: every other authenticated handler derives the account from `request.environ['issuer']`; `credentials.py` is the only one that trusts the header. Additionally, `core.credential.get_signed_url` never checks that the supplied `url`'s host/bucket belongs to the target RSE, so even a legitimate `root` caller gets an arbitrary-object signing oracle. In multi-VO the VO is also read from the `X-Rucio-VO` header, allowing cross-tenant targeting.\n\n## Proof of concept\nRequest (any valid token + spoofed account):\n```\nGET /credentials/signurl?rse=\u003ccloudRSE\u003e&svc=s3&op=delete&url=https://victim-bucket.s3.amazonaws.com/important/dataset.root HTTP/1.1\nX-Rucio-Auth-Token: \u003cany valid token, even a low-privilege account\u003e\nX-Rucio-Account: root\nAccept: application/octet-stream\n```\nResponse: `200 OK` with a presigned DELETE URL the attacker replays directly against the storage backend.\n\n## Impact\nA low-privilege Rucio user can read, overwrite, and delete arbitrary objects on any cloud-backed (S3/GCS/Swift) RSE using the server's stored credentials — enabling data exfiltration, tampering, and destruction, and cross-tenant access in multi-VO deployments.\n\n## Credit\nReported by seankohjs. Found via static analysis + manual verification.","aliases":["CVE-2026-58273"],"modified":"2026-10-28T20:00:03.935383872Z","published":"2026-10-28T19:50:05Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-10-28T19:50:05Z","nvd_published_at":null,"cwe_ids":["CWE-639"],"severity":"HIGH"},"references":[{"type":"WEB","url":"https://github.com/rucio/rucio/security/advisories/GHSA-5j9r-fx4x-rpvg"},{"type":"WEB","url":"https://github.com/rucio/rucio/commit/64c0e6a6a93ae88427ff3b7d77532ce08c8a69ec"},{"type":"WEB","url":"https://github.com/rucio/rucio/commit/8bdc38994a8766658d0314d36afc104c9242c96f"},{"type":"WEB","url":"https://github.com/rucio/rucio/commit/8d9ba31514e6f5dca32b8c1db4738582cb37ec3a"},{"type":"PACKAGE","url":"https://github.com/rucio/rucio"},{"type":"WEB","url":"https://github.com/rucio/rucio/releases/tag/35.9.0"},{"type":"WEB","url":"https://github.com/rucio/rucio/releases/tag/38.6.0"},{"type":"WEB","url":"https://github.com/rucio/rucio/releases/tag/41.0.0rc1"}],"affected":[{"package":{"name":"rucio","ecosystem":"PyPI","purl":"pkg:pypi/rucio"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.16.1"},{"fixed":"35.9.0"}]}],"versions":["1.16.1","1.16.2","1.16.3","1.17.0","1.17.1","1.17.2","1.17.2.post1","1.17.5","1.17.6","1.17.6.post1","1.17.6.post2","1.17.7","1.17.8","1.17.8.post1","1.17.8.post2","1.18.0","1.18.1","1.18.3","1.18.4","1.18.5","1.18.5.post1","1.18.6","1.18.6.post1","1.18.7","1.18.8","1.18.8.post1","1.18.9","1.19.0.post2","1.19.1","1.19.3","1.19.4","1.19.4.post1","1.19.4.post2","1.19.5","1.19.6","1.19.7","1.19.7.post1","1.19.8","1.20.0","1.20.0rc1","1.20.1","1.20.1.post1","1.20.10","1.20.11","1.20.12","1.20.13","1.20.14","1.20.15","1.20.16","1.20.2","1.20.3","1.20.3rc1","1.20.3rc2","1.20.4","1.20.4.post1","1.20.4.post2","1.20.4rc1","1.20.4rc2","1.20.4rc3","1.20.5","1.20.6","1.20.7","1.20.8","1.20.9","1.21.0","1.21.0.dev1","1.21.0.post1","1.21.0.post2","1.21.0rc1","1.21.0rc2","1.21.0rc3","1.21.1","1.21.10","1.21.10.post1","1.21.12","1.21.2","1.21.3","1.21.4","1.21.5","1.21.6","1.21.7","1.21.8","1.22.0","1.22.0.dev1","1.22.0.dev2","1.22.0.dev3","1.22.0rc1","1.22.0rc2","1.22.1","1.22.2","1.22.3","1.22.3.post1","1.22.4","1.22.4.dev1","1.22.6","1.22.6.post1","1.22.7","1.22.8","1.22.8.post1","1.23.0","1.23.0rc1","1.23.0rc2","1.23.1","1.23.10","1.23.11","1.23.11.post1","1.23.11.post2","1.23.11.post3","1.23.11.post4","1.23.12","1.23.13","1.23.14","1.23.15","1.23.17","1.23.18","1.23.19","1.23.2","1.23.2.post1","1.23.2.post2","1.23.20","1.23.4","1.23.5","1.23.5.post1","1.23.6","1.23.6.post1","1.23.7","1.23.7.post1","1.23.8","1.23.9","1.23.9.post1","1.23.9.post2","1.23.9.post3","1.23.9.post4","1.24.0","1.24.0rc1","1.24.1","1.24.1.post1","1.24.1.post2","1.24.1.post3","1.24.1.post4","1.24.2","1.24.2.post1","1.24.3","1.24.3.post1","1.24.4","1.24.5","1.24.5.post1","1.25.0","1.25.0rc1","1.25.0rc2","1.25.1","1.25.1.post1","1.25.2","1.25.3","1.25.3.post1","1.25.3.post2","1.25.4","1.25.4.post1","1.25.5","1.25.6","1.25.7","1.26.0","1.26.0rc1","1.26.0rc2","1.26.1","1.26.1.post1","1.26.10","1.26.11","1.26.12","1.26.13","1.26.14","1.26.15","1.26.16","1.26.17","1.26.18","1.26.2","1.26.4","1.26.5","1.26.6","1.26.7","1.26.7.post1","1.26.8","1.26.8.post1","1.26.9","1.27.0","1.27.0.post1","1.27.0rc1","1.27.0rc2","1.27.1","1.27.10","1.27.11","1.27.12","1.27.2","1.27.3","1.27.4","1.27.4.post1","1.27.5","1.27.7","1.27.9","1.28.0","1.28.0rc1","1.28.0rc2","1.28.1","1.28.2","1.28.3","1.28.4","1.28.5","1.28.6","1.28.7","1.29.0","1.29.0rc1","1.29.0rc2","1.29.1","1.29.10","1.29.11","1.29.12","1.29.13","1.29.14","1.29.15","1.29.16","1.29.17","1.29.18","1.29.19","1.29.2","1.29.2.post1","1.29.2.post2","1.29.3","1.29.3.post1","1.29.4","1.29.5","1.29.6","1.29.7","1.29.7.post1","1.29.8","1.29.9","1.30.0","1.30.0rc1","1.30.0rc2","1.30.0rc3","1.30.1","1.30.2","1.30.3","1.30.4","1.30.5","1.30.6","1.30.7","1.30.8","1.31.0","1.31.0rc1","1.31.0rc2","1.31.0rc3","1.31.1","1.31.2","1.31.3","1.31.4","1.31.5","1.31.6","1.31.7","32.0.0","32.0.0rc1","32.0.0rc2","32.1.0","32.2.0","32.3.0","32.3.1","32.4.0","32.5.0","32.5.0.post1","32.5.1","32.6.0","32.6.0.post1","32.7.0","32.8.0","32.8.1","32.8.2","32.8.3","32.8.4","32.8.5","32.8.6","33.0.0","33.0.0rc1","33.0.0rc2","33.0.0rc3","33.1.0","33.2.0","33.2.1","33.3.0","33.4.0","33.4.0.post1","33.5.0","33.6.0","33.6.1","34.0.0","34.0.0rc1","34.0.0rc2","34.1.0","34.2.0","34.3.0","34.4.0","34.4.1","34.4.2","34.4.3","34.5.0","34.6.0","35.0.0","35.0.0rc1","35.0.0rc2","35.0.1","35.1.0","35.1.1","35.2.0","35.2.1","35.3.0","35.4.0","35.4.1","35.5.0","35.6.0","35.6.1","35.7.0","35.8.0","35.8.1","35.8.2","35.8.3","35.8.4","35.8.5"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-5j9r-fx4x-rpvg/GHSA-5j9r-fx4x-rpvg.json"}},{"package":{"name":"rucio","ecosystem":"PyPI","purl":"pkg:pypi/rucio"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"36.0.0rc1"},{"fixed":"38.6.0"}]}],"versions":["36.0.0","36.0.0.post1","36.0.0.post2","36.0.0rc1","36.0.0rc2","36.0.0rc3","36.0.0rc4","36.0.0rc5","36.1.0","36.2.0","36.3.0","36.4.0","36.5.0","37.0.0","37.0.0rc1","37.0.0rc2","37.0.0rc3","37.0.0rc4","37.1.0","37.1.0.post1","37.2.0","37.3.0","37.4.0","37.5.0","37.6.0","37.7.0","37.7.1","38.0.0","38.0.0rc1","38.0.0rc2","38.0.0rc3","38.1.0","38.2.0","38.3.0","38.4.0","38.5.0","38.5.1","38.5.2","38.5.3","38.5.4","38.5.5"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-5j9r-fx4x-rpvg/GHSA-5j9r-fx4x-rpvg.json"}},{"package":{"name":"rucio","ecosystem":"PyPI","purl":"pkg:pypi/rucio"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"39.0.0rc1"},{"fixed":"41.0.0rc1"}]}],"versions":["39.0.0","39.0.0rc1","39.0.0rc2","39.1.0","39.2.0","39.3.0","39.3.1","39.4.0","39.4.1","39.4.2","40.0.0","40.0.0rc1","40.1.0","40.1.1","40.1.2","40.2.0","40.3.0","40.4.0","40.4.1","40.4.2"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-5j9r-fx4x-rpvg/GHSA-5j9r-fx4x-rpvg.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}]}