Study
PHP2Shell: PHP에서 발견된 CVSS 9.2 RCE 취약점 분석하기.ZIP
우리 회사도 PHP를 쓰는데요…

2026년 10월 21일, PHP에 보안 업데이트가 올라왔다.
- CVE-2026-109618
- CVE-2026-109619
둘 다 CVSS v4.0 기준 9.2 Critical 등급이고 공격에 성공하면 로그인이나 별도의 인증 없이 서버에서 임의 코드를 실행할 수 있는 RCE(Remote Code Execution) 취약점이다.
PHP 프로젝트는 해당 취약점을 수정한 보안 버전을 긴급하게 배포하면서 사실상 이렇게 이야기했다.
완전한 우회책은 없으니 업데이트하는 게 가장 안전하다.
실제로 PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12가 보안 릴리스로 배포됐다. 이번 건은 SQL Injection처럼 개발자가 쿼리를 잘못 짰다거나 파일 업로드 검증을 빼먹었다거나 하는 등의 단순한 문제가 아니다.
PHP 애플리케이션이 아니라 PHP 자체, 정확히는 PHP 엔진의 메모리 관리 쪽에서 발생한 문제다. 그리고 이 차이가 이번 취약점을 꽤 무섭게 만든다.
일단 RCE가 뭐냐면
RCE는 Remote Code Execution 말 그대로 원격 코드 실행이다. 공격자가 서버에 SSH로 로그인한 것도 아니고, 관리자 계정을 탈취한 것도 아닌데 외부에서 HTTP 요청을 보내는 것만으로 서버가 공격자가 원하는 코드를 실행하게 되는 취약점이다.
보안 취약점을 비교해보자면? 이런 느낌이다.
물론 실제 피해 범위는 서버 권한이나 격리 환경에 따라 달라진다. 그래도 PHP 프로세스가 실행되는 권한 범위 안에서 코드를 실행할 수 있다면 이후에는 설정 파일, 환경변수, DB Credential, API Key 같은 민감한 값에 접근하거나 웹 파일을 변조하는 식의 후속 공격으로 이어질 가능성이 생긴다. 그래서 보안 이슈에서 Unauthenticated RCE, 즉 인증조차 필요 없는 RCE가 나오면 위험도가 굉장히 높게 평가된다.
이번 두 CVE가 바로 여기에 해당한다. PHP 공식 발표에서도 두 취약점 모두 unauthenticated remote code execution으로 설명하고 있다.
그럼 PHP2Shell은 정확히 뭐가 문제였을까?
두 취약점의 세부 동작은 조금 다르지만 공통점이 있다. PHP가 HTTP 요청을 처리하다가 정상적인 흐름에서 벗어났을 때 내부 메모리 상태를 안전하게 정리하지 못했다.
정상적인 PHP 요청 처리는 대략 다음과 같다.

여기까지만 보면 별문제가 없다. 그런데 처리 도중 아주 특수한 조건에서 Fatal Error나 Out Of Memory 같은 상황이 발생하면 이야기가 달라진다. PHP는 중간에 작업을 멈췄는데,
“만들다 만 데이터”나 “이미 없어진 메모리를 가리키는 주소”가 내부에 남을 수 있었다.
그리고 그 상태가 나중에 다시 사용되면서 메모리 손상이 발생했다. 이게 이번 PHP2Shell의 핵심이다.
CVE-2026-109618 — 메모리가 부족해졌더염…
첫 번째는 CVE-2026-109618이다.
PHP 공식 설명은 이 취약점을 RCE via OOM corruption으로 분류하고 있다. 여기서 OOM은 Out Of Memory다. 즉 PHP가 메모리를 할당하다가 더 이상 메모리를 확보하지 못하는 상황을 말한다.
정상적인 경우라면:

요청 데이터 초기화 → 필요한 메모리 확보 → 처리 완료 → 메모리 정리 이 순서로 진행된다.
그런데 문제가 있는 상황에서는:

