PHP2Shell 취약점 CVE-2026-109618 원리·공격·방어
PHP 5.4부터 8.5.11까지 전부 걸린 인증 없는 RCE, PHP2Shell. 두 CVE가 어디서 터지는지, 왜 mod_php가 가장 위험한지, 패치·임시 완화·침해 점검을 명령어와 함께 정리했다.
2026년 10월 21일, PHP 보안팀이 예정보다 하루 앞당겨 긴급 릴리스를 냈다. CVE-2026-109618과 CVE-2026-109619, 둘 다 CVSS v4.0 9.2 짜리 인증 없는 원격 코드 실행(RCE)이고, 묶어서 PHP2Shell 이라고 부른다. 웹 요청을 받는 PHP라면 PHP-FPM이든 Apache mod_php든 예외가 없고, 취약한 코드는 PHP 5.4.0 시절부터 있었다.
바쁘다면 이 세 가지만
php -v로 버전을 확인한다. 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 미만이면 대상이다.- 수정 버전으로 올리고 PHP 워커를 전부 재시작한다. 이것 말고 완전한 방법은 없다.
- 10월 17일 이후 로그를 훑고, 노출됐을 수 있는 비밀값은 교체한다.
🧨 무슨 일이 벌어졌나
이번 건은 공개 과정부터 꼬였다. 원래 엠바고는 10월 22일이었는데 사흘 전부터 흐름이 어긋났다.
- 10월 17일 Amazon 위협 인텔리전스가 허니팟에서 첫 공격 시도를 관측했다
- 10월 20일 Hacker News에 “PHP에 크리티컬 취약점 두 개가 곧 공개된다”는 글이 올라왔다가 삭제됐다. 스크린샷이 돌면서 사실상 유출됐고, watchTowr가 같은 날 “진짜다”라고 확인했다
- 10월 21일 03:02 UTC Cloudflare가 WAF 규칙을 전 플랜에 기본 적용했다
- 10월 21일 14:00 UTC PHP 보안팀이 릴리스를 하루 당겨 수정 버전을 냈다
공개 나흘 전에 이미 시도가 있었다는 건, 유출 전부터 누군가는 알고 있었다는 뜻이다.
🔍 두 CVE는 무엇이 다른가
두 건 모두 PHP 엔진 자체의 메모리 처리 결함이다. 워드프레스 플러그인이나 라라벨 코드를 아무리 깨끗하게 짜도 소용이 없는 이유다.
| CVE | PHP 측 분류 | 한 줄 요약 |
|---|---|---|
| CVE-2026-109618 | RCE (OOM Corruption) | 메모리가 바닥나는 순간, 덜 초기화된 요청 데이터를 정상 데이터처럼 정리하다 터진다 |
| CVE-2026-109619 | RCE (Stale Global State) | 이전 요청이 남긴 해제된 메모리 참조를 같은 워커의 다음 요청이 그대로 쓴다 |
PHP 워커는 요청이 끝나도 프로세스가 살아 있고 다음 요청을 이어서 받는다. 두 결함 모두 이 재사용 구조에서 요청 사이의 상태 정리가 어긋나는 지점이라고 보면 된다. 공격 세부 절차는 이 글에서 다루지 않는다. 방어하는 쪽에 필요한 건 어디가 위험한지이지, 어떻게 터뜨리는지가 아니다.
서버 위에 앱이 뭐가 올라가 있든 상관없다. PHP 버전이 대상이다.
💥 뚫리면 무슨 일이 생기나
RCE는 공격자가 서버에서 원하는 코드를 돌릴 수 있다는 뜻이다. 권한은 웹 워커 계정(www-data, nginx, apache 같은 것) 수준으로 제한되지만, 웹 서버에서 이 계정이 만질 수 있는 범위는 생각보다 넓다.
- 비밀값 탈취. 설정 파일(
wp-config.php,.env)에는 DB 비밀번호와 각종 API 키가 평문으로 들어 있는 경우가 많다. 이걸 읽으면 DB 전체가 열린 것과 같다. - 고객 정보 유출. 회원·주문·상담 데이터가 빠져나간다. 쇼핑몰이라면 결제 페이지를 조작해 카드 정보를 가로채는 스키밍으로 이어지기도 한다.
- 웹셸과 지속 접근. 입구를 패치로 막아도 이미 심어 둔 웹셸이나 새 관리자 계정은 남는다. 패치와 침해 점검이 별개 작업인 이유다.
- 사이트 변조. 검색 결과에 스팸 페이지가 깔리거나 방문자가 악성 사이트로 넘어간다. 검색 엔진이 위험 사이트로 분류하면 복구에 몇 주가 걸리기도 한다.
- 서버 자원 도용. 코인 채굴, 스팸 메일 발송, 다른 서버를 공격하는 발판으로 쓰인다. 호스팅 계정이 정지되거나 IP가 블랙리스트에 오른다.
- 내부망 이동. 웹 서버가 사내 DB나 관리 도구에 닿는 구조라면 피해가 사이트 하나로 끝나지 않는다.
CVE-2026-109618에는 한 가지가 더 붙는다. 해제된 워커 메모리가 응답으로 새어 나갈 수 있어서, 같은 워커가 처리한 다른 사람의 요청 속 비밀번호, 세션 토큰, API 키, 심지어 TLS 개인키까지 노출될 수 있다고 벤더들은 경고한다. 코드가 실행되지 않아도 정보 유출만으로 피해가 시작되는 셈이다.
취약점이 PHP 엔진에 있으니 앱 종류를 가리지 않는다. 연구진은 워드프레스, 드루팔, 매젠토, 줌라, 미디어위키, Roundcube, phpMyAdmin, 넥스트클라우드, 라라벨, 심포니, WHMCS 등 널리 쓰는 앱 대부분에서 인증 없는 코드 실행이 가능하다고 보고했다.
현실적인 위험도도 짚어 둘 필요가 있다. PHP 보안팀과 Rapid7은 공개된 PoC를 검토했고, 대부분이 특정 OS·PHP 버전·웹서버 조합에서만 동작하며 기본 설치에 그대로 통하는 범용 공격은 확인하지 못했다고 밝혔다. 반면 Amazon은 공개 나흘 전인 10월 17일부터 시도를 관측했고 공개 당일에는 여러 공격 주체의 흔적을 봤다. 당장 모든 서버가 뚫리는 상황은 아니지만 시도는 이미 진행 중이다. 패치를 미룰 이유가 되지 못한다.
🎯 내 서버는 해당될까
웹 요청을 받는 PHP는 전부 해당한다. PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed 모두 포함이다. 그중 PHP 측이 가장 높게 보는 건 mod_php다. 앱 코드가 실행되기도 전에 요청 한 건으로 건드릴 수 있는 경로가 확인됐기 때문이다. CLI로만 도는 배치 스크립트는 외부 요청을 받지 않으니 우선순위가 낮다.
| 브랜치 | 이 버전 미만이면 취약 | 비고 |
|---|---|---|
| 8.5 | 8.5.12 | |
| 8.4 | 8.4.27 | |
| 8.3 | 8.3.36 | |
| 8.2 | 8.2.35 | |
| 7.4 ~ 8.1 | 공식 릴리스 없음 | 소스 패치만 제공, 배포판 백포트에 의존 |
| 5.4 ~ 7.3 | 수정 없음 | EOL |
8.1 이하는 사정이 다르다. 버전 번호가 그대로여도 배포판이 패치를 백포트했을 수 있어서 php -v만 보고 판단하면 안 된다. 공식 Docker php 이미지는 8.1 이하에 수정본을 내지 않는다.
🩹 패치 확인하는 법
먼저 지금 돌고 있는 버전을 확인한다. FPM과 Apache가 서로 다른 PHP를 쓰는 서버도 흔해서 CLI 버전만 보면 안 되고, 실제 웹 요청을 처리하는 쪽 바이너리를 봐야 한다.
php -v
php-fpm8.3 -v # 설치된 FPM 버전에 맞게
배포판 패키지는 버전 숫자 대신 changelog에 CVE 번호가 있는지로 확인한다.
# Debian / Ubuntu
zgrep CVE-2026-109618 /usr/share/doc/php*/changelog.Debian.gz
# RHEL 계열
rpm -q --changelog php-common | grep CVE-2026-109618
패치 뒤에는 워커를 전부 재시작해야 한다. 설치만 하고 프로세스를 그대로 두면 메모리에는 옛 코드가 남아 있다.
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2 # mod_php라면 graceful보다 restart
🚧 당장 패치가 어렵다면
PHP 측 권고는 분명하다. 업데이트만이 유일한 완전한 해결책이고, 아래는 노출을 줄이는 임시 조치일 뿐이다. 설정 변경 뒤에는 역시 워커 재시작이 필요하다.
- 멀티파트 차단. 업로드 기능을 쓰지 않는 서비스라면
multipart/form-data요청을 웹서버 앞단에서 막는다. 지금까지 알려진 트리거가 전부 이 형식이다. 결제·첨부 폼이 있으면 같이 막히니 막은 뒤 주문 한 건은 직접 넣어 본다. - 요청 크기 상한.
post_max_size를 업로드가 없으면 8M, 있으면 32M 안팎으로 두고upload_max_filesize는 그보다 작게 잡는다. 앞단에서도 요청 본문 크기와 멀티파트 파트 수를 제한한다. - PHP 설정 고정. 앱이 덮어쓸 수 없는 위치에 넣어야 한다.
; FPM pool 설정 (php_admin_* 는 앱에서 ini_set 으로 못 바꾼다)
php_admin_value[memory_limit] = -1
php_admin_flag[session.upload_progress.enabled] = off
php_admin_flag[fatal_error_backtraces] = off ; PHP 8.5만
여기서 .user.ini나 코드 안의 ini_set()은 이미 늦다. 요청 파싱이 그보다 먼저 일어나기 때문이다. 그리고 memory_limit=-1은 요청 하나가 서버 메모리를 다 먹을 수 있다는 부작용이 있어서 패치 후 원래 값으로 되돌려야 한다.
위 설정만으로는 안전해지지 않는다. 시간을 버는 용도로만 쓰고 패치 일정을 같이 잡는다.
컨테이너나 cgroup 메모리 제한은 가용성 경계로는 쓸 만해도 완화책은 아니다. RLIMIT_AS로 대신하려는 시도는 오히려 문제 경로를 건드릴 수 있다.
Cloudflare를 앞에 두고 있다면 WAF 규칙이 21일 03:02 UTC부터 전 플랜에 기본 적용돼 있다. 다만 Cloudflare 스스로 임시 방편이라고 밝혔고 거쳐 가는 트래픽만 막는다. 유료 플랜은 Managed Ruleset이 차단 모드인지 한 번 확인해 두자.
🕵️ 이미 당했는지 확인하기
Amazon이 첫 시도를 본 게 10월 17일이다. 패치를 했더라도 그 전에 뚫렸는지는 따로 봐야 한다. 벤더들이 공통으로 꼽는 징후는 이렇다.
- 멀티파트 요청 직후
Allowed memory size exhausted오류가 찍힌다 - 같은 서버에서 HTTP 500이 반복되거나 PHP 워커가 이유 없이 자주 죽고 다시 뜬다
- 평소보다 훨씬 길거나 깨진
PHPSESSID쿠키가 응답에 섞여 나간다
grep -r "Allowed memory size" /var/log/php* /var/log/nginx 2>/dev/null | tail -50
find /var/www -name '*.php' -mtime -10 -not -path '*/vendor/*' | head -50
둘째 줄은 업로드·캐시 디렉터리에 낯선 PHP 파일이 생겼는지 보는 용도다. CVE-2026-109618은 해제된 워커 메모리가 응답으로 새어 나갈 수 있는 결함이라서, 패치 이전에 노출된 서버라면 DB 비밀번호, API 키, 세션 토큰 같은 비밀값도 교체 대상으로 봐야 한다. 번거롭지만 이 항목을 빼먹으면 패치해 놓고도 열쇠가 밖에 남는다.
📌 정리해 둘 우선순위
상황별로 할 일이 갈린다.
- PHP 8.2 이상: 오늘 안에 수정 버전으로 올리고 워커를 재시작한다.
- PHP 8.1 이하: 배포판 changelog에서 백포트 여부를 확인한다. 없다면 EOL 버전을 인터넷에 열어 두는 구조 자체를 다시 봐야 한다. CISA도 패치할 수 없거나 8.1 이하인 PHP 시스템은 내리라고 권고했다.
- 패치 직후: 10월 17일 이후 로그를 훑고, 노출 가능성이 있었던 서버의 비밀값을 갈아 둔다.
관련 글
S3 비용 최대 80% 절감, Intelligent-Tiering 설정
S3 Intelligent-Tiering이 모든 버킷의 정답은 아니다. 서울 리전 단가로 손익분기 객체 크기를 계산하고, 라이프사이클 적용부터 검증, 티어가 안 내려가는 함정까지 정리했다.
Aurora PostgreSQL 15→16 Blue/Green 업그레이드
Aurora PostgreSQL 15를 Blue/Green으로 16에 올리며 세 번 실패한 원인(논리 복제 워커 수)과 해결, 사전 점검부터 그린 검증·스위치오버·사후 정리까지 콘솔 화면 기준으로 정리했다.