1. 개요
2026년 10월 21일, 전 세계 웹의 약 70%에서 사용 중인 프로그래밍 언어 PHP의 코어 엔진(Zend Engine)에서 2건의 치명적인 원격 코드 실행(RCE: Remote Code Execution) 취약점이 동시에 공개됐습니다. 보안 업계에서는 이 두 취약점을 묶어 'PHP2Shell'이라 부르고 있습니다.
이번 취약점은 특정 플러그인이나 프레임워크가 아닌 PHP 엔진(v5.4 ~ v8.5.11) 자체에 약 10년 이상 숨어 있던 근본적인 메모리 처리 결함입니다.
심지어 사용자가 작성한 PHP 애플리케이션 코드가 단 한 줄도 실행되기 전(인증 로직에 도달하기도 전)에 공격이 성립할 수 있어, CVSS v4.0 기준 9.2/10점(Critical)으로 평가됐습니다.
2. 사전 배경지식: PHP는 치명적 에러를 어떻게 처리할까? (longjmp)
두 취약점을 이해하려면 먼저 PHP 코어가 에러를 처리하는 방식을 알아야 합니다.
PHP는 사용자의 HTTP 요청을 처리하다가 메모리 한도 초과(OOM: Out-of-Memory)나 치명적인 에러(Fatal Error)를 만나면, 하던 작업을 즉시 멈추고 뒷정리(Cleanup) 단계로 넘어갑니다. 이때 C 언어의 longjmp() 함수를 호출해, 현재 실행 중인 콜 스택을 일일이 거슬러 올라가지 않고 단숨에 종료 루틴으로 '비상 탈출(점프)'합니다.
문제는 비상 탈출이 일어나는 그 찰나의 순간, 미처 정리되지 않거나 반쯤만 초기화된 메모리 상태가 그대로 방치된다는 점입니다. 이번 PHP2Shell의 두 취약점 모두 이 longjmp() 탈출 과정에서 발생합니다.
3. CVE-2026-109618: OOM 발생 시 초기화되지 않은 포인터 참조 (CWE-824)
비전공자를 위한 한 줄 비유
식당 점원이 손님의 주문서를 빈칸도 다 채우기 전에 상단에 '작성 완료' 도장부터 먼저 찍는 습관이 있었습니다. 해커가 엄청난 양의 주문을 쏟아내 점원이 과부하(메모리 부족)로 쓰러지자, 뒷정리 담당자가 도장만 보고 빈칸(해커가 조작한 가짜 데이터)을 정상 주문으로 착각해 그대로 실행해 버리는 상황입니다.
1) 핵심 원인
PHP가 HTTP 요청을 파싱하는 단계에서 내부 구조체를 사용하기 위해 여러 번의 연속적인 메모리 할당을 수행합니다. 이때 PHP 코어는 필요한 하위 메모리 할당이 모두 완료되기도 전에, 특정 상태 플래그나 구조체를 '초기화 완료(Ready for use)' 상태로 먼저 기록합니다.
2) 메모리 오염 과정
[정교한 요청 전송] ➔ [초기화 도중 OOM 유발] ➔ [longjmp() 강제 탈출] ➔ [오염된 포인터 역참조 및 RCE]
OOM 유발: 공격자가 정교하게 크기를 조절한 HTTP 요청을 전송하여, 상태 플래그는 활성화되었지만 하위 구조체 포인터 초기화는 끝나지 않은 순간의 할당 시점에 정확히 **OOM(Out-of-Memory)**을 유발합니다.
비상 탈출과 롤백 실패: OOM 핸들러가
longjmp()를 호출하여 파싱 컨텍스트를 즉시 탈출합니다. 이때 반쯤 초기화된 상태를 되돌리는 롤백(Rollback) 로직이 존재하지 않습니다.잘못된 뒷정리: 이어지는 요청 정리(Cleanup) 프로시저가 실행되면서 해당 구조체가 '초기화 완료' 상태라고 믿고, 초기화되지 않은 메모리를 유효한 포인터로 간주하여 읽기 또는 분기를 수행합니다.
원격 코드 실행(RCE): 공격자가 사전에 힙 레이아웃(Heap Layout)을 조작해 두었다면, 정리 루틴이 공격자가 통제하는 가짜 포인터를 역참조하게 되어 임의 코드 실행(RCE)으로 이어집니다.
4. CVE-2026-109619: 요청 간 잔류 포인터로 인한 Use-After-Free (CWE-416)
💡 비전공자를 위한 한 줄 비유
점원 한 명이 여러 손님을 순서대로 응대하며 수첩에 손님의 임시 사물함 번호를 적어둡니다. 앞선 해커 손님이 일부러 오류를 일으켜 쫓겨나면서 사물함은 비워졌는데, 점원의 수첩에 적힌 옛날 사물함 번호가 지워지지 않고 남았습니다. 다음 손님이 왔을 때 점원이 수첩의 옛날 번호를 그대로 열어보다가 해커가 심어둔 악성 명령을 실행하게 됩니다.
1) 핵심 원인
하나의 PHP 워커(Worker) 프로세스는 생성된 후 수백~수천 개의 HTTP 요청을 순차적으로 처리합니다. PHP 내부에는 워커 프로세스 단위로 유지되는 **전역 상태(Global State)**가 존재하며, 요청 처리 중에는 이 전역 변수가 현재 요청에 종속된 힙 메모리를 임시로 가리키게 됩니다.
에러 발생 시 요청 메모리는 해제(Free)되었지만, 이를 가리키던 전역 포인터가 초기화되지 않아 **이미 반납된 메모리를 가리키는 댕글링 포인터(Dangling/Stale Pointer)**가 남는 것이 핵심 원인입니다.
2) 메모리 오염(UAF) 발생 과정
[1차 요청: 에러 유발 및 메모리 해제] ➔ [전역에 댕글링 포인터 잔류] ➔ [2차 요청: 이전 포인터 재참조(UAF)]
1차 요청과 에러 발생: 첫 번째 요청 처리 도중 Fatal Error가 발생해
longjmp()로 컨텍스트를 탈출합니다.댕글링 포인터 잔류: 요청이 종료되면서 해당 요청에 할당되었던 메모리는 정상적으로 해제(Free)됩니다. 하지만 특정 에러 처리 경로에서 전역 상태 포인터가
NULL등으로 리셋되지 않거나, 리셋된 직후 종료 진단 과정에서 다시 해제된 메모리 주소로 채워지는(Repopulated) 결함이 발생합니다. 즉, 전역 공간에 댕글링 포인터(Dangling/Stale Pointer)가 남습니다.2차 요청과 UAF 트리거: 동일한 워커 프로세스가 두 번째 HTTP 요청을 받습니다. 정상적인 초기화 루틴이 전역 상태를 덮어쓰기 전에 실행되는 초기 단계에서 이 Stale 포인터를 참조하게 되어 Cross-Request Use-After-Free가 발생합니다.
3) 확인된 주요 공격 벡터 2가지
(PHP 공식 권고 및 Datadog 분석 기준)
업로드 처리 (
session.upload_progress등)파일 업로드 처리 중 치명적 에러가 발생하면 세션 또는 디코더 상태 포인터가 전역에 남습니다. 이후 동일 워커로 들어온 후속 업로드 요청이 정상 초기화 전에 이 상태를 읽으면서 RCE가 발생합니다 (
mod_php및PHP-FPM모두 영향).참고:
session.upload_progress는 PHP에서 기본 활성화(Default Enabled)되어 있으며, 애플리케이션 코드에서session_start()를 호출하지 않아도 동작합니다.
Fatal-Error 백트레이스 (PHP 8.5 한정)
PHP 8.5에서 치명적 에러 리포팅 시 생성된 백트레이스 상태가 셧다운 이후에도 클리어되지 않고 남습니다. 다음 요청에서 초기 설정 진단(Configuration diagnostic)이 실행될 때 초기화 전의 백트레이스 메모리를 읽어들여
mod_php환경에서 RCE를 유발합니다.
5. 실행 환경별 위험도 비교 (mod_php vs PHP-FPM)
웹 서버가 PHP를 구동하는 방식(SAPI)에 따라 공격 난이도와 영향 범위가 달라집니다.
구분 | Apache |
|
|---|---|---|
Pre-App 공격 가능 여부 | 가능 (앱 코드 실행 전 RCE) | 초기 파싱 단계 공격은 차단됨 |
CVE-2026-109618 동작 | Startup 중 OOM이 나도 워커가 죽지 않고 Cleanup을 수행하여 즉시 RCE 가능 | Startup 단계 OOM 발생 시 워커 프로세스 자체를 종료(Exit)시켜 차단 |
CVE-2026-109619 동작 | 동일 워커에 연속 요청 도달 시 즉시 악용 가능 | 동일 워커에 연속 요청이 도달하고 특정 작업 수행 시 RCE 가능 |
6. 해결 방법 및 대응 방안
1) 보안 패치 즉시 적용 (유일한 근본 해결책)
아래의 패치 버전으로 즉시 업데이트하고, 메모리에 상주 중인 기존 워커 프로세스를 교체하기 위해 반드시 php-fpm 또는 Apache를 재시작해야 합니다.
PHP 8.5.12 이상
PHP 8.4.27 이상
PHP 8.3.36 이상
PHP 8.2.35 이상
2) 임시 완화책 (패치 전 하드닝 가이드)
워커 프로세스 재사용 방지:
PHP-FPM설정 파일(www.conf)에서pm.max_requests = 1로 설정하면 워커가 1개의 요청만 처리한 뒤 재시작되므로, 요청 간 메모리가 재사용되는CVE-2026-109619공격을 원천 차단할 수 있습니다.잘못된 완화책 주의:
session.upload_progress.enabled = 0설정이나display_errors = Off설정만으로는 다른 파싱 경로 및 쿠키 기반 메모리 유출을 막을 수 없으므로 반드시 엔진 업데이트를 진행해야 합니다.
댓글
로그인 상태를 확인하고 있어요.