요청 데이터 초기화를 시작했는데 일부 데이터만 만들어지고, OOM이 발생하면 PHP 실행이 중단된다. 그렇게 클린업을 수행하면 완전히 만들어지지 않은 데이터를 정상 데이터라고 판단하게 된다. 결론은 메모리 손상되서 ㅂㅇㅂㅇ 하는 것임…
예를 들자면

창고 정리를 하는 프로그램이 있다고 해보자. 원래라면 물건을 전부 등록한 다음 마지막에 등록된 물건을 순서대로 치워야 한다. 그런데 등록 중간에 시스템이 멈췄다.
문제는 정리 프로그램이
“등록 목록에 있으니까 정상적인 물건이겠지.”
라고 생각하고 아직 제대로 만들어지지도 않은 데이터를 정상 데이터처럼 처리해버린다는 점이다. PHP에서도 이런 비슷한 문제가 발생한 거다.
PHP 공식 설명에 따르면 OOM 처리가 PHP 실행을 internal request data가 부분적으로만 초기화된 상태에서 중단시킬 수 있었고, 이후 request cleanup 과정에서 잘못된 데이터를 유효한 데이터로 취급할 수 있었다.
그리고 메모리 안전성 취약점에서는 이런 작은 착각 하나가 꽤 크게 번질 수 있다. 메모리 내용을 공격자가 원하는 형태로 어느 정도 유도할 수 있다면 단순 프로세스 Crash에서 끝나는 게 아니라 결국 RCE로 발전할 수 있기 때문이다.
Wiz 연구진은 실제 연구 과정에서 WordPress, Drupal, Joomla 환경을 이용해 인증 전 RCE를 시연했다. 중요한 건 여기서 WordPress 취약점이라는 뜻은 아니다. 이 CMS들은 취약점을 보여주기 위한 테스트 대상으로 사용됐을 뿐이고, 실제 취약한 코드는 PHP 자체에 있다.
CVE-2026-109619 — 이미 없어진 메모리를 다음 요청에서 다시 쓴다
두 번째인 CVE-2026-109619는 조금 더 흥미롭다.
PHP 공식 설명에서는 이를 RCE via stale global state라고 표현한다.
여기서 중요한 단어가 stale이다. 직역하면 오래돼서 더 이상 유효하지 않은 상태라는 뜻인데, 개발자 입장에서는 이미 폐기됐어야 하는 값이 남아있는 상태라고 생각하면 편하다. PHP-FPM 같은 환경에서는 보통 HTTP 요청 하나가 끝났다고 프로세스를 바로 죽이지 않는다.
Worker 하나가 살아있으면서 여러 요청을 계속 처리한다.!!!!
PHP Worker
│
├─ Request A
│
├─ Request B
│
├─ Request C
│
└─ Request D당연히 Request A가 끝나면 A에서 사용하던 요청 전용 상태는 깨끗하게 정리돼야 한다. 그런데 특정 Fatal Error 상황에서는 일부 PHP 내부 상태가 그대로 살아남을 수 있었다.
문제 상황을 단순화하면:
Request A
메모리 생성
↓
PHP 내부 객체가 메모리 주소를 기억
↓
Fatal Error 발생
↓
메모리는 해제됨
↓
그런데 내부 객체에는
예전 메모리 주소가 남아 있음여기서 Request A가 끝난다.
그리고 같은 Worker가 Request B를 처리한다.
Request B
↓
PHP가 이전 요청에서 남은 주소 사용
↓
이미 해제된 메모리에 접근보안에서 흔히 말하는 Use-After-Free 계열 문제다.
말 그대로:
Free한 메모리를 나중에 다시 사용한다.
Wiz가 공개한 세부 내용에서도 fatal error 이후의 backtrace 데이터나 OPcache 상태 등이 이런 문제와 연결되는 것으로 설명되어 있다. 연구진은 이 원시적인 메모리 오류를 발전시켜 WordPress와 Drupal 환경에서 인증 없는 코드 실행까지 시연했다.
그런데 이전 요청의 메모리가 남는 게 왜 RCE가 되는 걸까?
여기가 처음 보면 가장 이해하기 어려운 부분이다.
“옛날 메모리 좀 잘못 읽는다고 서버 명령까지 실행된다고?”

