fix(exiftool): remove System: prefixes

This commit is contained in:
Julien Neuhart
2026-04-13 17:44:58 +02:00
parent 55d19522a8
commit 46e190970f
2 changed files with 39 additions and 8 deletions

View File

@@ -268,15 +268,18 @@ func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *slog.Logger,
} }
// Filter user-supplied metadata to prevent ExifTool pseudo-tags from // Filter user-supplied metadata to prevent ExifTool pseudo-tags from
// triggering dangerous side effects like file renames, moves, or link // triggering dangerous side effects like file renames, moves, link
// creation. Comparison is case-insensitive because ExifTool processes // creation, or permission changes. Comparison is case-insensitive
// tag names case-insensitively. // because ExifTool processes tag names case-insensitively, and group
// prefixes are stripped because ExifTool treats "System:FileName" the
// same as "FileName".
// See https://exiftool.org/TagNames/Extra.html. // See https://exiftool.org/TagNames/Extra.html.
dangerousTags := []string{ dangerousTags := []string{
"FileName", // Writing this triggers a file rename in ExifTool "FileName", // Writing this triggers a file rename in ExifTool
"Directory", // Writing this triggers a file move in ExifTool "Directory", // Writing this triggers a file move in ExifTool
"HardLink", // Writing this creates a hard link in ExifTool "HardLink", // Writing this creates a hard link in ExifTool
"SymLink", // Writing this creates a symbolic link in ExifTool "SymLink", // Writing this creates a symbolic link in ExifTool
"FilePermissions", // Writing this changes the file's permissions
} }
// Reject metadata keys containing characters that could inject ExifTool // Reject metadata keys containing characters that could inject ExifTool
// stdin arguments. ExifTool uses a line-based stdin protocol; a newline // stdin arguments. ExifTool uses a line-based stdin protocol; a newline
@@ -293,8 +296,16 @@ func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *slog.Logger,
} }
for key := range metadata { for key := range metadata {
// Strip ExifTool group prefixes (e.g., "System:FileName" →
// "FileName") before comparing. ExifTool allows leading group
// names separated by colons, and treats the prefixed and bare
// forms identically.
bare := key
if i := strings.LastIndex(key, ":"); i >= 0 {
bare = key[i+1:]
}
for _, tag := range dangerousTags { for _, tag := range dangerousTags {
if strings.EqualFold(key, tag) { if strings.EqualFold(bare, tag) {
delete(metadata, key) delete(metadata, key)
} }
} }

View File

@@ -123,6 +123,26 @@ Feature: /forms/pdfengines/metadata/{write|read}
At least one PDF engine cannot process the requested metadata At least one PDF engine cannot process the requested metadata
""" """
Scenario: POST /forms/pdfengines/metadata/write (Reject Group-Prefixed Dangerous Tag)
# Regression: ExifTool treats "System:FileName" identically to "FileName".
# The dangerous-tag blocklist must strip group prefixes before comparing,
# otherwise the attacker renames/moves files with a single HTTP request.
Given I have a default Gotenberg container
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/write" endpoint with the following form data and header(s):
| files | testdata/page_1.pdf | file |
| metadata | {"System:FileName":"stolen.pdf","System:Directory":"/tmp","Author":"legit"} | field |
| Gotenberg-Output-Filename | foo | header |
Then the response status code should be 200
Then the response header "Content-Type" should be "application/pdf"
Then there should be 1 PDF(s) in the response
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s):
| files | teststore/foo.pdf | file |
Then the response status code should be 200
Then the response body should contain string:
"""
"Author":"legit"
"""
Scenario: POST /forms/pdfengines/metadata/read (Bad Request) Scenario: POST /forms/pdfengines/metadata/read (Bad Request)
Given I have a default Gotenberg container Given I have a default Gotenberg container
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s):