Object Injection Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
object-injection | 🛡️ Security | Critical | 25 minutes |
What This Checks
Detects PHP object injection vulnerabilities through unsafe deserialization patterns and exploitable magic methods used in Property-Oriented Programming (POP) gadget chains:
unserialize()with user input - Critical withoutallowed_classesrestriction; Medium whenallowed_classes => falseis present; High when input source is unknowncall_user_func('unserialize', ...)/call_user_func_array('unserialize', ...)- High; indirect deserialization via dynamic dispatch- Magic methods (
__wakeup,__unserialize,__destruct,__toString,__call,__callStatic,__get,__set,__isset,__unset,__invoke) containing dangerous file, exec, eval, or database operations - High when sinks consume$this->properties, Medium otherwise - Filesystem functions (
file_get_contents,fopen,getimagesize,md5_file, etc.) with user-controlled paths - High; vulnerable tophar://stream wrapper deserialization
Why It Matters
PHP object injection is a critical vulnerability that allows attackers to manipulate application objects and exploit magic methods for arbitrary code execution:
- Remote Code Execution - By chaining magic methods (POP chains), attackers can execute arbitrary PHP code on the server
- File System Manipulation - Reading, writing, or deleting arbitrary files through exploited
__destruct()or__wakeup()methods - SQL Injection - Triggering database queries through magic methods that interact with the database
- Authentication Bypass - Manipulating session or user objects to escalate privileges
- Denial of Service - Crafting objects that consume excessive resources during deserialization
Object injection attacks are particularly dangerous because the vulnerability exists at deserialization time -- before any application logic can validate the data.
How to Fix
Quick Fix (5 minutes)
Replace unserialize() with JSON:
Before (❌):
public function loadPreferences(Request $request)
{
$data = $request->input('preferences');
// VULNERABLE: User input passed to unserialize()
$preferences = unserialize($data);
return view('settings', ['preferences' => $preferences]);
}After (✅):
public function loadPreferences(Request $request)
{
$data = $request->input('preferences');
// SAFE: JSON does not instantiate objects
$preferences = json_decode($data, true);
if (json_last_error() !== JSON_ERROR_NONE) {
abort(400, 'Invalid preferences format');
}
return view('settings', ['preferences' => $preferences]);
}Proper Fix (25 minutes)
If you must use unserialize(), restrict allowed classes:
Before (❌):
public function restoreSession(string $sessionData)
{
// VULNERABLE: Any class can be instantiated
$session = unserialize($sessionData);
return $session;
}After (✅):
public function restoreSession(string $sessionData)
{
// SAFE: Only allow specific classes to be unserialized
$session = unserialize($sessionData, [
'allowed_classes' => [
\App\ValueObjects\SessionData::class,
\App\ValueObjects\UserPreference::class,
],
]);
if ($session === false) {
throw new \RuntimeException('Failed to deserialize session data');
}
return $session;
}Prevent phar:// deserialization attacks:
Before (❌):
public function downloadFile(Request $request)
{
$path = $request->input('path');
// VULNERABLE: User can provide phar:// path to trigger deserialization
$contents = file_get_contents($path);
return response($contents);
}After (✅):
public function downloadFile(Request $request)
{
$validated = $request->validate([
'path' => 'required|string|max:255',
]);
// SAFE: Validate and sanitize the file path
$path = realpath(storage_path('app/downloads/' . basename($validated['path'])));
if (!$path || !str_starts_with($path, storage_path('app/downloads'))) {
abort(403, 'Invalid file path');
}
return response()->download($path);
}Audit magic methods for dangerous operations:
Before (❌):
class CacheItem
{
private string $filePath;
// DANGEROUS: __destruct with file operations - POP gadget
public function __destruct()
{
// If an attacker controls $filePath, they can delete arbitrary files
if (file_exists($this->filePath)) {
unlink($this->filePath);
}
}
}After (✅):
class CacheItem
{
private string $filePath;
private string $basePath;
public function __construct(string $filePath)
{
$this->basePath = storage_path('cache');
$this->filePath = $this->validatePath($filePath);
}
public function __destruct()
{
// SAFE: Path validated and restricted to cache directory
if (file_exists($this->filePath)) {
unlink($this->filePath);
}
}
private function validatePath(string $path): string
{
$resolved = realpath($this->basePath . '/' . basename($path));
if (!$resolved || !str_starts_with($resolved, $this->basePath)) {
throw new \InvalidArgumentException('Invalid cache file path');
}
return $resolved;
}
// Prevent unserialization entirely
public function __wakeup(): void
{
throw new \RuntimeException('Unserialization is not allowed');
}
}References
- OWASP Deserialization Cheat Sheet
- CWE-502: Deserialization of Untrusted Data
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
- PHP unserialize() Documentation
- PHP Magic Methods Documentation
- OWASP Top 10 - Insecure Deserialization
- Phar Deserialization Attack
Related Analyzers
- Eval Usage Analyzer - Detects dynamic code execution functions
- RCE Analyzer - Detects remote code execution via variable functions
- Command Injection Analyzer - Detects shell command injection
- Arbitrary File Upload Analyzer - Detects unsafe file upload handling