취약점 사전 유출: 패치보다 나흘 앞서 시작된 실제 공격
2026년 10월 21일, PHP 개발팀은 원격 코드 실행(RCE)을 유발하는 두 건의 치명적 결함인 CVE-2026-109618과 CVE-2026-109619에 대한 보안 업데이트를 배포했습니다. 두 취약점 모두 CVSS v4.0 기준 9.2점을 기록했습니다. 수정 버전은 PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12에 적용되었으며, 보안 팀은 이미 공식 지원이 종료된 7.4, 8.0, 8.1 버전에 대해서도 이례적으로 소스 코드 레벨의 백포트 패치를 제공했습니다.
보안 업계의 관례적인 보안 취약점 조정 공개(CVD) 모델은 외부 공격이 시작되기 전 방어자에게 패치가 전달되는 것을 전제로 합니다. 그러나 이번 ‘PHP2Shell’ 사태에서는 이 질서가 완전히 무너졌습니다. 공식 패치가 공개되기도 전에 보안 업체 헌트리스(Huntress)는 취약점 정보가 비공식 경로로 유출되었음을 경고하는 긴급 보고서를 게시했습니다. PHP 관리 팀 역시 이례적으로 php.announce 509호 공지를 띄워 전 세계 시스템 관리자들에게 사전 대비를 촉구했습니다. 아스 테크니카(Ars Technica)는 유지보수 팀이 이처럼 사전 경보를 발령한 것은 취약점 통제권이 이미 상실되었음을 뜻한다고 분석했습니다.
위협은 공식 패치를 기다려주지 않았습니다. 데이터독 보안 연구소(Datadog Security Labs)는 취약점 공식 발표 전 자체적으로 실증 공격 도구를 제작하는 데 성공했습니다. AWS 위협 인텔리전스 팀은 자체 허니팟 맷팟(MadPot)을 통해 10월 17일부터 이미 실제 공격 시도가 발생하고 있었음을 확인했습니다. 제로데이 공격이 공식 발표보다 나흘이나 앞서 작동하고 있었던 것입니다. 방어자가 사전 권고를 인지하기도 전에 인터넷에 노출된 서버들은 무차별 정찰과 침투 공격을 받고 있었습니다.
보안 기업 산섹(Sansec)은 패치 배포 후 15분 이내에 인터넷 전역을 대상으로 한 대규모 자동화 공격이 개시될 것이라고 경고했습니다. 10월 21일, 데이터독은 패치스택(Patchstack)의 텔레메트리 데이터를 인용하여 실제 공격 시도와 대규모 무차별 스캔이 이미 인터넷망 전반에 퍼지고 있음을 확인했습니다. 공격자와 방어자가 동시에 패치 코드를 받아보게 되면서, 방어자가 점검과 대응에 활용하던 업데이트 유예 기간은 순식간에 사라졌습니다.
그림: Datadog Security Labs가 발표한 PHP2Shell 영향 범위 및 공격 체인 개요. 출처: Datadog Security Labs
애플리케이션 코드 도달 전 격파: 파서 정리 루틴에서 곧바로 셸 탈취
대다수 웹 애플리케이션 보안 체계는 API 게이트웨이 인증, 라우팅 인가, 소스 코드 감사를 중심으로 설계됩니다. 그러나 데이터독 보안 연구소의 기술 검증에서 PHP2Shell은 이러한 애플리케이션 계층의 모든 방어벽을 우회했습니다. PHP 8.5.11, 아파치 프리포크(mod_php), memory_limit=16M, post_max_size=64M 환경에서, 공격자는 인증되지 않은 조작된 단 한 번의 HTTP POST 요청만으로 호스트 시스템 권한을 장악할 수 있었습니다.
취약점 발동은 PHP 런타임의 C 언어 엔진 내부에서 일어납니다. 악성 요청이 서버에 도달하는 즉시 파싱 실패가 발생합니다. 이 시점에서는 제어 흐름이 그 어떤 비즈니스 스크립트에도 넘어가지 않은 상태입니다. 문제는 파싱에 실패한 비정상 요청을 정리하는 PHP 내부 루틴에서 발생합니다. 정리 과정에서 공격 페이로드에 삽입된 시스템 명령이 그대로 실행됩니다. 서버에 고정된 텍스트 하나만 출력하는 단순한 스크립트만 올려두었더라도, 파서가 오류를 처리하는 순간 서버 전체가 공격자에게 넘어가게 됩니다.
이 공격은 현존하는 주요 웹 서버 배포 구조 전반에 영향을 미칩니다. 헌트리스 보고서와 분석 자료에 따르면 PHP 5부터 PHP 8에 이르는 광범위한 버전이 취약합니다. 영향받는 환경은 아파치 mod_php, 레거시 php-cgi, 대규모 프로덕션의 표준인 PHP-FPM, 신형 웹서버인 프랑켄PHP(FrankenPHP), 라이트스피드(LiteSpeed)를 모두 아우릅니다. 파싱 단계에서 공격이 트리거되므로 애플리케이션 레벨의 인증은 무용지물입니다.
그림: 요청 파싱 단계의 오류 정리 루틴에서 시스템 명령이 실행되는 호출 흐름. 출처: Datadog Security Labs
메모리 할당보다 앞선 카운터 증가: 이중 CVE 상태 잔존 분석
두 CVE는 HTTP 요청 처리 생명주기의 서로 다른 설계 결함을 파고듭니다. CVE-2026-109618의 핵심은 메모리 할당 실패 후 실행되는 예외 정리 루틴이 무효화된 데이터 구조를 유효한 것으로 잘못 신뢰한다는 점입니다. 취약점 유발 지점은 젠드 엔진(Zend Engine) 핵심 함수 _zend_hash_str_add_or_update_i()에 있습니다. 이 함수는 해시 테이블에 키-값 쌍을 저장할 때, 키 문자열을 저장할 메모리를 할당하기 전에 내부 카운터를 먼저 증가시킵니다.
만약 키 이름을 위한 메모리 할당이 memory_limit 한도를 초과하면 젠드 엔진은 즉시 치명적 오류(Fatal Error)를 발생시킵니다. 그러나 이때 내부 카운터는 아직 초기화되지 않은 버킷 노드를 이미 포함하고 있습니다. 이 노드 내부에는 이전 힙 메모리 작업에서 남은 오염된 바이트(더티 바이트)가 그대로 남아 있습니다. 이후 실행되는 정리 절차는 이 잔여 바이트를 정상적인 변수로 간주하고 메모리 해제를 시도하며, 공격자는 이 메모리 잔여물을 제어해 system() 시스템 호출을 유도할 수 있습니다. 최신 PHP 8.5.12에서도 카운터를 먼저 올리는 구조 자체는 유지되고 있으며, 공식 패치는 메모리 할당 실패 시 불완전한 항목이 남지 않도록 오류 처리 흐름을 수정하는 방식으로 대응했습니다.
또 다른 취약점인 CVE-2026-109619는 워커 프로세스가 여러 요청 사이에서 이전 상태를 재사용하는 특성을 파고듭니다. PHP 8.5 환경의 mod_php에서는 치명적 오류 발생 시 생성되는 역추적(backtrace) 정보가 직접적인 임의 명령 실행으로 이어질 수 있습니다. 또한 보편적인 멀티파트 파일 업로드 처리 과정에서, 치명적 오류로 인해 완전히 정리되지 않은 세션이나 디코더 상태가 다음 요청으로 전파됩니다. 많은 배포판에서 기본 활성화된 session.upload_progress.enabled 옵션은 애플리케이션이 session_start()를 명시적으로 호출하지 않아도 백그라운드에서 항상 동작합니다.
CVE-2026-109619를 성공적으로 악용하려면 연속된 악성 요청이 동일한 워커 프로세스에 할당되어야 합니다. 멀티 프로세스 풀이나 대규모 트래픽 클러스터에서는 단일 공격 성공 확률이 분산되지만, 이는 통계적 확률의 문제일 뿐 취약점 자체의 위험성을 낮추지는 못합니다.
그림: 워커 프로세스가 요청 간 오염된 메모리를 재사용하여 발생하는 상태 전이 메커니즘. 출처: Datadog Security Labs
업로드 진행률 옵션 비활성화로는 역부족: 설정 변경으로 가릴 수 없는 메모리 결함
취약점이 알려진 직후 커뮤니티에서는 임시방편으로 session.upload_progress.enabled = 0으로 변경하라는 대응책이 공유되었습니다. 그러나 이 설정 변경에는 치명적인 방어 공백이 존재합니다. 이는 CVE-2026-109619의 일부 업로드 경로만 차단할 뿐, CVE-2026-109618의 근본적인 메모리 할당 결함을 막아내지 못합니다. 메모리 할당 결함을 방치한 채 업로드 기능만 끄는 것은 시스템 위험을 전혀 해소하지 못합니다.
이에 PHP 공식 개발팀은 엄격한 임시 완화 지침을 발표했습니다. CVE-2026-109618에 대해서는 memory_limit = -1 설정을 권장했습니다. 동시에 관리자는 리버스 프록시 단에서 HTTP 요청 본문 크기를 엄격히 제한하고, 컨테이너 계층에서 cgroups를 통해 자원 상한선을 적용해야 합니다. 시스템 운영자는 RLIMIT_AS나 RLIMIT_DATA 같은 프로세스 단위 메모리 할당 제한을 적용해서는 안 되며, 이러한 제한은 공격자가 익스플로잇에 필요로 하는 메모리 할당 실패를 오히려 인위적으로 유발하기 때문입니다.
CVE-2026-109619에 대한 대응으로는 업로드 진행률 기능을 끄는 것 외에도 PHP 8.5 환경에서 fatal_error_backtraces = 0 설정을 적용해야 합니다. 이러한 모든 완화 설정은 PHP가 요청을 파싱하기 전에 완전히 로드되어야 하므로 각 워커 프로세스를 반드시 재시작해야 합니다. 유럽연합 침해사고대응기구(CERT-EU)는 2026-018 보안 권고를 통해 즉각 패치가 불가능한 시스템은 네트워크로부터 격리할 것을 권고했습니다. 데이터독 역시 리버스 프록시나 경계 방화벽에서 비정상적인 요청을 선제적으로 차단할 것을 당부했습니다.
40년간 누적된 기술 부채의 청산: 런타임 즉각 교체만이 유일한 해결책
PHP2Shell의 전개 과정을 돌아보면, 무너진 것은 단순한 PHP 파서의 방어선뿐만이 아닙니다. 벤더가 패치를 먼저 내놓고 관리자가 시간을 두고 시스템을 업데이트하던 오랜 보안 공조 모델 자체가 무너졌습니다. 공식 발표 전 취약점 유출과 나흘간 이어진 사전 공격으로 방어자가 확보할 수 있었던 안전 완충 지대는 완전히 사라졌습니다. 공격자와 방어자가 동시에 패치 코드를 받아보게 되면서, 대규모 자동화 공격은 패치 공개 후 불과 몇 분 만에 시작되었습니다.
IEEE 스펙트럼(IEEE Spectrum)은 이번 사건을 두고 인공지능 기술의 발전이 지난 40년간 방치되었던 소프트웨어 기술 부채를 한꺼번에 청산하도록 강요하고 있다고 지적했습니다. PHP2Shell은 그 대표적인 단면입니다. 메모리 할당보다 카운터를 먼저 올리는 코딩 패턴은 수십 년간 의심받지 않고 유지되었습니다. 최신 PHP 8.5.12의 공식 수정 패치조차 카운터 증가 순서 자체를 재설계하지 못하고 주변 오류 처리 로직을 손보는 데 그쳤습니다.
침해 여부를 점검해야 하는 시스템 운영자는 세 가지 대표적인 징후를 면밀히 살펴야 합니다. 첫째, HTTP 요청 초기화 단계에서 급증하는 메모리 고갈(OOM) 오류입니다. 둘째, 비정상적으로 긴 필드명이나 기형적인 값을 포함한 대용량 폼 전송입니다. 셋째, 웹 서버 프로세스 계정 권한으로 비정상적으로 생성된 셸 자식 프로세스의 존재입니다. 이러한 이상 징후는 파서 계층을 뚫기 위한 악성 페이로드가 실행되었음을 나타냅니다.
대응 시간이 사라진 현실에서 설정 변경은 불안정한 임시 처방에 불과합니다. 리버스 프록시 차단으로 무차별 스캔을 일부 걸러낼 수는 있지만, 파서 내부의 취약점을 근본적으로 없앨 수는 없습니다. 모든 운영 환경에서 영향을 받는 런타임을 패치된 최신 버전으로 즉각 교체하는 것만이 방어자가 취할 수 있는 유일한 대책입니다.
참고 링크:
- Datadog Security Labs 취약점 분석 보고서
- PHP 공식 보안 업데이트 공지 및 php.announce 509호
- Huntress 위협 인텔리전스 보고서
- AWS Security Blog 관측 기록
- Ars Technica 특집 기사
- CERT-EU 보안 권고 2026-018
- IEEE Spectrum 심층 분석 기사