{"id":"CVE-2026-58273","kind":"vulnerability","aliases":["CVE-2026-58273","GHSA-5j9r-fx4x-rpvg"],"related":[],"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) -> 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=<cloudRSE>&svc=s3&op=delete&url=https://victim-bucket.s3.amazonaws.com/important/dataset.root HTTP/1.1\nX-Rucio-Auth-Token: <any valid token, even a low-privilege account>\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.","summary":"Rucio's /credentials/signurl endpoint takes the account from a client-supplied X-Rucio-Account header instead of the authenticated token, so setting it to root passes the root-only check. An attacker with any valid token can get presigned read, write or delete URLs for arbitrary objects on cloud-backed RSEs, signed with the server's stored S3, GCS or Swift credentials.","severity":"high","cvss":8.8,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"rucio","fixed":["35.9.0"],"affected":">= 1.16.1, < 35.9.0","ecosystem":"pypi"},{"name":"rucio","fixed":["38.6.0"],"affected":">= 36.0.0rc1, < 38.6.0","ecosystem":"pypi"},{"name":"rucio","fixed":["41.0.0rc1"],"affected":">= 39.0.0rc1, < 41.0.0rc1","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-639","gcs","multi-vo","package","rucio","s3","self-hosted","swift"],"not_affected_if":["api-restricted-to-trusted-users","no-cloud-rse","versions-outside-range"],"how_to_check":"- Run `pip show rucio` on the Rucio server and compare the version against the fixed releases.\n- Check whether any RSE uses S3, GCS or Swift storage, since the flaw only affects cloud-backed RSEs.\n- Check web server or access logs for requests to `/credentials/signurl` that carry an `X-Rucio-Account` header.","fix":"- Upgrade rucio to `35.9.0` on the 35.x line, `38.6.0` on the 38.x line, or `41.0.0rc1` on the 39 to 41 line.\n- Rotate the S3, GCS and Swift credentials stored for affected RSEs if signing requests were seen from untrusted clients.\n- Block or strip the `X-Rucio-Account` header at the reverse proxy until the upgrade is in place.","status":"open","published_at":"2026-10-28T19:50:05+00:00","first_seen_at":"2026-10-28T19:51:20+00:00","triaged_at":"2026-10-28T19:52:21+00:00","updated_at":"2026-10-28T19:52:21+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-10-28T19:52:21Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade rucio to `35.9.0` on the 35.x line, `38.6.0` on the 38.x line, or `41.0.0rc1` on the 39 to 41 line.\n- Rotate the S3, GCS and Swift credentials stored for affected RSEs if signing requests were seen from untrusted clients.\n- Block or strip the `X-Rucio-Account` header at the reverse proxy until the upgrade is in place.","reach":"internet","title":"Rucio: any valid token can get presigned storage URLs as root","impact":"high","summary":"Rucio's /credentials/signurl endpoint takes the account from a client-supplied X-Rucio-Account header instead of the authenticated token, so setting it to root passes the root-only check. An attacker with any valid token can get presigned read, write or delete URLs for arbitrary objects on cloud-backed RSEs, signed with the server's stored S3, GCS or Swift credentials.","category":"package","nicknames":[],"who_cares":["rucio","self-hosted","s3","gcs","swift","multi-vo"],"confidence":0.85,"needs_auth":"yes","worst_case":"Attacker reads, overwrites or deletes arbitrary cloud storage objects using server credentials","analyst_note":"","how_to_check":"- Run `pip show rucio` on the Rucio server and compare the version against the fixed releases.\n- Check whether any RSE uses S3, GCS or Swift storage, since the flaw only affects cloud-backed RSEs.\n- Check web server or access logs for requests to `/credentials/signurl` that carry an `X-Rucio-Account` header.","needs_analyst":false,"not_affected_if":["no-cloud-rse","api-restricted-to-trusted-users","versions-outside-range"],"who_is_affected":"Rucio deployments that expose the REST API to users holding any valid token and that have at least one S3, GCS or Swift RSE. Deployments with only non-cloud RSEs, or with the API reachable only by fully trusted operators, are less exposed. Multi-VO deployments also face cross-tenant access through the X-Rucio-VO header.","user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.000842,"input_tokens":4571,"output_tokens":770,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-2026':14A,22A '-58273':15A,23A '-639':28B '/credentials/signurl':42B,108C,438C '/important/dataset.root':447C '200':471C '5j9r':18A 'accept':468C 'access':523C 'account':46B,55B,120C,129C,142C,151C,207C,212C,223C,258C,336C,347C,351C,364C,436C,462C,466C 'action':262C 'addit':377C 'allow':423C 'also':414C 'analysi':536C 'anomal':357C 'api':8A 'application/octet-stream':469C 'arbitrari':88B,175C,403C,498C 'arbitrary-object':402C 'attack':74B,217C,479C 'attacker-control':216C 'auth':135C,253C,342C,452C 'auth.get':346C 'authent':60B,126C,203C,360C 'authenticatedblueprint':314C 'author':4A,111C,191C 'back':93B,504C 'backend':485C 'belong':389C 'bypass':5A 'caller':399C 'check':72B,160C,250C,382C 'client':50B,115C,131C 'client-suppli':49B,114C 'cloud':92B,503C 'cloud-back':91B,502C 'compar':332C 'concept':430C 'control':218C 'core.credential.get':378C 'credenti':105B,186C,512C 'credentials.py':368C 'credit':529C 'cross':425C,521C 'cross-ten':424C,520C 'cve':13A,21A 'cwe':27B 'data':514C 'decid':110C 'def':278C 'default':213C 'delet':85B,171C,443C,476C,497C 'deploy':528C 'deriv':362C 'destruct':518C 'detail':187C 'direct':481C 'enabl':513C 'endpoint':9A,43B,109C,311C 'env':343C 'even':137C,395C,457C 'ever':331C 'everi':358C 'exfiltr':515C 'extern':298C 'flask.request.environ':344C 'found':533C 'function':271C 'fx4x':19A 'gate':303C 'gateway':238C 'gcs':29B,102B,184C 'get':80B,220C,263C,280C,400C,437C 'ghsa':17A 'ghsa-5j9r-fx4x-rpvg':16A 'handler':361C 'header':56B,122C,196C,242C,307C,376C,422C 'hold':132C 'hook':318C 'host':38B 'host/bucket':388C 'http':121C 'http/1.1':448C 'ident':204C 'impact':486C 'instead':57B,123C 'internalaccount':295C 'issuer':246C,257C,283C,289C,325C,345C,367C 'kwarg':266C,267C,284C 'l165':215C 'l168':348C 'l194':228C 'l57':261C 'legitim':144C,397C 'lib/rucio/core/permission/generic.py':277C 'lib/rucio/gateway/credential.py':252C 'lib/rucio/web/rest/flaskapi/v1/common.py':340C 'lib/rucio/web/rest/flaskapi/v1/credentials.py':206C 'lifetim':233C,234C 'low':140C,460C,489C 'low-privileg':139C,459C,488C 'manual':537C 'mint':166C 'multi':31B,409C,526C 'multi-vo':30B,408C,525C 'never':198C,381C 'none':214C 'noth':330C 'object':89B,176C,404C,499C 'ok':472C 'one':372C 'op':442C 'oper':229C,230C 'oracl':406C 'overwrit':495C 'packag':33B 'pass':67B,154C,304C 'perm':279C 'permiss':159C,249C,256C,270C 'permission.has':255C 'presign':81B,168C,475C 'princip':192C 'privileg':141C,461C,490C 'proof':428C 'python':205C,251C,276C,339C 'read':82B,169C,415C,494C 'real':350C 'replay':480C 'report':530C 'request':195C,317C,341C,431C 'request.environ':324C,366C 'request.headers.get':208C 'respons':470C 'rest':7A 'result':219C,254C 'return':286C 'root':66B,70B,152C,157C,274C,288C,291C,294C,296C,299C,309C,398C,467C 'root-on':69B,156C,273C 'rpvg':20A 'rse':180C,224C,225C,393C,439C,506C 'rses':94B 'rucio':1A,24A,25A,26A,34B,40B,54B,119C,150C,164C,211C,335C,420C,451C,465C,491C 's3':35B,101B,183C,441C 's3/gcs/swift':505C 'seankohj':532C 'self':37B 'self-host':36B 'send':147C 'server':98B,165C,509C 'servic':226C,227C 'session':285C 'set':63B,323C 'sign':11A,95B,177C,221C,264C,281C,379C,405C 'signurl':354C 'signurl.get':188C 'spoof':435C 'static':535C 'storag':484C 'store':100B,182C,511C 'summari':106C 'suppli':51B,116C,385C 'svc':440C 'swift':39B,104B,185C 'take':44B,189C 'tamper':516C 'target':392C,427C 'tenant':426C,522C 'token':61B,78B,127C,136C,202C,321C,328C,434C,453C,456C 'token-authent':201C 'trust':374C 'uncondit':300C 'unus':352C 'url':12A,86B,172C,222C,231C,232C,265C,282C,380C,386C,444C,477C 'use':199C,240C,507C 'user':492C 'valid':77B,134C,319C,433C,455C 'valu':243C 'verif':538C 'via':534C 'victim-bucket.s3.amazonaws.com':446C 'victim-bucket.s3.amazonaws.com/important/dataset.root':445C 'vo':32B,235C,236C,259C,260C,297C,410C,412C,421C,527C 'whenev':305C 'whose':315C 'write':83B,170C 'x':53B,118C,149C,210C,292C,334C,419C,450C,464C 'x-rucio-account':52B,117C,148C,209C,333C,463C 'x-rucio-auth-token':449C 'x-rucio-vo':418C 'x.external':293C","nicknames":[],"mitigations":[],"who_affected":"Rucio deployments that expose the REST API to users holding any valid token and that have at least one S3, GCS or Swift RSE. Deployments with only non-cloud RSEs, or with the API reachable only by fully trusted operators, are less exposed. Multi-VO deployments also face cross-tenant access through the X-Rucio-VO header.","tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-28T19:50:05+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-5j9r-fx4x-rpvg","url":"https://github.com/advisories/GHSA-5j9r-fx4x-rpvg","severity":"high","published_at":"2026-10-28T19:50:05+00:00","modified_at":"2026-10-28T19:50:05+00:00","first_seen_at":"2026-10-28T19:51:20+00:00","fetched_at":"2026-10-28T19:51:20+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}