
phpMyAdmin 5.2.3 - Alta - Remota - XSS - Generador de consultas visuales Designer
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · phpmyadmin-vqb-xss
Clase: XSS Almacenado Alcance: Remoto
phpMyAdmin 5.2.3 (RELEASE_5_2_3 / 962857e) - phpMyAdmin
js/src/designer/history.js construye el historial de consultas visuales con innerHTML. Los nombres de columna pasan por $('<div/>').text(...).html(). Los nombres de tabla no. TableName todavía permite < y >. Una cuenta con bajos privilegios con CREATE TABLE en una base de datos compartida planta un identificador HTML. Cuando un usuario con mayores privilegios abre el Designer, getTableName() está presente en la página y DesignerHistory.display() lo escribe en #ab.
Un XSS almacenado en el origen de phpMyAdmin. Misma clase que PMASA-2025-1, funcionalidad diferente. Los encabezados de Check-tables estaban escapados. Esta ruta no lo estaba.
| ID | sin CVE todavía |
| Clase | XSS Almacenado |
| Alcance | Remoto (CREATE TABLE autenticado; la víctima abre el Designer) |
| CWE | CWE-79 |
| CVSS | Alto: 8.0 CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N |
| Producto | phpMyAdmin |
| Afectado | 5.2.3 tag RELEASE_5_2_3 (962857e4f63d42e38f11ff4d63f5e722018add76) |
| Autenticación | autenticado. El atacante necesita CREATE TABLE en una base de datos que la víctima abra en el Designer |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
Plantar un nombre de tabla HTML, luego esperar a que un usuario con mayores privilegios abra el Designer en esa base de datos y renderice el historial de consultas visuales.
| Privilegio | Método / sink | Reconocimiento / integridad / disponibilidad | Probado en laboratorio o inferido |
|---|---|---|---|
| Inyectar marcado en el origen de phpMyAdmin | DesignerHistory.display() concatena getTab() en el innerHTML de #ab | Integridad del origen de la víctima | Probado en laboratorio. history.js en vivo sobre phpmyadmin:5.2.3. <h3> contenía <i id="pma-vqb"> |
Escapar de un atributo table_name del designer | database_tables.twig small_tab_pref_1 usa designerTable.getTableName() | Reconocimiento del nombre MySQL sin procesar en el DOM del Designer | Probado en laboratorio, legible como el identificador sin procesar |
Leer la cookie de sesión de la víctima y el token CSRF | Script del mismo origen una vez que se renderiza el historial | Reconocimiento de la sesión PMA de la víctima | Inferido a partir del XSS almacenado en el origen de PMA. Este laboratorio usa un marcador <i>, no un script |
| Ejecutar SQL, gestionar usuarios, exportar, como la cuenta MySQL de la víctima | POST respaldado por token a /sql, /server/privileges, export | Integridad de todo lo que esa cuenta MySQL pueda tocar | Inferido. Misma clase de impacto que PMASA-2025-1 |
| Cambiar 2FA / userconfig | POST a /config/set como la víctima | Integridad del almacén de preferencias de la víctima | Inferido. Residuo hermano, no este laboratorio |
Descartado en este residuo: acceso no autenticado, RCE del sistema operativo y la ruta del botón OK del VQB. .small_tab_pref_click_opt pre-escapa table_name y Twig lo autoescapa de nuevo, por lo que ese clic actualmente introduce entidades en getTab(). Eso no es una corrección de history.js. Las celdas de columna permanecen escapadas como texto (c<b id=pma-col>y, sin nodo <b id="pma-col">).
Mismo pin: Residuo de WebAuthn CustomServer.
Fijé phpMyAdmin RELEASE_5_2_3. PMASA-2025-1/2/3 ya están en 5.2.2. El Designer todavía construye el HTML del historial del VQB a mano.
js/src/designer/history.js (distribuido como js/dist/designer/history.js):
temp = historyArray[i].getTab();
str += '<h3 class="tiger"><a href="#">' + temp + '</a></h3>';
// ...
'<td width="175" style="padding-left: 5px">' + $('<div/>').text(historyArray[i].getColumnName()).html() + '<td>';
libraries/classes/Dbal/TableName.php solo verifica que no esté vacío, máximo 64, sin espacio final. < y > son legales. MySQL los acepta en un identificador entre comillas.
templates/database/designer/database_tables.twig copia designerTable.getTableName() en table_name sobre .small_tab_pref_1. La celda de opción del VQB usa una copia pre-escapada del mismo nombre.
Caminos equivocados ya registrados: tratar el atributo del botón de opción doblemente escapado como la corrección (no lo es - small_tab_pref_1 todavía expone el nombre sin procesar, y history.js es el sink); tratar el helper de columna como si cubriera los nombres de tabla; afirmar XSS no autenticado; una reverse shell. Teatro.
python3 phpmyadmin-vqb-xss-Abraxas-Labs.py
# or: cd lab && ./run.sh
Objetivo solo http://127.0.0.1:19250. Imágenes phpmyadmin:5.2.3 y mysql:8.0. Proyecto Compose phpmyadmin-vqb-xss. Playwright Chromium en el servicio poc. El stack se desmonta tras la ejecución.
Dos usuarios, shareddb compartida. El usuario de bajos privilegios creator ejecuta CREATE TABLE \tx` (`cy` INT). El usuario de mayores privilegios victiminicia sesión en phpMyAdmin y abre el Designer conquery=1. history.js en vivo es la copia del contenedor (h3-unescaped=yes, helper de columna presente). Chromium llama a DesignerHistory.display()con el nombre de tabla degetTableName()` en la página.
Resultado: el innerHTML de #ab h3.tiger es <a href="#">t<i id="pma-vqb">x</i></a>. El texto de la td de columna es c<b id=pma-col>y sin nodo b#pma-col.
SUCCESS phpmyadmin-vqb-xss h3-markup=yes col-escaped=yes how=live PMA-VQB-XSS-WITNESS
Escapar getTab() con $('<div/>').text(temp).html() o Functions.escapeHtml antes de concatenar en el <h3>. No confiar en la doble codificación de Twig de un atributo. Mantener el helper de nombre de columna como el modelo.
962857e4f63d42e38f11ff4d63f5e722018add76)js/src/designer/history.jslibraries/classes/Dbal/TableName.phpGNU Affero GPL v3.0. Ver LICENSE. Solo laboratorio en loopback. Sin garantía.