exploitation php2shell aslr bypass

정보 유출 없이 ASLR 우회하기: 배포판 바이너리 강화 매트릭스와 fork 악용

php2shell.run Research Lab •

정보 유출 없이 ASLR 우회하기: 배포판 바이너리 강화 매트릭스와 fork 악용

<time datetime="2026-10-23">검증일: 2026년 10월 23일</time>

Address Space Layout Randomization(ASLR)은 고전적 익스플로잇의 성공률을 낮추는 핵심 완화 기법입니다. 그러나 CVE-2026-109619 계열의 PHP2Shell 공격 체인은 대화형 정보 유출 없이도 목표를 달성하는 변형을 사용합니다. 이 문서는 그 물리적 메커니즘과, 배포판별로 실제 방어 효과가 어떻게 달라지는지를 정리합니다.


1. 유출 없는 하이재킹의 물리적 메커니즘

1.1 부분 포인터 재사용

64비트 리눅스에서 사용자 공간 주소는 47비트 수준으로 제한됩니다. ASLR은 상위 비트를 무작위화하지만, 하위 12비트(페이지 오프셋) 는 고정입니다. 공격자는 다음 전략으로 주소 공간을 좁힙니다.

  1. 하위 12비트가 알려진 함수/객체를 대상으로 선택
  2. 상위 비트는 포크 상속으로 예측
  3. 실패 시 워커 크래시 후 재시도 (brute force)

1.2 PIE 미적용 실행 파일

일부 배포판 패키지는 성능 또는 호환성 이유로 php-fpm 바이너리에 PIE를 적용하지 않거나, 메인 실행 파일만 PIE이고 확장 모듈은 고정 주소에 매핑됩니다. 이 경우 엔진 내부 함수의 주소가 예측 가능해집니다.

# 바이너리 보호 기법 확인
checksec --file=/usr/sbin/php-fpm8.3
# 또는
readelf -h /usr/sbin/php-fpm8.3 | grep Type
readelf -l /usr/sbin/php-fpm8.3 | grep -i GNU_RELRO

Type: EXEC이면 비PIE, Type: DYN이면 PIE입니다. 배포판과 빌드 옵션에 따라 결과가 다릅니다.

1.3 fork() 메모리 상속

PHP-FPM 마스터는 시작 시 자식 워커를 fork()합니다. 자식은 부모의 메모리 레이아웃을 복사하므로, 모든 워커가 동일한 라이브러리 base 주소를 공유합니다. 공격자가 한 워커에서 주소를 추론하거나 한 번의 크래시로 정보를 얻으면, 나머지 워커에도 동일하게 적용됩니다.

# FPM 마스터-워커 구조 확인
ps -eo pid,ppid,user,cmd | grep -E 'php-fpm' | head -20
php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)   PID 1200
 ├─ php-fpm: pool www                                    PID 1201
 ├─ php-fpm: pool www                                    PID 1202
 └─ php-fpm: pool www                                    PID 1203

ppid가 동일한 것을 확인할 수 있습니다. 이 구조는 성능에는 유리하지만, ASLR 엔트로피를 워커 풀 전체가 공유하게 만듭니다.


2. 리눅스 배포판 강화 매트릭스

배포판php-fpm PIEFull RELROStack Canarylibc 버전PHP2Shell 완화 수준
Ubuntu 22.04 LTS대체로 DYN부분적용2.35중간 — backport 필수
Ubuntu 24.04 LTSDYN적용적용2.39중간 — 8.3.36+ 권장
Debian 12 (bookworm)DYN적용적용2.36중간 — 8.2.35+ 권장
RHEL 9 / Rocky 9DYN적용적용2.34중간 — vendor 패치 확인
Alpine 3.20 (musl)DYN적용적용musl 1.2상이 — musl 레이아웃
컨테이너 이미지이미지별 상이이미지별 상이이미지별 상이이미지별 상이낮음 — 베이스 이미지 고정 위험

배포판의 PHP 패키지가 항상 최신 보안 브랜치를 따르지는 않습니다. Ubuntu 22.04의 기본 PHP 8.1은 upstream EOL이지만, Ubuntu Security Team이 backport를 제공합니다. 따라서 "PHP 버전"만 보지 말고 "패키지 릴리스 노트의 CVE 수정 여부" 를 확인해야 합니다.

