
phpMyAdmin 5.2.3 — высокий — удалённый — XSS — Designer Visual Query Builder
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · phpmyadmin-vqb-xss
Класс: Stored XSS Достижимость: Удалённая
phpMyAdmin 5.2.3 (RELEASE_5_2_3 / 962857e) - phpMyAdmin
js/src/designer/history.js строит историю визуальных запросов через innerHTML. Имена столбцов проходят через $('<div/>').text(...).html(). Имена таблиц — нет. TableName по-прежнему допускает < и >. Учётная запись с низкими привилегиями и правом CREATE TABLE на общей базе данных внедряет HTML-идентификатор. Когда пользователь с более высокими привилегиями открывает Designer, getTableName() уже находится на странице, и DesignerHistory.display() записывает его в #ab.
Stored XSS в origin phpMyAdmin. Тот же класс, что и PMASA-2025-1, но другая функция. Заголовки Check-tables были экранированы. Этот путь — нет.
| ID | CVE пока нет |
| Класс | Stored XSS |
| Достижимость | Удалённая (аутентифицированный CREATE TABLE; жертва открывает Designer) |
| CWE | CWE-79 |
| CVSS | High: 8.0 CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N |
| Продукт | phpMyAdmin |
| Затронуто | 5.2.3 тег RELEASE_5_2_3 (962857e4f63d42e38f11ff4d63f5e722018add76) |
| Аутентификация | аутентифицированная. Атакующему нужно право CREATE TABLE на базе данных, которую жертва открывает в Designer |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Внедрить HTML-имя таблицы, а затем дождаться, пока пользователь с более высокими привилегиями откроет Designer на этой базе данных и отрисует историю визуальных запросов.
| Привилегия | Метод / sink | Разведка / целостность / доступность | Доказано в лаборатории или выведено |
|---|---|---|---|
| Внедрение разметки в origin phpMyAdmin | DesignerHistory.display() конкатенирует getTab() в innerHTML #ab | Целостность origin жертвы | Доказано в лаборатории. Живой history.js на phpmyadmin:5.2.3. <h3> содержал <i id="pma-vqb"> |
Выход за пределы атрибута table_name в designer | database_tables.twig small_tab_pref_1 использует designerTable.getTableName() | Разведка сырого имени MySQL в DOM Designer | Доказано в лаборатории, читается как сырой идентификатор |
Чтение cookie сессии жертвы и CSRF token | Скрипт того же origin после отрисовки истории | Разведка сессии PMA жертвы | Выведено из stored XSS в origin PMA. В этой лаборатории используется маркер <i>, а не скрипт |
| Выполнение SQL, управление пользователями, экспорт от имени учётной записи MySQL жертвы | POST /sql, /server/privileges, экспорт с токеном | Целостность всего, к чему имеет доступ эта учётная запись MySQL | Выведено. Тот же класс воздействия, что и PMASA-2025-1 |
| Изменение 2FA / userconfig | POST /config/set от имени жертвы | Целостность хранилища настроек жертвы | Выведено. Смежный остаток, не эта лаборатория |
Закрыто в этом остатке: неаутентифицированный доступ, OS RCE и путь кнопки OK в VQB. .small_tab_pref_click_opt предварительно экранирует table_name, и Twig автоматически экранирует его снова, поэтому этот клик в настоящее время передаёт сущности в getTab(). Это не исправление history.js. Ячейки столбцов остаются текстово экранированными (c<b id=pma-col>y, узла <b id="pma-col"> нет).
Тот же пин: WebAuthn CustomServer leftover.
Я зафиксировал phpMyAdmin RELEASE_5_2_3. PMASA-2025-1/2/3 уже присутствуют в 5.2.2. Designer по-прежнему собирает HTML истории VQB вручную.
js/src/designer/history.js (поставляется как 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 проверяет только непустоту, максимум 64, отсутствие завершающего пробела. < и > допустимы. MySQL принимает их в заключённом в кавычки идентификаторе.
templates/database/designer/database_tables.twig копирует designerTable.getTableName() в table_name на .small_tab_pref_1. Ячейка опции VQB использует предварительно экранированную копию того же имени.
Уже зафиксированные ошибочные пути: трактовка дважды экранированного атрибута кнопки-опции как исправления (это не так — small_tab_pref_1 по-прежнему раскрывает сырое имя, а history.js является sink); трактовка хелпера столбцов как покрывающего имена таблиц; заявление о неаутентифицированном XSS; reverse shell. Театр.
python3 phpmyadmin-vqb-xss-Abraxas-Labs.py
# or: cd lab && ./run.sh
Цель только http://127.0.0.1:19250. Образы phpmyadmin:5.2.3 и mysql:8.0. Compose-проект phpmyadmin-vqb-xss. Playwright Chromium в сервисе poc. Стек разбирается после запуска.
Два пользователя, общая shareddb. Низкопривилегированный creator выполняет CREATE TABLE \tx` (`cy` INT). Высокопривилегированный victimвходит в phpMyAdmin и открывает Designer сquery=1. Живой history.js — это копия из контейнера (h3-unescaped=yes, хелпер столбцов присутствует). Chromium вызывает DesignerHistory.display()с именем таблицы изgetTableName()` на странице.
Результат: innerHTML #ab h3.tiger равен <a href="#">t<i id="pma-vqb">x</i></a>. Текст td столбца — c<b id=pma-col>y без узла b#pma-col.
SUCCESS phpmyadmin-vqb-xss h3-markup=yes col-escaped=yes how=live PMA-VQB-XSS-WITNESS
Экранируйте getTab() с помощью $('<div/>').text(temp).html() или Functions.escapeHtml перед конкатенацией в <h3>. Не полагайтесь на двойное кодирование одного атрибута в Twig. Сохраните хелпер имени столбца как образец.
962857e4f63d42e38f11ff4d63f5e722018add76)js/src/designer/history.jslibraries/classes/Dbal/TableName.phpGNU Affero GPL v3.0. См. LICENSE. Только loopback-лаборатория. Без гарантий.