바로 RCE가 되는 건 아니다.
보통 메모리 취약점은 여러 단계를 거친다.

쉽게 말하면 프로그램이
“여기에는 A 타입 객체가 있을 거야.”
라고 믿고 특정 메모리를 읽었는데 사실 공격자가 그 영역을 다른 값으로 채워둔다면 문제가 생긴다. 프로그램은 공격자가 만든 데이터를 정상적인 내부 구조라고 착각할 수 있다. 그걸 여러 번 조합해 프로그램의 실행 흐름까지 제어하게 되면 결국 공격자가 원하는 코드를 실행할 수 있게 된다. 그래서 메모리 안전성 취약점은 처음 발견됐을 때 단순 Crash 정도로 보였다가 연구가 진행되면서 RCE로 발전하는 경우도 있다.
이번 PHP2Shell도 단순한 PHP Fatal error 문제가 아니라 복수의 메모리 안전성 버그를 연결해 실제 RCE까지 가능하다는 점이 입증됐기 때문에 Critical 등급까지 올라간 것이다.
더 무서운 건 애플리케이션 코드와 크게 상관없다는 것
이번 이슈에서 개인적으로 가장 중요한 부분은 이거라고 생각한다.
보통 PHP 보안 이슈라고 하면 이런 코드를 먼저 떠올린다.
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];그리고 바로 생각한다.
“아 SQL Injection이네.”
혹은:
include($_GET['page']);같은 코드를 보고
“경로 검증 안 했네.”
라고 할 수 있다. 이런 경우는 애플리케이션 코드가 문제다. 그런데 PHP2Shell은 다르다.
예를 들어 애플리케이션이 정말 단순하게:
<?php
echo "Hello World";이 정도뿐이라고 해도 취약한 PHP 런타임을 이용해서 HTTP 요청을 처리하고 있다면 문제를 완전히 배제할 수 없다.
왜냐하면 취약점이 Application Code 에 있는 게 아니라
HTTP Request
↓
PHP Runtime
↓
Request Parsing
↓
Memory Management
↓
Application Code여기서 PHP Runtime 영역에 있기 때문이다.
일부 취약 동작은 정상적인 애플리케이션 코드가 본격적으로 실행되기 이전의 요청 처리 단계와도 연관돼 있다.
그래서
“우리는 워드프레스 안 쓰는데요?”
도 충분한 확인 방법이 아니고,
“Laravel 최신인데요?”
도 마찬가지다. 확인해야 하는 건 우선 PHP 런타임 버전이다.
PHP2Shell이라는 이름은 어디에서 나왔을까
PHP2Shell은 연구진이 두 취약점을 결합해 만든 exploit chain에 붙인 이름이다.
즉:
PHP
↓
취약점 악용
↓
Shell / Code Execution이라는 의미를 담고 있다고 보면 된다. 그리고 이번 사건에서 흥미로운 부분이 하나 더 있다.
AI가 취약점 연구에 상당히 적극적으로 사용됐다는 점이다.
OpenAI 공개 내용에 따르면 Calif는 GPT-5.6 Sol을 활용한 연구 과정에서 PHP의 Critical 취약점을 발견했고, Wiz도 독립적으로 두 취약점을 발견해 PHP 프로젝트에 제보했다. 이후 PHP Maintainer가 실제 수정 작업을 주도했고 여러 보안 연구팀이 패치와 공개 준비 과정에 참여했다. Wiz 역시 자체 연구에서 여러 Frontier AI 모델을 취약점 탐색과 exploitability 분석에 활용했다고 공개했다. 특히 꽤 인상적인 부분은 이 취약점의 기반이 된 코드가 최근 들어 새로 들어간 게 아니라는 점이다.
Wiz에 따르면 관련 설계와 취약 코드의 일부는 오랜 기간 PHP 코드베이스에 존재해 왔는데, 실제 공격 가능한 형태까지 연결하는 과정이 상당히 복잡해서 그동안 현실적인 exploit으로 발전하지 못하고 있었다.
AI가 갑자기 버그를 만들어낸 건 아니고,
기존에 존재하던 복잡한 취약점의 탐색과 분석 비용을 크게 낮춘 것
에 더 가깝다.
이 부분은 앞으로 보안 업계에서도 꽤 중요한 변화가 될 것 같다.
영향받는 버전은?
PHP 공식 프로젝트에서 현재 지원하는 버전에 대해서는 다음 보안 릴리스가 배포됐다.
PHP 8.1 이하처럼 이미 PHP 프로젝트의 공식 지원이 종료된 버전은 PHP 프로젝트에서 직접 신규 릴리스를 제공하지 않는다. 배포판이나 LTS Vendor가 자체적으로 보안 패치를 Backport하는 경우도 있기 때문에 Ubuntu, RHEL 계열 등 패키지 관리자를 통해 PHP를 설치했다면 단순 버전 문자열만 보기보다 사용 중인 배포판의 Security Advisory도 함께 확인하는 게 안전하다.
그래서 뭘 해야 할까?
일단 현재 서버 버전을 확인한다.
php -v예를 들어:
PHP 8.3.35라면 공식 PHP 기준 패치 릴리스인:
PHP 8.3.36이상으로 업데이트해야 한다.
그런데 여기서 끝나면 안 된다. PHP-FPM이나 Apache Worker처럼 기존 PHP 프로세스가 계속 살아 있다면 업데이트된 바이너리가 실제 요청에 적용되지 않을 수 있기 때문이다. 따라서 패키지 업데이트 이후 실제 웹 요청을 처리하는 런타임을 재시작하고 버전을 다시 확인해야 한다.
예를 들어 PHP-FPM 환경이라면 배포판 구성에 따라:
sudo systemctl restart php8.3-fpm같은 작업이 필요할 수 있다.
그리고:
php -v뿐만 아니라 실제 웹 서버가 어떤 PHP-FPM 또는 PHP 바이너리에 연결돼 있는지도 확인해야 한다. 서버에 PHP가 여러 버전 설치돼 있으면 CLI의 PHP와 실제 서비스 PHP가 다른 경우도 있기 때문이다.
나름 익숙한 함정이다.
터미널: “8.3.36 맞는데요?”
이러면 패치했다고 생각하고 안심했는데 실제 서비스는 그대로일 수 있다.
WAF로 막으면 안 되나?
WAF나 Reverse Proxy에서 특정 공격 패턴을 차단하는 방법은 분명 도움이 된다. 실제 공격이 활발해질 때 긴급하게 시간을 벌 수 있는 방어 수단이기도 하다. 다만 PHP 프로젝트의 공식 입장은 꽤 명확하다.
완전한 mitigation은 없으며 업데이트가 안전한 해결책이다. 왜냐하면 이번 문제는 특정 문자열 하나를 차단하면 끝나는 단순한 공격 패턴이 아니라 PHP의 내부 메모리 처리와 관련된 문제이기 때문이다.
즉:
WAF 적용
↓
일부 공격 차단 가능
↓
하지만 우회 가능성 존재이고,
PHP 보안 패치
↓
취약한 코드 자체 수정가 근본적인 해결이다.
운영 서버라면 같이 확인할 것
PHP 버전을 올리고 끝내기보다는 최소한 아래 정도는 같이 확인하는 게 좋다.
1. 서버에 설치된 PHP 버전 확인
2. 실제 웹 서비스가 사용하는 PHP 버전 확인
3. PHP-FPM / Apache / LiteSpeed 등 실행 방식 확인
4. 보안 패키지 적용
5. PHP Worker 재시작
6. 서비스 정상 동작 확인
7. 최근 비정상 요청 및 PHP Crash 로그 확인특히 최근에 이유 없이 PHP-FPM Worker가 죽었다거나 Out Of Memory, Segmentation Fault 등이 반복됐다면 보안 패치와 별개로 로그를 한번 살펴볼 필요가 있다. 다만 이런 로그가 있다고 해서 곧바로 공격당했다고 판단할 수는 없다. 메모리 부족과 프로세스 Crash는 정상적인 운영 환경에서도 발생할 수 있기 때문이다.
취약하다는 것과 실제 침해당했다는 것은 완전히 다른 이야기다.
결국 이번 취약점에서 중요한 건
이번 PHP2Shell을 처음 보면 CVE 번호 두 개와 CVSS 9.2, RCE라는 무서운 단어부터 눈에 들어온다.
그런데 구조를 뜯어보면 핵심은 의외로 단순하다.
HTTP 요청 처리 중 오류 발생
↓
PHP 내부 상태가 정상적으로 정리되지 않음
↓
이미 해제됐거나 불완전한 메모리 상태가 남음
↓
다음 처리 과정에서 그 상태를 다시 사용
↓
Memory Corruption
↓
RCE결국 “요청 하나가 끝나면 해당 요청의 상태도 완전히 끝나야 한다” 는 기본적인 가정이 무너진 문제다.
그리고 이런 종류의 문제는 애플리케이션 코드 리뷰만으로 찾기 굉장히 어렵다. PHP 엔진 내부의 메모리 수명, 요청 Lifecycle, Fatal Error 처리, Worker 재사용 같은 부분을 같이 따라가야 하기 때문이다. 그래서 이번 사건은 PHP 보안 이슈라는 점도 흥미롭지만, 개인적으로는 AI가 소프트웨어 보안 연구에 어떤 영향을 주기 시작했는지를 보여준 사례라는 점에서도 꽤 인상적이었다.
그동안 코드베이스 어딘가에 숨어 있던 버그가 갑자기 생긴 건 아니다.
그 버그를 찾아내고 이거 Crash 나네에서 끝나는 게 아니라, 이걸 이런 식으로 연결하면 실제 RCE까지 가능한데? 까지 가는 비용이 크게 줄어들고 있다는 거다. 개발자 입장에서는 조금 무서운 이야기이기도 하다. 보안 연구자만 AI를 쓰는 건 아니니까…

