Summary
| Field |
Value |
| CVE ID |
No CVE assigned |
| Advisory |
GHSA-87qc-37cw-84h4 |
| Severity |
Low |
| Package |
knplabs/knp-snappy (Composer) |
| Affected |
<= 1.7.1 |
| Fixed in |
>= 1.7.2 |
The $temporaryFiles property in AbstractGenerator is declared public, which means any code holding a reference to a Snappy generator instance can inject arbitrary file paths into it. When the object is destroyed at the end of the request, __destruct() calls removeTemporaryFiles(), which blindly calls unlink() on every path in the array with no containment check. This gives any code that can reach the Snappy instance a reliable file deletion primitive that fires at shutdown.
Detailed Content
How it works
Snappy uses a temporary file list to track files it creates during PDF/image generation so it can clean them up automatically when the generator object goes out of scope. The list lives in:
// src/Knp/Snappy/AbstractGenerator.php, line 31
public $temporaryFiles = [];
Because the property is public, nothing stops external code from pushing any path into it. When the PHP process shuts down and the object destructor runs, every path in the list is deleted without checking whether it is actually a file Snappy created, whether it lives in a temp directory, or whether it is a sensitive application file.
Why this matters
On its own this vulnerability requires an attacker to already be executing code in the same request, so it is not a standalone exploit. The real risk comes from two chaining scenarios:
Scenario 1: Leak then delete (cover tracks)
A separate information disclosure bug (path traversal, debug page, verbose error, etc.) reveals a sensitive file path such as a .env file, a private key, or a config file. The attacker injects that path into $temporaryFiles and the file is silently deleted at the end of the request, removing evidence or breaking the application.
Scenario 2: Deserialization gadget
If the application deserializes untrusted data anywhere (common in PHP applications using cookies, session data, or message queues), a deserialization gadget that reaches a Snappy instance turns this into a generic arbitrary file deletion primitive. The attacker constructs a serialized payload containing a Snappy object with the target path pre-loaded into $temporaryFiles, and the deletion happens automatically on deserialization and subsequent garbage collection.
Root Cause
The $temporaryFiles property has no access control. It should be private or protected with controlled access through a dedicated method. The removeTemporaryFiles() method also lacks any path validation or containment check before calling unlink().
Fix
Version 1.7.2 changes the visibility of $temporaryFiles so external code can no longer write to it directly.
Workaround (if you cannot upgrade immediately)
Ensure that no untrusted code can obtain a reference to the Snappy generator instance. In practice this means not passing the instance through user-controlled channels, not exposing it as a public service property, and ensuring it is not reachable through any deserialization path.
Proof of Concept
Direct injection (requires code execution in the same request)
<?php
// An attacker or a compromised dependency that holds a reference to $pdf
// can schedule any file for deletion at no extra cost.
$pdf = new Knp\Snappy\Pdf('/usr/local/bin/wkhtmltopdf');
// Inject an arbitrary sensitive path.
$pdf->temporaryFiles[] = '/var/www/html/.env';
$pdf->temporaryFiles[] = '/etc/cron.d/my-app-job';
$pdf->temporaryFiles[] = '/var/www/html/storage/oauth-private.key';
// Nothing else needed. When $pdf goes out of scope or the request ends,
// __destruct() -> removeTemporaryFiles() -> unlink() fires on every entry.
// All three files are silently deleted.
Deserialization gadget
<?php
// Attacker crafts a malicious serialized payload targeting a Snappy instance.
// When the application deserializes this, a Snappy object is reconstructed
// with the target path already in $temporaryFiles.
// The file is deleted when the object is garbage collected.
$malicious = new Knp\Snappy\Pdf('/usr/local/bin/wkhtmltopdf');
$malicious->temporaryFiles = ['/var/www/html/.env'];
$payload = serialize($malicious);
// Attacker delivers $payload via cookie, session, message queue, etc.
// The receiving application calls unserialize($payload) somewhere,
// and the deletion is scheduled automatically.
Chain: info disclosure + file deletion
1. Attacker exploits a path disclosure bug to learn the location of /var/www/html/.env
2. Attacker injects that path into $temporaryFiles of a reachable Snappy instance
3. At request shutdown, .env is deleted
4. Application loses its database credentials, secret key, and API tokens
5. Evidence of the original disclosure is gone
Upgrading
composer require knplabs/knp-snappy:^1.7.2
Key Takeaways
- This is a Low severity issue on its own but becomes meaningful as a force multiplier when chained with other vulnerabilities.
- The most realistic attack path is through PHP deserialization, which is a common weakness in PHP applications.
- Making
$temporaryFiles private and adding path containment checks to removeTemporaryFiles() are both necessary to close this properly.
- Upgrade to 1.7.2. If you cannot, ensure the Snappy instance is never reachable from untrusted code paths or deserialization flows.