
Reproducible Docker-based proof-of-concept for CVE-2026-19949, a second-order SQL injection in All-in-One WP Migration <= 7.109 that leaks the ai1wm_secret_key via anonymous REST and escalates to remote code execution.
Inyección SQL de segundo orden no autenticada en All-in-One WP Migration and Backup (WordPress)
que escala a filtrado de la ai1wm_secret_key y, con ella, a ejecución remota de código (RCE).
Lee esto en: English · Español
| CVE | CVE-2026-19949 |
| Plugin | All-in-One WP Migration and Backup (ServMask), ≤ 7.109 |
| Parche | 7.110 (20 de agosto de 2026) |
| CVSS | 8.8 (Alta) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| Requisito | Que un administrador exporte y restaure el sitio (acción de rutina con este plugin) |
| Investigador | Jack Taylor (programa de recompensas de Wordfence) |
Uso exclusivamente educativo, formativo y defensivo. Este material es un recurso de formación interna para laboratorios propios y aislados: aulas, academias, CTF y equipos de seguridad que practican sobre su propia infraestructura. La PoC ataca un sitio WordPress que corre en tu propia máquina, dentro de contenedores Docker; ejecútala solo contra ese laboratorio o contra sistemas para los que tengas autorización escrita y explícita. No lo uses contra sistemas de terceros: es ilegal y no está autorizado.
Ámbito de uso y advertencia — léelo antes que nada.
Este repositorio es material formativo de laboratorio. Está pensado para estudiar el fallo, reproducirlo y verificar el parche en tu propia máquina. Todo lo que salga de ese contexto es responsabilidad exclusiva de quien lo ejecute, y este proyecto no lo ampara ni lo autoriza.
| Uso permitido | Uso prohibido |
|---|---|
| Reproducir la PoC en el laboratorio Docker incluido en este repositorio. | Ejecutar la PoC contra cualquier sitio que no sea tuyo. |
| Practicar en aulas, academias, CTF y entornos de formación controlados. | Usarla contra clientes, terceros o sistemas en producción sin autorización escrita. |
| Auditar tu propia infraestructura, con permiso explícito. | Escanear o explotar rangos de Internet «para ver si caen». |
| Verificar la 7.110 y construir detecciones con los indicadores del §13. | Redistribuirla como herramienta de ataque o integrarla en un panel de explotación. |
Este software es de doble uso: la misma cadena que sirve para aprender a
defenderse sirve para atacar. Por eso el modo invasivo del CLI exige
--acepto-responsabilidad y confirmación explícita del dominio, y por eso la
cadena solo se ha probado contra contenedores locales. No se ofrece ninguna
garantía y el autor declina toda responsabilidad por el uso indebido, los daños
derivados o las consecuencias legales (ver §16).
Si buscas la parte defensiva, ve directo a §13 Detección y mitigación.
El flujo de exportación/importación del plugin vuelca la base de datos a SQL
(database.sql dentro del .wpress) y, al importar, reescribe cada sentencia
con Ai1wm_Database::replace_table_values() para sustituir URLs y prefijos de
tabla. Para localizar los literales de cadena usa el regex:
// 7.109 (vulnerable) — class-ai1wm-database.php:1637
preg_replace_callback( "/'(.*?)(?<!\\\\)'/S", array( $this, 'replace_table_values_callback' ), $input );
El problema está en el negative lookbehind (?<!\\): solo examina un byte
antes de cada comilla candidata a cierre, en lugar de contar el run completo de
backslashes. En un volcado MySQL, unos datos que terminan en \ se escriben como
...\\' — una comilla de cierre precedida de un número par de backslashes
(cadena real terminada en backslash) — pero el regex la cree escapada y
sobre-captura el siguiente literal. El callback ejecuta entonces
unescape_mysql → replace_serialized_values → escape_mysql sobre el valor
sobre-capturado, y ese ciclo re-emite una secuencia de comillas/backslashes
desequilibrada que voltea el límite de la cadena MySQL en la sentencia
resultante, promoviendo datos del atacante a SQL ejecutable.
Con el trackback plantado (ver §4), la fila en database.sql es:
INSERT INTO `…_comments` VALUES (2,4,'Jack Blogs\\\\','','/*payload*/…','172.18.0.1',…);
El author termina en \ a nivel de datos → el volcado lo escribe como \\ →
la comilla de cierre queda precedida de un run par. El regex sobre-captura
hasta la comilla de apertura del campo siguiente y el callback re-escapa el
conjunto:
'Jack Blogs\\\\',' ← contenido sobre-capturado
unescape → 'Jack Blogs\\',' ← strtr colapsa los pares
escape → 'Jack Blogs\\\\\', ← una comilla ESCAPADA nueva ha aparecido
salida → 'Jack Blogs\\\\\','…' ← el par `\ , '` voltea el límite de la cadena
A partir de ahí, todo lo que seguía en la sentencia se re-tokeniza con la paridad cambiada (datos que eran cadena pasan a ser código y viceversa). El regex parcheado de 7.110 procesa la misma línea y la devuelve idéntica.
Puedes verlo byte a byte, sin explotar nada, con make demo-flip
(exploit/04_demo_flip.php), que ejecuta el código REAL del plugin 7.109 y
7.110 sobre la misma línea de volcado.
- $input = preg_replace_callback( "/'(.*?)(?<!\\\\)'/S", array( $this, 'replace_table_values_callback' ), $input );
+ $input = preg_replace_callback( "/'((?:[^'\\\\]++|\\\\.)*+)'/sS", array( $this, 'replace_table_values_callback' ), $input );
El nuevo patrón tokeniza literales MySQL correctamente: o un carácter que no es
comilla/backslash, o un par de escape \x, con cuantificadores posesivos. Un run
par de backslashes ya no confunde el cierre del literal.
Solo laboratorio. Los pasos que siguen se ejecutan contra el WordPress Docker que levantas tú mismo en
localhost:8080. No los reproduzcas contra un sitio ajeno: hacerlo es un delito de acceso no autorizado.
La cadena publicada por Wordfence (sept. 2026) tiene cuatro pasos: