PHAR Deserialization Attack
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm as tools, both specialist SAST tools, and notes automated detection is possible via code patterns like file_exists($_GET['file']). A default linter won't flag this; it requires a configured SAST tool scanning for user-controlled paths passed to file functions.
Closest to 'simple parameterised fix' (e3). The quick_fix is to validate and canonicalise all paths with realpath() before passing to file functions. This is not a single-line swap everywhere — it requires auditing all call sites where user input reaches file functions and adding path canonicalisation — but it's a small, repeatable pattern fix rather than a cross-cutting architectural refactor.
Closest to 'localised tax' (b3). The fix applies to PHP web and CLI contexts wherever user-controlled paths reach file functions, but once stream wrapper restrictions and input validation are in place the burden is contained. It doesn't reshape the entire architecture, though it requires ongoing discipline in any code that handles file paths.
Closest to 'serious trap' (t7). The misconception field directly states the canonical wrong belief: developers assume PHAR injection requires uploading a .phar file, when in reality any file function (file_exists, fopen, copy) on a user-controlled path starting with phar:// triggers deserialization — even on files masquerading as images. This contradicts the intuition that mime-type checking uploads is sufficient protection, making it a serious trap.
Also Known As
TL;DR
Explanation
PHAR (PHP Archive) files embed serialized PHP metadata in their stub. Any PHP file function (file_exists(), fopen(), copy(), rename(), include()) that operates on a phar:// URI will deserialize this metadata — triggering magic methods and enabling PHP Object Injection without a call to unserialize(). An attacker who can upload a file (even one with a safe extension like .jpg) and control a path passed to a file function can exploit this. Mitigations: disable the phar stream wrapper (Phar::unlinkArchive, stream_wrapper_unregister('phar')), validate file paths strictly, and use realpath() before file operations.
How It's Exploited
Common Misconception
Why It Matters
Common Mistakes
- Allowing user file uploads to be passed to file_exists(), file_get_contents(), or similar — a phar:// URL triggers deserialization.
- Not disabling PHAR-based stream wrappers when user input is used in file paths.
- Mime-type checking uploads but not considering that .jpg files can be valid PHAR archives.
- Not restricting allowed PHP stream wrappers via allow_url_fopen and htaccess rules.
Code Examples
// User-controlled path triggers PHAR deserialization:
$file = $_GET['file'];
if (file_exists($file)) { // Attacker: ?file=phar://uploads/evil.jpg
echo file_get_contents($file);
}
// Validate file paths — never pass user input to file functions:
function safeRead(string $userPath): string {
// Realpath resolves .. and symlinks:
$base = realpath('/var/www/uploads/');
$full = realpath($base . '/' . basename($userPath));
// Ensure resolved path is inside the allowed directory:
if (!$full || !str_starts_with($full, $base . '/')) {
throw new SecurityException('Path traversal attempt');
}
return file_get_contents($full);
}
// Disable phar stream wrapper if not needed:
stream_wrapper_unregister('phar');
// Validate file magic bytes, not extension:
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$type = $finfo->file($path); // Check actual content type