Una extensión de donaciones puede vulnerar tu sitio
El 27 de agosto de 2026, GiveWP publicó la versión 4.16.7.2, parcheando una vulnerabilidad que suena a ejercicio teórico hasta que leés las precondiciones: sin autenticación, instalación por defecto, ejecución remota de código. CVE-2026-82222 tiene un score CVSS de 10.0, el máximo, y afecta toda versión de GiveWP hasta 4.16.7.1 inclusive. Más de 100.000 sitios de WordPress usan este plugin para recibir donaciones.
La vulnerabilidad encadena tres fallos independientes que por separado parecen poco destacables. Juntos, permiten a un atacante registrar una cuenta en un sitio que tiene el registro deshabilitado, plantar un payload serializado en la base de datos y ejecutar comandos arbitrarios del sistema operativo como el usuario del servidor web. Sin interacción del administrador. Sin modo debug. Sin modo de prueba. Un formulario de donación publicado y una pasarela de pago activa son suficientes. Eso es una instalación por defecto.
Qué hace GiveWP
GiveWP es el plugin de donaciones y fundraising más popular para WordPress. ONGs, iglesias, universidades y campañas políticas lo usan para aceptar donaciones online a través de formularios personalizables con múltiples pasarelas de pago, gestión de donantes y reportes. Si tu organización recolecta dinero a través de WordPress, GiveWP probablemente esté corriendo en tu servidor.
La cadena de ataque en tres partes
1. El helper "seguro" que no es seguro
GiveWP incluye una función helper llamada safeUnserialize() en src/Helpers/Utils.php. El nombre implica protección. La implementación no la entrega.
// src/Helpers/Utils.php — safeUnserialize()
public static function safeUnserialize( $data ) {
$data = self::removeBackslashes( $data );
$unserializedData = @unserialize( trim( $data ), [ 'allowed_classes' => false ] );
return ! $unserializedData && ! self::containsSerializedDataRegex( $data ) ? $data : $unserializedData;
}
La opción allowed_classes => false estaba pensada para prevenir la inyección de objetos. Lo que hace en realidad es convertir cualquier objeto serializado en un placeholder __PHP_Incomplete_Class, un tipo interno de PHP que preserva el nombre de la clase original y todas sus propiedades pero no puede llamar métodos. El objeto del atacante no se destruye. Se oculta para esa única lectura.
Cuando ese placeholder se serializa de nuevo camino al almacenamiento, PHP escribe el nombre de clase original y las propiedades verbatim. El payload malicioso pasa por el helper "seguro" sin tocar nada y aterriza en la base de datos intacto. La próxima vez que los datos se lean y se deserialicen sin esta guardia, lo cual ocurre en varios paths de código, el gadget real cobra vida.
El helper da a los administradores del sitio una falsa sensación de protección mientras silenciosamente reenvía el ataque a la próxima lectura sin guardia.
2. El flujo de donación que alimenta el helper
El atacante necesita una forma de plantar un objeto serializado en la base de datos. GiveWP proporciona una a través del flujo de donación mismo.
Un donante logueado puede almacenar un gadget chain serializado en el campo last_name de su propia cuenta vía profile.php. Cuando envía una donación, includes/process-donation.php construye el user_info del donante desde los datos de esa cuenta y pasa cada campo por el helper "seguro":
// includes/process-donation.php — give_process_donation_form()
$user_info = [
'id' => $user['user_id'],
'title' => $user['user_title'],
'email' => $user['user_email'],
'first_name' => $user['user_first'],
'last_name' => $user['user_last'], // gadget serializado controlado por el atacante
'address' => $user['address'],
];
$user_info = array_map( '\Give\Helpers\Utils::maybeSafeUnserialize', stripslashes_deep( $user_info ) );
El placeholder __PHP_Incomplete_Class se serializa en la tabla wp_give_sessions. Como PHP re-emite el nombre de clase original y las propiedades para ese placeholder, los bytes del objeto malicioso aterrizan en la base de datos intactos. La próxima petición que lea la sesión deserializa esos bytes sin la guardia allowed_classes y trae el gadget real a la vida.
Los datos maliciosos no llegan en la petición que dispara la deserialización. Vienen de la base de datos. La validación de input normal nunca los ve.
3. El gadget chain en el código que viene incluido
La inyección de objetos por sí sola no ejecuta código. El atacante necesita un gadget chain, una secuencia de métodos en clases ya cargadas que, cuando un objeto se destruye, finalmente llama a una función peligrosa. GiveWP incluye tanto la librería TCPDF como las clases Give TestData, y juntas forman una cadena completa:
// src/TestData/Framework/ProviderForwarder.php — el gadget terminal
public function __call( $name, $arguments ) {
$provider = isset( $this->loadedProviders[ $name ] )
? $this->loadedProviders[ $name ]
: $this->loadProvider( $name );
// sin verificación de qué es realmente $provider
return call_user_func_array( $this->loadedProviders[ $name ], $arguments );
}
La destrucción del objeto inyectado entra en TCPDF::__destruct(), que llama a _destroy(). Eso llega al método mágico __call() del trait ProviderForwarder, que redirige directo a call_user_func_array() usando un callable y un argumento controlados por el atacante. Como loadedProviders es simplemente una propiedad de array dentro del objeto inyectado, el atacante la setea a cualquier callable que quiera. Apuntarla a system() ejecuta un comando arbitrario del sistema operativo como el usuario del servidor web.
Cómo obtener la cuenta gratis
La cadena anterior necesita un usuario logueado. GiveWP le entrega uno al atacante.
El plugin expone una acción de registro sin autenticación (give_action=user_register) que nunca consulta la opción users_can_register de WordPress. Incluso en un sitio que tiene el registro deshabilitado en la configuración de WordPress, el atacante puede crear una cuenta y recibir una cookie de autenticación.
La versión 4.16.6 agregó un requisito de nonce a este handler, lo que reduce la ventana pero no la cierra. El nonce solo lo emite el template del shortcode [give_register], y los nonces de WordPress para visitantes no logueados son idénticos para cada petición anónima a un sitio dado. En cualquier sitio que renderice ese shortcode en una página pública, el atacante cosecha el nonce una vez y lo reutiliza.
La cadena completa: sin autenticación a RCE
- Registrar una cuenta. Enviar un POST con
give_action=user_register. El servidor crea la cuenta y devuelve una cookie de autenticación, independientemente de la configuración de registro del sitio. - Plantar el gadget. Leer el nonce de perfil desde
profile.php, luego POSTear el gadget chain serializado en el campolast_namede la cuenta. - Envenenar la sesión. Obtener un nonce de donación, luego enviar una donación con el form ID, pasarela y monto pero sin
give_last. El servidor escribe el objeto gadget enwp_give_sessionsantes de devolver un HTTP 500. - Disparar la ejecución. Solicitar cualquier página del front-end con la misma cookie. El servidor lee la sesión envenenada, deserializa el gadget y ejecuta el comando del atacante. El output se puede leer de vuelta por HTTP.
Por qué esto es crítico para tu negocio
CVSS 10.0 es el score máximo. La instalación por defecto de GiveWP hasta 4.16.5.1 es explotable de punta a punta. Las versiones 4.16.6 a 4.16.7.1 reducen la alcanzabilidad pero siguen siendo explotables en cualquier sitio que tenga un post give_forms sin formBuilderSettings, lo que incluye todo sitio actualizado desde una versión anterior, cualquier importación o restauración de formulario, y cualquier sitio donde un administrador haya habilitado Settings → Advanced → "Option-Based Form Editor."
No requiere autenticación. El atacante crea su propia cuenta a través de un path de registro que ignora la política de registro del propio WordPress. Nunca necesita credenciales de admin ni acceso previo.
RCE significa compromiso total. Una vez que el atacante ejecuta comandos del sistema operativo como el usuario del servidor web, puede leer la base de datos, exfiltrar datos de donantes incluyendo información de pago, desplegar malware, pivotear a otros servicios en el mismo servidor y persistir acceso a través de backdoors que sobreviven actualizaciones del plugin.
Las ONGs no son blancos blindados. Las organizaciones que corren GiveWP son típicamente iglesias, pequeñas caridades y grupos comunitarios. No tienen equipos de seguridad dedicados. Instalan el plugin, configuran una pasarela de pago y siguen adelante. Son la definición de blanco blando.
Si tu organización corre GiveWP y no ha actualizado a 4.16.7.2, la puerta está abierta.
Cómo arreglarlo
GiveWP 4.16.7.2 se publicó el 27 de agosto de 2026. El fix es por capas, rompiendo la cadena en cinco puntos independientes:
- El path de escritura.
process-donation.phpahora rechaza la donación completa si cualquier campo de nombre contiene datos serializados, antes de que algo se almacene. El fallback de user meta también pasa porgive_clean(), que devuelve un string vacío para input serializado. - Los puntos de lectura. Los tres lugares que deserializaban estos datos ahora pasan
allowed_classes => false: el getter de sesión, la lectura de tabla de sesión y el donor wall. Este último era alcanzable por visitantes anónimos a través del shortcode[give_donor_wall]sin cookie de sesión. - El gadget.
ProviderForwarder::__call()ahora verifica que el provider resuelto implemente el contrato esperado antes de llamarlo. Elcall_user_func_array()terminal ya no puede apuntarse a un callable arbitrario. - Escrituras de meta. Los meta de nombre de donante y billing pasan por
sanitize_text_field()en lugar de almacenarse crudos. - Daño existente. Una migración llamada
SanitizeSerializedObjectPayloadsrecorreusermeta,give_donormeta,give_donationmetaygive_sessionsy reemplaza cualquier objeto anidado con un string vacío. Sin este paso, un sitio envenenado antes de actualizar mantiene un payload vivo en su base de datos.
Actualiza a 4.16.7.2 inmediatamente. Si no podés actualizar ahora, deshabilitá el plugin GiveWP temporalmente. Un formulario de donación no disponible es un inconveniente temporal. Un servidor comprometido es una catástrofe.
Qué significa esto para la seguridad de WordPress
Tres causas raíz hicieron posible esta vulnerabilidad, y ninguna es exclusiva de GiveWP:
Confiar en un sanitizador de serialización que no elimina objetos. La opción allowed_classes => false de PHP no destruye objetos serializados. Los enmascara. Cualquier helper que dependa de esta opción para deserializar datos "seguramente" está ofreciendo teatro, no seguridad. Si tu código llama unserialize() sobre datos que alguna vez fueron controlados por el atacante, asumí que es vulnerable.
Deserializar datos de base de datos como si fueran confiables. El patrón de "sanitizar al escribir, confiar al leer" se rompe cuando el path de escritura tiene un bypass. GiveWP aceptó user meta sin validar que no contuviera datos serializados, luego lo deserializó al leer sin la misma guardia. Cualquier plugin de WordPress que almacene datos controlados por el usuario y luego los deserialice está jugando el mismo juego.
Incluir librerías de desarrollo en producción. El gadget chain existe porque GiveWP incluye las clases TCPDF y TestData en su build de producción. Estas librerías contienen métodos útiles para desarrollo y testing pero peligrosos cuando se cargan en un contexto donde un atacante puede alcanzarlos. Cada librería cargada en tu instancia de WordPress es una fuente potencial de gadgets.
El parche está disponible. La migración limpia daño existente. Instalalo antes de que un atacante encuentre tu formulario de donación.
¿Corrés GiveWP en tu sitio de WordPress? Nuestro equipo puede ayudarte a auditar, parchear y verificar que no se haya plantado ningún payload antes de la actualización.
¿Necesitas ayuda con tu proyecto?
Hablemos de cómo podemos ayudarte a construir software confiable.
Contáctanos