{"id":"CVE-2026-58273","aliases":["EUVD-2026-117819","GHSA-5j9r-fx4x-rpvg"],"status":"published","title":"Rucio: Authorisation Bypass on REST API endpoint for signing URLs","description":"Rucio is a scientific data management framework. From 1.16.1 until 35.9.0, 38.6.0, and 41.0.0rc1, the /credentials/signurl endpoint reads X-Rucio-Account instead of the account stored in request.environ['issuer'] by token authentication. Any authenticated low-privilege user can supply root as the header value and pass the root-only get_signed_url permission check. The server then returns a presigned read, write, or delete URL for an attacker-selected object using stored S3, GCS, or Swift credentials, and X-Rucio-VO can select another tenant in multi-VO deployments. This permits data disclosure, modification, deletion, and cross-tenant access on cloud-backed RSEs. This issue is fixed in versions 35.9.0, 38.6.0, and 41.0.0rc1.","severity":{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00557,"percentile":0.4691,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-639"],"published":"2026-10-28T19:50:05Z","modified":"2026-11-06T17:38:26Z","fixes":[{"ecosystem":"PyPI","package":"rucio","fixed_in":["35.9.0","38.6.0","41.0.0rc1"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"rucio","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.16.1"},{"fixed":"35.9.0"}]}]},{"source":"ghsa","ecosystem":"PyPI","package":"rucio","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"36.0.0rc1"},{"fixed":"38.6.0"}]}]},{"source":"ghsa","ecosystem":"PyPI","package":"rucio","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"39.0.0rc1"},{"fixed":"41.0.0rc1"}]}]},{"source":"osv","ecosystem":"PyPI","package":"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"]},{"source":"osv","ecosystem":"PyPI","package":"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"]},{"source":"osv","ecosystem":"PyPI","package":"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"]}],"products":[{"vendor":"rucio","product":"rucio"}],"references":[{"url":"https://github.com/rucio/rucio/security/advisories/GHSA-5j9r-fx4x-rpvg","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/rucio/rucio/commit/64c0e6a6a93ae88427ff3b7d77532ce08c8a69ec","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/rucio/rucio/commit/8bdc38994a8766658d0314d36afc104c9242c96f","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/rucio/rucio/commit/8d9ba31514e6f5dca32b8c1db4738582cb37ec3a","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/rucio/rucio/releases/tag/35.9.0","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/rucio/rucio/releases/tag/38.6.0","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/rucio/rucio/releases/tag/41.0.0rc1","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-5j9r-fx4x-rpvg"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/58xxx/CVE-2026-58273.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-58273","tags":["advisory"]},{"url":"https://github.com/rucio/rucio","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-58273","title":"Rucio: Authorisation Bypass on REST API endpoint for signing URLs","description":"Rucio is a scientific data management framework. From 1.16.1 until 35.9.0, 38.6.0, and 41.0.0rc1, the /credentials/signurl endpoint reads X-Rucio-Account instead of the account stored in request.environ['issuer'] by token authentication. Any authenticated low-privilege user can supply root as the header value and pass the root-only get_signed_url permission check. The server then returns a presigned read, write, or delete URL for an attacker-selected object using stored S3, GCS, or Swift credentials, and X-Rucio-VO can select another tenant in multi-VO deployments. This permits data disclosure, modification, deletion, and cross-tenant access on cloud-backed RSEs. This issue is fixed in versions 35.9.0, 38.6.0, and 41.0.0rc1.","status":"published","published":"2026-11-05T21:56:20.677Z","modified":"2026-11-06T17:38:26.076Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high","assessor":"cna"}],"cwes":["CWE-639"],"updated_at":"2026-11-06T18:59:08.885567Z"}],"euvd":[{"id":"EUVD-2026-117819","description":"Rucio is a scientific data management framework. From 1.16.1 until 35.9.0, 38.6.0, and 41.0.0rc1, the /credentials/signurl endpoint reads X-Rucio-Account instead of the account stored in request.environ['issuer'] by token authentication. Any authenticated low-privilege user can supply root as the header value and pass the root-only get_signed_url permission check. The server then returns a presigned read, write, or delete URL for an attacker-selected object using stored S3, GCS, or Swift credentials, and X-Rucio-VO can select another tenant in multi-VO deployments. This permits data disclosure, modification, deletion, and cross-tenant access on cloud-backed RSEs. This issue is fixed in versions 35.9.0, 38.6.0, and 41.0.0rc1.","status":"published","published":"2026-11-05T21:56:20Z","modified":"2026-11-06T17:38:26Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"updated_at":"2026-11-06T19:05:53.354277Z"}],"ghsa":[{"id":"GHSA-5j9r-fx4x-rpvg","title":"rucio has an Authorization Bypass on REST API endpoint for signing URLs","description":"## 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\u0026svc=s3\u0026op=delete\u0026url=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.","status":"published","published":"2026-10-28T19:50:05Z","modified":"2026-10-28T19:50:05Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T14:41:39.926569Z"}],"nvd":[{"id":"CVE-2026-58273","description":"Rucio is a scientific data management framework. From 1.16.1 until 35.9.0, 38.6.0, and 41.0.0rc1, the /credentials/signurl endpoint reads X-Rucio-Account instead of the account stored in request.environ['issuer'] by token authentication. Any authenticated low-privilege user can supply root as the header value and pass the root-only get_signed_url permission check. The server then returns a presigned read, write, or delete URL for an attacker-selected object using stored S3, GCS, or Swift credentials, and X-Rucio-VO can select another tenant in multi-VO deployments. This permits data disclosure, modification, deletion, and cross-tenant access on cloud-backed RSEs. This issue is fixed in versions 35.9.0, 38.6.0, and 41.0.0rc1.","status":"published","published":"2026-11-05T22:18:49.23Z","modified":"2026-11-06T18:18:14.843Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-639"],"updated_at":"2026-11-06T20:01:16.80823Z"}],"osv":[{"id":"CVE-2026-58273","title":"Rucio: Authorisation Bypass on REST API endpoint for signing URLs","description":"Rucio is a scientific data management framework. From 1.16.1 until 35.9.0, 38.6.0, and 41.0.0rc1, the /credentials/signurl endpoint reads X-Rucio-Account instead of the account stored in request.environ['issuer'] by token authentication. Any authenticated low-privilege user can supply root as the header value and pass the root-only get_signed_url permission check. The server then returns a presigned read, write, or delete URL for an attacker-selected object using stored S3, GCS, or Swift credentials, and X-Rucio-VO can select another tenant in multi-VO deployments. This permits data disclosure, modification, deletion, and cross-tenant access on cloud-backed RSEs. This issue is fixed in versions 35.9.0, 38.6.0, and 41.0.0rc1.","status":"published","published":"2026-11-05T21:56:20.677Z","modified":"2026-11-08T02:31:10.419120273Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-639"],"updated_at":"2026-11-07T03:08:07.940184Z"},{"id":"GHSA-5j9r-fx4x-rpvg","title":"rucio has an Authorization Bypass on REST API endpoint for signing URLs","description":"## 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\u0026svc=s3\u0026op=delete\u0026url=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.","status":"published","published":"2026-10-28T19:50:05Z","modified":"2026-10-28T20:00:03.935383872Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T15:46:38.553953Z"}]}}