# Ubuntu/Debian: backport 반영 여부
apt changelog php8.1-fpm 2>/dev/null | grep -i 'CVE-2026-10961'
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.1-fpm/changelog.Debian.gz 2>/dev/null

# RHEL 계열
rpm -q --changelog php-fpm | grep -i 'CVE-2026-10961'

# 컨테이너 이미지
docker run --rm php:8.3-fpm php -v
docker inspect php:8.3-fpm --format '{{json .Config.Labels}}'

3. 마스터-워커 fork 모델 악용

공격자는 다음과 같은 순서로 fork 상속을 활용합니다.

  1. 레이아웃 학습: 한 워커에 저위험 요청을 보내 오류 메시지나 타이밍 차이로 base 주소를 추정합니다.
  2. 재시도 루프: 실패한 요청은 워커를 크래시시키지만, FPM 마스터가 즉시 새 워커를 생성합니다. 새 워커는 동일한 부모 레이아웃을 상속합니다.
  3. 성공 확률 누적: 브루트 포스가 반복될수록 성공 확률이 선형으로 증가합니다. 초당 수백 요청이 가능한 환경에서는 수 분 내 성공할 수 있습니다.
# FPM 로그에서 비정상 재시작 패턴 확인
journalctl -u php8.3-fpm --since "30 min ago" | \
  grep -E 'WARNING|seems busy|SIGSEGV|SIGABRT|terminated'

# 재시작 빈도 집계
journalctl -u php8.3-fpm --since "1 hour ago" --output=json | \
  jq -r 'select(.MESSAGE | test("exited|terminated")) | .__REALTIME_TIMESTAMP' | wc -l

4. 방어: 크래시 스로틀과 재시작 임계값

4.1 FPM 재시작 임계값

; /etc/php/8.3/fpm/pool.d/www.conf
; 짧은 시간 내 워커가 반복 종료되면 마스터가 재시작을 중단
emergency_restart_threshold = 5
emergency_restart_interval = 1m
process_control_timeout = 10s

; 워커 수명 제한으로 레이아웃 학습 창 축소
pm.max_requests = 300

emergency_restart_threshold를 초과하면 FPM은 재시작을 멈추고 관리자 개입을 요구합니다. 가용성과 보안의 트레이드오프이므로, 서비스 중요도에 따라 값을 조정하십시오.

4.2 ASLR 엔트로피 확인과 강화

# 커널 ASLR 설정 (2 = full randomization)
cat /proc/sys/kernel/randomize_va_space

# systemd 서비스에 추가 강화 옵션
sudo systemctl edit php8.3-fpm
[Service]
# 주소 공간 무작위화 강제 (이미 기본이지만 명시)
Environment="LD_PRELOAD="
# 코어 덤프 제한 (주소 노출 방지)
LimitCORE=0
# 파일 시스템 보호
ProtectSystem=strict
PrivateTmp=yes
NoNewPrivileges=yes

4.3 바이너리 강화 점검 스크립트

#!/usr/bin/env bash
set -euo pipefail
BIN="${1:-/usr/sbin/php-fpm8.3}"
echo "[*] $BIN"
readelf -h "$BIN" | awk -F: '/Type/{print "  ELF Type:" $2}'
readelf -lW "$BIN" | grep -q GNU_RELRO && echo "  RELRO: yes" || echo "  RELRO: no"
readelf -dW "$BIN" | grep -q BIND_NOW && echo "  BIND_NOW: yes" || echo "  BIND_NOW: no"
readelf -sW "$BIN" | grep -q __stack_chk_fail && echo "  Canary: yes" || echo "  Canary: no"

5. 결론

ASLR은 만능 방어가 아닙니다. fork 기반 워커 모델은 무작위화 이점을 공유하고, 비PIE 바이너리와 작은 PHP 페이로드는 공격 비용을 낮춥니다. 가장 확실한 대응은 여전히 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 이상으로의 업그레이드이며, 그 전까지는 다음을 병행하십시오.

  1. session.upload_progress.enabled = Off
  2. FPM emergency_restart_threshold 설정과 pm.max_requests 축소
  3. checksec/readelf 기반 배포판 바이너리 강화 점검
  4. Suricata/Falco 상관 경보 운영