FlawOpen

flawopen.com/Deserialización insegura/PHP unserialize()

¿Es seguro PHP unserialize() con datos no confiables?

CWE-502: Deserialization of Untrusted DataPágina de referencia
Respuesta breve

No pases datos no confiables a unserialize(), ni siquiera con allowed_classes. PHP puede restaurar objetos y llamar a __unserialize() o __wakeup(); un atacante podría aprovechar clases disponibles en la aplicación o el autoloader. Usa JSON para datos no confiables. Con decodificación asociativa, los objetos JSON se convierten en arrays y no instancian clases de la aplicación con comportamiento de métodos mágicos.

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.
CORREGIDO
<?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.

Qué requiere una cadena de inyección de objetos en PHP

El riesgo de inyección de objetos no implica ejecución automática de código. El valor serializado debe nombrar una clase que PHP pueda resolver y que esté permitida por las opciones de deserialización; además, la restauración o el uso posterior debe alcanzar un comportamiento con impacto de seguridad. PHP puede llamar a __unserialize() o __wakeup() al restaurar el objeto; __destruct() puede ejecutarse al destruirlo y __toString() solo cuando se usa en un contexto de cadena.

Una cadena Property-Oriented Programming (POP) enlaza comportamientos ya presentes en la aplicación o en sus dependencias cargadas. La explotabilidad depende de las clases reales, las propiedades serializadas, la ruta de llamadas y las condiciones de ejecución. Hay que demostrar una cadena de gadgets para el objetivo; la mera presencia de unserialize() no basta.

Por qué filtrar la cadena o usar allowed_classes no es una solución completa

Bloquear un marcador visible como O:, filtrar nombres de clases o limitar la longitud no valida de forma segura la serialización PHP. Además, PHP advierte explícitamente que no se deben pasar datos no confiables a unserialize(), independientemente de allowed_classes. Si una clase no está permitida, PHP crea un objeto __PHP_Incomplete_Class; la estructura se sigue analizando. Para datos heredados confiables, valida la forma resultante y limita la profundidad. Prefiere JSON para estado del cliente.

Preguntas frecuentes

¿allowed_classes => false hace seguros los datos no confiables?

No. Impide restaurar clases normales de la aplicación; los objetos no permitidos se convierten en __PHP_Incomplete_Class. PHP sigue indicando que no se pase entrada no confiable a unserialize(), incluso con esta opción. Úsala solo como defensa en profundidad para datos heredados confiables, valida el resultado y limita max_depth. El manual de PHP indica que esta opción no afecta a las enumeraciones.

¿Un HMAC hace seguros los datos serializados?

Puede verificar la integridad de datos generados por la aplicación si la clave sigue siendo secreta y hash_equals() valida la firma antes de deserializar. Rechaza firmas inválidas antes de analizar. Un HMAC no cifra ni evita la repetición; añade caducidad o nonce si necesitas frescura. No uses firmas para legitimar datos serializados arbitrarios del usuario.

¿Es el mismo riesgo que Python pickle o Java ObjectInputStream?

Comparten el riesgo general de restaurar objetos desde datos serializados, pero los formatos, hooks, reglas de resolución de clases y mitigaciones difieren. Evalúa cada runtime y sus clases disponibles por separado; no presupongas que una cadena de gadgets de una plataforma sirve en otra.

Referencias

Véase también Deserialización insegura: riesgos y defensa Seguridad de pickle.load() en Python: riesgos de datos no confiables ¿Es seguro Ruby YAML.load? Versiones de Psych y safe_load .NET BinaryFormatter: riesgos y migración ObjectInputStream en Java: deserialización segura Newtonsoft.Json TypeNameHandling: ¿configuración segura?