무슨 일이 있었나
10월 21일, PHP 프로젝트는 두 취약점 CVE-2026-109618과 CVE-2026-109619에 대한 수정을 배포했습니다. Arctic Wolf의 분석에 따르면 둘 다 CVSS 9.2 등급이며, PHP로 웹 요청을 처리하는 서버에서 인증되지 않은 공격자가 코드를 실행할 수 있게 할 수 있습니다.
이 버그들은 프레임워크나 플러그인이 아니라 PHP 자체의 요청 처리 라이프사이클에 있습니다:
- CVE-2026-109618은 요청 데이터가 일부만 준비된 상태에서 메모리 부족 상황이 PHP를 중단시킬 때 발생하는 메모리 손상입니다.
- CVE-2026-109619는 use-after-free입니다. 한 요청에 속한 상태가 남아 같은 워커의 이후 요청에서 읽힐 수 있으며, 이 과정에서 메모리 내용이 유출될 수도 있습니다.
두 번째 항목은 잠시 짚고 넘어갈 가치가 있습니다. Qualys에 따르면 유출된 워커 메모리에는 비밀번호, 토큰, API 키가 들어 있을 수 있습니다. 즉, 아무도 서버에서 셸을 얻지 못하더라도 시크릿이 밖으로 새어 나갈 수 있다는 뜻입니다.
패치 이후 상황은 빠르게 전개됐고 규모도 큽니다. The Hacker News는 Censys의 추정을 인용해 영향받는 버전을 실행 중인 PHP 서비스가 아직 3,200만 개가 넘는다고 전했습니다.
- BleepingComputer 보도에 따르면, Amazon의 위협 인텔리전스 팀은 수정이 공개되기 4일 전부터 공격 대상 탐색이 시작됐다고 밝혔습니다.
- 10월 24일, Perplexity의 한 연구원이 허니팟을 대상으로 코드 실행에 성공했음을 확인했습니다(The Hacker News).
- 10월 26일, CISA는 두 CVE를 알려진 악용 취약점(KEV) 카탈로그에 추가하고 미국 연방 기관에 10월 29일까지 수정하도록 지시했습니다.
작은 앱에도 중요한 이유
이번 공격에서 공격자는 회사 규모로 대상을 고르지 않습니다. 인터넷 전체를 스캔해 PHP처럼 응답하는 것을 찾아 익스플로잇을 시도합니다. VPS에 잊힌 채 남아 있는 사이드 프로젝트도 대형 쇼핑몰만큼이나 쉽게 닿을 수 있습니다.
이번 건을 특히 서둘러 대응해야 하는 이유가 몇 가지 있습니다:
- 내 코드와 무관합니다. Arctic Wolf가 지적하듯, 결함이 PHP의 요청 처리에 있으므로 어떤 애플리케이션을 실행하는지와 상관없이 노출됩니다. 완벽하게 작성된 Laravel 앱이라도 오래된 PHP 빌드 위에서 돌아가면 여전히 노출됩니다.
- 많은 서버 구성이 해당됩니다. mod_php를 쓰는 Apache가 가장 위험한 것으로 꼽히지만, PHP-FPM, php-cgi, FrankenPHP, LiteSpeed도 모두 영향을 받는 것으로 명시됐고, PHP를 번들로 포함한 제품과 어플라이언스도 마찬가지입니다.
- 오래된 PHP는 흔합니다. Qualys에 따르면 버전을 확인할 수 있었던 PHP 설치의 약 71%가 이미 지원 종료(EOL) 상태였습니다. 8.1 이하를 쓰고 있다면 정규 업스트림 수정은 없습니다.
내 앱을 확인하는 방법
서버마다 찾아야 할 답은 하나입니다. PHP로 웹 요청을 처리하는 것이 있는가, 그리고 정확한 버전은 무엇인가?
1. 운영 중인 모든 것을 목록으로 만드세요. 메인 앱만이 아닙니다. 마케팅 사이트, 블로그, 문서, 스테이징 환경, 관리 도구, 서브도메인에 있는 모든 것을 포함하세요. 잊힌 것들이 가장 위험합니다.
2. 응답 헤더를 확인하세요. 많은 PHP 환경이 X-Powered-By나 Server 헤더로 스스로를 드러냅니다. 헤더가 없다고 PHP가 없는 것은 아니지만(expose_php = Off로 끌 수 있습니다), 헤더가 있다면 바로 ‘예’라는 답이 됩니다. 상세한 오류 페이지도 단서가 됩니다. 운영 환경의 PHP 스택 트레이스는 무엇을 실행 중인지 공격자에게 정확히 알려 줍니다.
curl -sI https://your-site.example | grep -i -E 'x-powered-by|server'
# X-Powered-By: PHP/8.3.20 <- PHP, and an old patch level상세한 오류 페이지와 스택 트레이스: 끄는 방법3. CLI뿐 아니라 웹 트래픽을 처리하는 버전을 확인하세요. 서버에서 php -v가 보고하는 빌드는 Apache나 PHP-FPM이 실제로 사용하는 빌드와 다를 수 있습니다. Qualys도 이 점을 특별히 강조합니다. FPM 바이너리나 웹 서버의 PHP 모듈을 확인하거나 호스팅 업체에 문의하세요.
php-fpm8.3 -v # or php-fpm -v, depending on your distro
apache2ctl -M | grep php # is mod_php loaded?4. 수정된 버전과 비교하세요. Linux 배포판에서 PHP를 설치했다면 수정 사항이 더 오래돼 보이는 버전 번호의 패키지에 백포트되어 있을 수 있으므로, 버전 문자열만 보지 말고 배포판의 보안 권고를 확인하세요. 그 외의 경우 최소한 다음 버전이 필요합니다:
- PHP 8.5: 8.5.12
- PHP 8.4: 8.4.27
- PHP 8.3: 8.3.36
- PHP 8.2: 8.2.35
5. 매니지드 호스팅을 쓰나요? 매니지드 WordPress 호스트나 PaaS를 사용한다면 상태 페이지나 변경 로그에서 PHP2Shell 관련 공지를 확인하세요. 대부분은 대신 패치해 주지만 ‘대부분’이 ‘전부’는 아니며, 직접 관리하는 VPS는 전적으로 여러분의 책임입니다.
해결 방법
패치한 다음 재시작하세요. PHP 업데이트만이 유일한 완전한 해결책입니다.
재시작이 중요합니다. 실행 중인 워커는 교체될 때까지 이전 코드를 메모리에 유지합니다. Docker라면 업데이트된 베이스 이미지(예: 최신 php:8.3-fpm 태그)로 다시 빌드하고 재배포하세요. 기존 컨테이너를 재시작하는 것만으로는 안 됩니다.
PHP-FPM을 쓰는 Debian이나 Ubuntu에서는 보통 다음과 같습니다:
sudo apt update && sudo apt install --only-upgrade 'php8.3*'
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2 # if you use mod_php오늘 패치할 수 없다면, Qualys가 정리한 PHP의 임시 완화 조치가 있습니다. 위험을 줄여 주지만 패치되지 않은 서버를 안전하게 만들지는 못합니다. php.ini 또는 FPM 풀 설정(.user.ini나 ini_set이 아니라)에 적용한 뒤 모든 워커를 재시작하세요.
패치되지 않은 서비스가 공개될 필요가 없다면 VPN이나 IP 허용 목록 뒤에 두거나, 업데이트할 수 있을 때까지 오프라인으로 전환하세요.
; php.ini or FPM pool config (not .user.ini or ini_set)
session.upload_progress.enabled = 0
post_max_size = 8M ; 32M if you need uploads
upload_max_filesize = 6M ; keep below post_max_sizePHP 8.1 이하라면 지금 바로 업그레이드를 계획하세요. 일부 오래된 브랜치에는 제한적인 백포트가 있지만, 이런 일이 생길 때마다 운에 기대는 셈입니다.
침해 징후를 찾아보세요. 예상치 못한 프로세스, 웹 루트의 새 파일, 낯선 cron 작업, 웹 서버에서 나가는 연결을 확인하세요. Arctic Wolf는 로그에 남은 ‘PHP2Shell’ 요청 상당수가 예전 기법을 재사용한다고도 지적합니다. 무서워 보이는 로그 한 줄이 곧 침입 성공을 뜻하지는 않습니다.
노출됐었다면 시크릿을 교체하세요. CVE-2026-109619는 워커 메모리를 유출할 수 있으므로, 패치되지 않은 채 인터넷에 노출된 PHP 프로세스를 거친 시크릿은 노출됐을 수 있다고 간주하세요. 데이터베이스 비밀번호, API 키, 세션 서명 키, 결제 서비스 키가 여기에 해당합니다. Laravel 같은 프레임워크는 이 값들을 .env에 보관하므로, 애초에 그 파일이 웹에서 접근 가능하지 않았는지도 확인하세요.
빠른 체크리스트
보유한 모든 도메인에 대해 점검하세요:
- 오래된 마케팅 사이트와 스테이징 사이트를 포함해 보유한 모든 도메인과 서브도메인을 목록으로 만들었다.
- 어느 것이 PHP를 실행하는지, 웹을 처리하는 정확한 버전이 무엇인지 알고 있다.
- 각각 8.5.12, 8.4.27, 8.3.36, 8.2.35 또는 벤더가 패치한 빌드를 쓰고 있다.
- 업데이트 후 PHP 워커와 웹 서버를 재시작했다.
- 패치되지 않은 것은 접근을 제한하거나 오프라인으로 전환했고, 완화 조치를 적용했다.
- 노출된 호스트의 시크릿을 교체했다.
- 운영 환경이 더 이상 PHP 버전을 노출하거나 스택 트레이스를 표시하지 않는다.
더 큰 교훈
PHP2Shell은 몇 주 안에 패치되고 잊힐 것입니다. 하지만 이 패턴은 그대로입니다. ‘수정 공개’에서 ‘악용’까지의 간격은 이제 며칠, 때로는 마이너스입니다. 작은 팀이 영웅적인 노력으로 이를 앞지를 수는 없습니다. 효과적인 방법은 내가 무엇을 운영하는지 알고, 거기에 변화가 생겼을 때 빠르게 알아차리는 것입니다.
이는 AI로 만든 앱을 위한 출시 체크리스트와 같은 생각입니다. 실제 사고의 대부분은 실제로 확인하기만 하면 쉽게 찾을 수 있는 것에서 비롯됩니다.
AppSafe는 공격자가 보는 방식 그대로 외부에서 운영 중인 사이트를 스캔합니다. 누락된 보안 헤더, 스택을 노출하는 X-Powered-By 헤더, 노출된 파일, 상세한 오류 같은 문제를 찾아 쉬운 말로 해결 방법을 알려 줍니다. 패치를 마쳤다면 각 도메인에서 무료 스캔을 실행해 다른 손쉬운 개선점도 함께 찾아보세요.