그래도 결국 대응 방법은 꽤 익숙하다.
지원 종료된 런타임을 오래 방치하지 않고, 보안 릴리스가 나오면 영향도를 확인하고, 패치하고, 실제 프로세스까지 교체됐는지 확인하는 것. 평소에는 너무 당연해서 재미없는 이야기인데 이런 사건이 한 번 터질 때마다 왜 기본적인 패치 관리가 중요한지 다시 알게 된다.
마무리!
- PHP Core에서 CVE-2026-109618, CVE-2026-109619 두 개의 Critical 취약점이 발견됐다.
- 두 취약점 모두 CVSS v4.0 9.2이며 인증 없이 RCE로 이어질 수 있다.
- 애플리케이션 코드가 아닌 PHP 런타임 자체의 메모리 처리 문제다.
- OOM/Fatal Error 과정에서 PHP 내부 상태가 비정상적으로 남으면서 Memory Corruption, Use-After-Free 등이 발생할 수 있었다.
- WordPress, Drupal, Joomla에서 RCE가 시연됐지만 해당 CMS만의 취약점은 아니다.
- PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12에 수정 사항이 포함됐다.
- PHP 프로젝트는 완전한 우회책이 없다고 안내하고 있으며 보안 버전 업데이트가 가장 중요한 대응 방법이다.
- 업데이트 후에는 실제 PHP-FPM / Apache Worker까지 재기동됐는지 확인해야 한다.
- 이번 취약점 연구에는 AI가 적극적으로 활용됐으며, 오래 존재하던 복잡한 버그를 실제 exploit 가능성까지 분석하는 비용이 크게 낮아졌다는 점도 주목할 만하다.
댓글
익명 캐릭터로 가볍게 이야기를 남겨 주세요.