Back to advisories
GHSA-87qc-37cw-84h4 Low

$temporaryFiles is public, enabling arbitrary file deletion at shutdown

GHSA-87qc-37cw-84h4 is a Low severity vulnerability in knplabs/knp-snappy (<= 1.7.1) where the $temporaryFiles property on AbstractGenerator is declared public instead of private. Snappy uses this array to track temp files it creates during generation, then deletes everything in it automatically when the object is destroyed at shutdown via __destruct().

Affected: Snappy <=1.7.1 Vendor: KnpLabs Discovered: Reported: May 13, 2026 Patched: May 15, 2026 Reporter: Teodor Radoi

Details

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.

Start securing your codebase today

Connect your repositories and let AI agents handle continuous scanning, research, and triage.

Have questions? Get in touch →