← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Insecure Deserialization

Security CWE-502 OWASP A8:2021 CVSS 9.8 PHP 5.0+ Advanced
debt(d5/e3/b3/t9)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm — both specialist/SAST tools. The code_pattern is a textual match (unserialize called on user-controlled sources), which these tools can flag, but it won't surface as a default linter warning and certainly won't be caught at compile time.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix is a clear replacement: swap unserialize() for json_decode(), or use allowed_classes:false plus HMAC signing. This is a per-call substitution that may touch a handful of sites in one component, but does not require cross-cutting architectural rework.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). The vulnerability applies wherever unserialize() is called on user-supplied data (web, cli, queue-worker per applies_to), but once the call sites are fixed the rest of the codebase is unaffected. It is not a load-bearing abstraction used everywhere; it is a specific dangerous call pattern.

t9 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'catastrophic trap — the obvious way is always wrong' (t9). The misconception field states it precisely: developers believe validating data before calling unserialize() makes it safe, but PHP instantiates objects and fires __wakeup/__destruct during deserialization itself — the damage occurs before any validation code runs. This directly contradicts the intuitive validate-then-process pattern that competent developers apply everywhere else, making the obvious defensive approach always wrong.

About DEBT scoring →

Also Known As

unsafe deserialization unserialize attack PHP object injection

TL;DR

Untrusted data passed to unserialize() can trigger PHP magic methods and lead to remote code execution.

Explanation

PHP's unserialize() reconstructs objects from a serialised string, invoking magic methods like __wakeup() and __destruct() in the process. Attackers can craft serialised payloads that exploit available classes (known as "gadget chains") to achieve arbitrary code execution, file deletion, or privilege escalation — without needing a file upload. The safe alternative is json_decode() for data exchange; if serialisation is required, restrict allowed_classes.

How It's Exploited

# Generate a POP-chain payload with phpggc:
$ phpggc Laravel/RCE1 system 'id' -b
# Set as session cookie — on unserialize(), executes 'id' on the server

Common Misconception

Validating data before unserializing it makes the operation safe. PHP's unserialize() instantiates objects and triggers magic methods (__wakeup, __destruct) during deserialization itself — the damage happens before your validation code runs.

Why It Matters

PHP's unserialize() triggers __wakeup and __destruct magic methods on attacker-controlled objects — chained together, existing classes can be weaponised into remote code execution without any custom exploit code.

Common Mistakes

  • Passing user-supplied cookies, tokens, or POST data directly to unserialize().
  • Believing that a HMAC on the serialized string is sufficient — if the verification is bypassable, it isn't.
  • Not using json_decode() or other format-specific parsers when PHP object serialization is not needed.
  • Using unserialize() with allowed_classes but not restricting the class list tightly enough.

Code Examples

✗ Vulnerable
// Deserialising untrusted data — attacker crafts a payload
// that triggers arbitrary PHP object instantiation
$data = unserialize($_COOKIE['session']);
// Any class with __wakeup() or __destruct() in scope
// can be chained into a POP chain for RCE
✓ Fixed
// Never unserialize untrusted input — use JSON instead
$data = json_decode(base64_decode($_COOKIE['session']), true);

// If you must use serialization, whitelist allowed classes:
$data = unserialize($input, ['allowed_classes' => [SafeValueObject::class]]);

// Best: sign the payload so tampering is detected
$payload = base64_encode(json_encode($data));
$sig     = hash_hmac('sha256', $payload, $_ENV['SECRET']);
$cookie  = $payload . '.' . $sig;

// Verify on read:
[$payload, $sig] = explode('.', $cookie, 2);
if (!hash_equals(hash_hmac('sha256', $payload, $_ENV['SECRET']), $sig)) abort(400);
$data = json_decode(base64_decode($payload), true);

Added 15 Mar 2026
Edited 22 Mar 2026
Views 74
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 2 pings M 0 pings T 0 pings W 1 ping T 3 pings F 1 ping S 2 pings S 5 pings M 1 ping T 4 pings W 0 pings T 0 pings F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Scrapy 16 ChatGPT 10 Perplexity 9 Amazonbot 7 Google 5 Ahrefs 4 SEMrush 4 Unknown AI 2 Claude 2 Majestic 1 Bing 1 Meta AI 1 PetalBot 1
crawler 59 crawler_json 3 pre-tracking 1
DEV INTEL Tools & Severity
🔴 Critical ⚙ Fix effort: Medium
⚡ Quick Fix
Replace unserialize() with json_decode(); if unavoidable use allowed_classes:false and HMAC-sign the serialised payload
📦 Applies To
PHP 5.0+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
unserialize($_GET[ or unserialize($_POST[ or unserialize($_COOKIE[ or unserialize(base64_decode(
Auto-detectable: ✓ Yes semgrep psalm
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: High Context: File Tests: Update
CWE-502


✓ schema.org compliant