PT-2026-130609

CVE-2026-58273

·

تاريخ النشر

2026-10-28

·

تاريخ التحديث

2026-10-28

الوصف من

CVSS 3.1
8.8
عالية
المقاييسAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Summary

The /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.

Details

SignURL.get() takes the authorization principal from a request header and never uses the token-authenticated identity:
python
# lib/rucio/web/rest/flaskapi/v1/credentials.py
account = request.headers.get('X-Rucio-Account', default=None)   # L165 (attacker-controlled)
...
result = get signed url(account, rse=rse, service=service,     # L194
            operation=operation, url=url, lifetime=lifetime, vo=vo)
The gateway then uses that header value as the issuer for the permission check:
python
# lib/rucio/gateway/credential.py
auth result = permission.has permission(issuer=account, vo=vo,   # L57
                    action='get signed url', kwargs=kwargs, ...)
and the permission function is root-only:
python
# lib/rucio/core/permission/generic.py
def perm get signed url(issuer, kwargs, session):
  return is root(issuer)    # is root(x) -> x.external == 'root'
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:
python
# lib/rucio/web/rest/flaskapi/v1/common.py (request auth env)
flask.request.environ['issuer'] = auth.get('account')       # L168 (the real account; unused by SignURL)
This 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.

Proof of concept

Request (any valid token + spoofed account):
GET /credentials/signurl?rse=<cloudRSE>&svc=s3&op=delete&url=https://victim-bucket.s3.amazonaws.com/important/dataset.root HTTP/1.1
X-Rucio-Auth-Token: <any valid token, even a low-privilege account>
X-Rucio-Account: root
Accept: application/octet-stream
Response: 200 OK with a presigned DELETE URL the attacker replays directly against the storage backend.

Impact

A 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.

Credit

Reported by seankohjs. Found via static analysis + manual verification.
قابلية الاستغلال
إصلاح
التأثيرات
المورّدون
المنتجات المتأثرة
المعرّفات ذات الصلة
GHSA-5J9R-FX4X-RPVG
وجدت خطأً في الوصف؟ لديك ما تضيفه؟ لا تتردد في مراسلتنا 👾