FlawOpen

flawopen.com/Insecure Deserialization/PHP unserialize()

Is PHP unserialize() safe for untrusted input?

CWE-502: Deserialization of Untrusted DataReference page
Short answer

Do not pass untrusted input to unserialize(), even when allowed_classes is set. PHP may restore objects and invoke __unserialize() or __wakeup(); an attacker may exploit classes available to the application or autoloader. Use JSON for untrusted data. With associative decoding, JSON objects become arrays and cannot instantiate application classes with magic-method behavior.

VULNERABLE
<?php
// Cookie content is controlled by the client.
$input = $_COOKIE['prefs'] ?? '';
$data = unserialize($input);

// Restoring an object may invoke its
// __unserialize() or __wakeup() method.
// Later object destruction may invoke __destruct().

// The risk depends on available classes,
// their serialized state, and reachable behavior.
FIXED
<?php
$input = $_POST['prefs'] ?? null;
if (!is_string($input)) {
    http_response_code(400);
    exit;
}

try {
    $data = json_decode(
        $input,
        true,
        512,
        JSON_THROW_ON_ERROR
    );
} catch (JsonException $e) {
    http_response_code(400);
    exit;
}

if (!is_array($data)) {
    http_response_code(400);
    exit;
}

// Validate required keys and value types here.

What a PHP object-injection chain requires

An object-injection risk is not automatic code execution. The serialized value must name a class PHP can resolve, the class must be permitted by the unserialization options, and its restoration or later use must reach behavior with a security impact. PHP may call __unserialize() or __wakeup() while restoring an object; __destruct() can run when the object is destroyed, and __toString() runs only when the object is used in a string context.

A property-oriented programming (POP) chain links behavior already present in the application or its loaded dependencies. Exploitability depends on the actual classes, serialized properties, call path, and runtime conditions; a dangerous gadget chain must be demonstrated for the target rather than assumed from the presence of unserialize() alone.

Why string filtering and allowed_classes are not a complete fix

Blocking a visible marker such as O:, filtering class names, or limiting string length does not safely validate PHP serialization. More importantly, PHP explicitly warns against passing untrusted input to unserialize() regardless of allowed_classes. When a class is disallowed, PHP creates a __PHP_Incomplete_Class object; the serialized structure is still parsed, so validate output shape and bound depth if handling trusted legacy data. Prefer JSON for client-provided state.

FAQ

Does allowed_classes => false make untrusted input safe?

No. It prevents normal application classes from being restored; disallowed objects become __PHP_Incomplete_Class. PHP still warns not to pass untrusted input to unserialize() regardless of this option. Treat it only as defense in depth for trusted legacy data, validate the result, and set a bounded max_depth. PHP documents that this option does not affect enumerations.

Can an HMAC make serialized data safe?

It can establish integrity for data generated by your application if the key remains secret and verification with hash_equals() happens before deserialization. Reject invalid signatures before parsing. An HMAC does not encrypt data or prevent replay; add expiry or nonce checks when the application needs freshness. Do not use signing to legitimize arbitrary user-supplied serialized input.

Is this the same risk as Python pickle or Java ObjectInputStream?

They share the broad risk of restoring objects from serialized input, but their formats, hooks, class-resolution rules, and mitigations differ. Assess each runtime and its available classes separately; do not assume that a gadget chain for one platform works on another.

References

See also Insecure Deserialization: Risks and Remediation Python pickle.load() Security: Untrusted Data Risks Is Ruby YAML.load Safe? Psych Versions & Safe Loading .NET BinaryFormatter Security: Risks and Migration Java ObjectInputStream Security: Safer Deserialization Newtonsoft.Json TypeNameHandling: Safe Configuration?