abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · phpmyadmin-vqb-xss
技术分析文章:https://abraxaslabs.tech/research/phpmyadmin-vqb-xss
类别: 存储型 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。
phpMyAdmin 源中的存储型 XSS。与 PMASA-2025-1 同类,但功能不同。check-tables 的标题已被转义。此路径未被转义。
| ID | 暂无 CVE |
| 类别 | 存储型 XSS |
| 可达性 | 远程(已认证的 CREATE TABLE;受害者打开 Designer) |
| CWE | CWE-79 |
| CVSS | 高危: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) |
| 认证 | 已认证。攻击者需要在受害者在 Designer 中打开的数据库上拥有 CREATE TABLE 权限 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1 |
植入一个 HTML 表名,然后等待更高权限的用户在该数据库上打开 Designer 并渲染可视化查询历史。
| 权限 | 方法 / 汇聚点 | 侦察 / 完整性 / 可用性 | 实验验证或推断 |
|---|---|---|---|
| 向 phpMyAdmin 源注入标记 | DesignerHistory.display() 将 getTab() 拼接到 #ab 的 innerHTML 中 | 受害者源的完整性 | 实验验证。phpmyadmin:5.2.3 上运行中的 history.js。<h3> 包含 <i id="pma-vqb"> |
突破 designer 的 table_name 属性 | database_tables.twig 的 small_tab_pref_1 使用 designerTable.getTableName() | 在 Designer DOM 中侦察原始 MySQL 名称 | 实验验证,可作为原始标识符读取 |
读取受害者会话 cookie 和 CSRF token | 历史渲染后的同源脚本 | 侦察受害者 PMA 会话 | 从 PMA 源中的存储型 XSS 推断。本实验使用 <i> 标记,而非脚本 |
| 以受害者 MySQL 账户执行 SQL、管理用户、导出 | 带令牌的 POST /sql、/server/privileges、导出 | 该 MySQL 账户可触及的任何内容的完整性 | 推断。与 PMASA-2025-1 影响类别相同 |
| 更改 2FA / userconfig | 以受害者身份 POST /config/set | 受害者偏好存储的完整性 | 推断。同类遗留问题,非本实验 |
本遗留问题已排除:未认证访问、操作系统 RCE,以及 VQB OK 按钮路径。.small_tab_pref_click_opt 会预先转义 table_name,Twig 又会自动转义一次,因此该点击目前会将实体送入 getTab()。这并非对 history.js 的修复。列单元格仍保持文本转义(c<b id=pma-col>y,没有 <b id="pma-col"> 节点)。
同一版本固定点:WebAuthn CustomServer 遗留问题。
我固定了 phpMyAdmin RELEASE_5_2_3。PMASA-2025-1/2/3 已在 5.2.2 中。Designer 仍然手工构建 VQB 历史 HTML。
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() 复制到 .small_tab_pref_1 上的 table_name 中。VQB 选项单元格使用同一名称的预转义副本。
已记录的弯路:将双重转义的选项按钮属性视为修复(并非如此——small_tab_pref_1 仍然暴露原始名称,而 history.js 才是汇聚点);将列辅助函数视为覆盖表名;声称存在未认证 XSS;一个反向 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,并以query=1打开 Designer。实时history.js 是来自容器的副本(h3-unescaped=yes,存在列辅助函数)。Chromium 使用页面上 getTableName()返回的表名调用DesignerHistory.display()`。
结果:#ab h3.tiger 的 innerHTML 为 <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
在拼接进 <h3> 之前,用 $('<div/>').text(temp).html() 或 Functions.escapeHtml 对 getTab() 进行转义。不要依赖 Twig 对单个属性的双重编码。保留列名辅助函数作为模型。
962857e4f63d42e38f11ff4d63f5e722018add76)js/src/designer/history.jslibraries/classes/Dbal/TableName.phpGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。