PHP2Shell 치명적 취약점 총정리: CVE-2026-109618 & CVE-2026-109619 긴급 기술 권고
PHP2Shell 치명적 취약점 총정리: CVE-2026-109618 & CVE-2026-109619 긴급 기술 권고
<time datetime="2026-10-23">검증일: 2026년 10월 23일</time> · 기준 자료: php.net 보안 공지, GitHub Security Advisories GHSA-w8hw-w9fx-j79w / GHSA-6wmf-4rv5-p3rp
2026년 10월 21일, PHP 코어 팀과 여러 보안 연구 기관은 PHP2Shell로 통칭되는 두 건의 치명적 취약점 CVE-2026-109618과 CVE-2026-109619를 공개했습니다. CVSS v4.0 기준 점수는 **9.2 (Critical)**이며, 공격자는 인증 없이 HTTP 요청만으로 웹 서버 또는 PHP-FPM 워커 프로세스 권한(www-data, nginx, apache)으로 임의의 운영체제 명령을 실행할 수 있습니다. 이 취약점군은 애플리케이션 코드(Laravel, Symfony, WordPress 플러그인)보다 한 계층 아래인 PHP 엔진의 C 코드에서 발생하기 때문에, 전통적인 입력 검증이나 프레임워크 미들웨어로는 방어가 불가능합니다.
1. 취약점 매트릭스와 핵심 사실
| 항목 | CVE-2026-109618 | CVE-2026-109619 |
|---|---|---|
| 취약점 분류 | OOM 처리 중 메모리 상태 오염 | 전역 상태 잔존 / Use-After-Free (UAF) |
| CVSS v4.0 | 9.2 (Critical) | 9.2 (Critical) |
| 인증 필요 여부 | 불필요 (Unauthenticated) | 불필요 (Unauthenticated) |
| 기본 노출 조건 | session.upload_progress 기본 활성화 | session.upload_progress 기본 활성화 |
| 트리거 시점 | 요청 본문 파싱 단계 (사용자 PHP 코드 실행 전) | 요청 본문 파싱 및 세션 해제 주기 |
| 영향 환경 | PHP-FPM, Apache mod_php, FrankenPHP, LiteSpeed | PHP-FPM, Apache mod_php, FrankenPHP, LiteSpeed |
| 영향 버전 | PHP 5.4–8.5 | PHP 5.4–8.5 |
중요한 사실은 영향 범위가 PHP 5.4부터 8.5까지라는 점입니다. 일부 요약 자료가 5.x–8.4로 축소 표기하는 경우가 있으나 이는 사실과 다릅니다. 8.5 브랜치도 8.5.12 이전 빌드는 동일하게 취약합니다. 또한 session.upload_progress는 PHP 5.4에서 도입된 이래 기본값이 On이었기 때문에, 별도로 끄지 않은 거의 모든 프로덕션 런타임이 공격면에 노출됩니다.
2. 공격 생명주기: 애플리케이션 실행 전에 침해가 끝난다
PHP2Shell의 본질적 위험은 언제 취약한 C 로직이 실행되는지에 있습니다. 아래 다이어그램은 WAF와 프레임워크 필터가 왜 물리적으로 우회되는지를 보여줍니다.
[ 외부 HTTP POST 요청 도착 ]
(경로 무관: /index.php, /login, 존재하지 않는 /404)
│
▼
┌─────────────────────────────────────────────────────────┐
│ PHP 엔진 SAPI 계층 (C 언어 코어) │
│ - Content-Type: multipart/form-data 파싱 시작 │
└───────────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ext/session/session.c :: php_session_rfc1867_callback() │
│ - PHP_SESSION_UPLOAD_PROGRESS 파라미터 탐지 │
│ - 내부 원시 포인터 cached_post_bytes 캐싱 │
│ - [치명 결함] OOM/세션 리셋으로 청크 해제 │
│ - 해제된 메모리를 가리키는 Dangling Pointer 생성 │
└───────────────────────────┬─────────────────────────────┘
│
┌────────────────┴────────────────┐
▼ ▼
[ 정상 사용자 요청 ] [ PHP2Shell 익스플로잇 ]
애플리케이션 코드 실행 공격자가 Fake zval 스프레이
(Laravel, WordPress) vtable 탈취 → system() 호출
│ │
▼ ▼
정상 웹 응답 [ RCE 달성 ]
(사용자 랜드 코드 실행 이전!)
요약하면 다음과 같습니다.
- 실행 전 트리거: 콜백은
index.php가 실행되기 전, HTTP 스트리밍 단계에서 동작합니다. - 폼 존재 불필요: 대상 URL이 파일 업로드를 받지 않아도, 404 핸들러여도 파서는 호출됩니다.
- 세션 미사용 무관: 애플리케이션이
session_start()를 호출하지 않아도 취약 경로가 활성화됩니다.
3. 근본 원인: cached_post_bytes와 longjmp
ext/session/session.c의 업로드 진행 콜백은 세션 해시 테이블 내부의 zval을 가리키는 원시 포인터를 프로세스 전역 정적 변수에 저장합니다.
/* ext/session/session.c (개념 축약) */
static zval *cached_post_bytes;
static zval *cached_file_bytes;
void php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra) {
/* 세션 HashTable 내부 엔트리의 주소를 그대로 캐싱 */
cached_post_bytes = &entry_of(session_ht, "bytes_processed");
cached_file_bytes = &entry_of(session_ht, "files[0].bytes_processed");
/* 스트리밍 중 지속적으로 역참조하여 갱신 */
*cached_post_bytes += chunk_len;
}
세션 재적재(session_reread)나 zend_hash_update()로 엔트리가 교체되면 zval_ptr_dtor가 원본 청크를 Zend Memory Manager(ZMM)의 FreeBin으로 반환합니다. 그러나 전역 포인터는 초기화되지 않습니다. 이후 도착한 청크는 해제된 주소에 그대로 기록됩니다.
*cached_post_bytes += chunk_len; /* 전형적인 UAF 쓰기 */
CVE-2026-109618은 다른 경로로 같은 지점에 도달합니다. memory_limit 초과 시 PHP는 longjmp() 기반 bailout을 수행하는데, RFC1867 멀티파트 구조체가 완전히 초기화되기 전에 OOM이 발생하면 정리 루틴이 부분 초기화된 구조체를 해제·재사용하면서 힙 블록 메타데이터가 어긋납니다. 두 취약점 모두 결과적으로 공격자가 제어하는 바이트가 해제된 ZMM 청크에 덮어써지는 상태를 만듭니다.
4. 공식 수정 버전과 EOL 분기
| PHP 분기 | 취약 범위 | 최초 안전 버전 | 조치 |
|---|---|---|---|
| PHP 8.5 | < 8.5.12 | 8.5.12 | 즉시 업그레이드 |
| PHP 8.4 | < 8.4.27 | 8.4.27 | 즉시 업그레이드 |
| PHP 8.3 | < 8.3.36 | 8.3.36 | 즉시 업그레이드 |
| PHP 8.2 | < 8.2.35 | 8.2.35 | 보안 지원 기간 내 업그레이드 |
| PHP 8.1 / 8.0 / 7.4 이하 | 전체 | 공식 릴리스 없음 | 배포판 backport 적용 또는 우회책 |
PHP 8.1 이하 브랜치는 upstream 지원이 종료(EOL)되어 공식 마이너 릴리스가 나오지 않습니다. Ubuntu Pro, Debian LTS, CloudLinux, RHEL 계열의 vendor backport를 적용하거나, 최소한 5장의 임시 우회책을 즉시 적용해야 합니다.
5. 즉시 대응 체크리스트
5.1 런타임 버전 확인
# CLI 및 FPM 바이너리 버전
php -v
php-fpm -v
# Debian/Ubuntu 패키지 버전
dpkg -l | grep -E '^ii\s+php[0-9.]*-(fpm|cli|common)'
# RHEL/CentOS/Rocky/AlmaLinux
rpm -qa | grep -i '^php'
# 패치 이력에 CVE 번호가 포함되었는지 확인
zgrep -E "CVE-2026-10961[89]" /usr/share/doc/php*/changelog* 2>/dev/null
# FPM 설정 문법 검사
php-fpm -t
5.2 긴급 무중단 우회 (패치 전)
전역 php.ini 또는 FPM 풀 설정에서 업로드 진행 콜백을 끕니다.
; /etc/php/8.x/fpm/php.ini
session.upload_progress.enabled = Off
; /etc/php/8.x/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
sudo systemctl reload php8.3-fpm
# 또는 직접 시그널
sudo kill -USR2 "$(cat /var/run/php-fpm.pid)"
php -r 'var_dump(ini_get("session.upload_progress.enabled"));'
이 설정은 파일 업로드 자체를 막지 않습니다. 브라우저의 HTML5 File API, Axios 업로드 이벤트는 영향을 받지 않으며, 오직 레거시 진행률 표시 콜백만 비활성화됩니다.
5.3 침해 흔적 점검
# 웹 루트 내 최근 생성/변조 파일
find /var/www -type f -mtime -2 -ls 2>/dev/null | head -50
# www-data 권한으로 실행된 의심 프로세스
ps -eo pid,user,lstart,cmd | awk '$2=="www-data"||$2=="nginx"'
# 아웃바운드 연결 (리버스 셸 탐지)
ss -tpn | grep -E 'ESTAB' | grep -E 'www-data|php-fpm'
6. 자주 묻는 질문
Q. session.upload_progress.enabled = Off가 정상 업로드를 깨뜨리나요?
아니요. 이 지시자는 PHP 5.4 시절의 진행률 표시 콜백만 제어합니다. 실제 파일 저장, $_FILES 처리, 임시 파일 경로에는 영향이 없습니다.
Q. WAF만으로 충분한가요?
충분하지 않습니다. 실제 공격 페이로드는 500바이트 미만인 경우가 많아 본문 크기 제한(client_max_body_size)으로는 걸러지지 않으며, 요청이 애플리케이션 라우팅 이전에 파싱되므로 시그니처 기반 차단은 우회되기 쉽습니다. 근본 해결은 패치 또는 엔진 레벨 비활성화입니다.
Q. PHP 8.5.12는 8.5.11 대비 어떤 수정이 포함되나요? RFC1867 콜백의 전역 포인터 수명 관리와 OOM bailout 경로의 정리 순서가 수정되었습니다. 8.4.27, 8.3.36, 8.2.35도 동일한 수정을 각 브랜치에 백포트한 릴리스입니다.
7. 참고 자료
- php.net 보안 릴리스 공지 (2026-10-21)
- GitHub Security Advisory GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
- GitHub Security Advisory GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- Ubuntu Security Tracker, Debian Security Tracker, Red Hat CVE Database
- 본 사이트의 진단 도구:
https://php2shell.run/checker/
본 문서는 방어와 패치 목적의 기술 자료입니다. 실제 익스플로잇 코드는 제공하지 않으며, 운영 환경 점검은 반드시 변경 관리 절차에 따라 수행하십시오.