핵심 요약
- "CVSS 10.0"은 공식 점수가 아닙니다. PHP가 낸 두 보안 권고(CVE-2026-109618, CVE-2026-109619)는 모두 CVSS v4.0 9.2입니다. 10.0은 패치 전날 해커 뉴스에 올라온 유출 글의 숫자였습니다. 9.2도 최고 등급인 "치명적(critical)"입니다.
- 공개 PoC로는 쉽게 뚫리지 않는다는 말과, 기본 설정에서 뚫린다는 말은 둘 다 맞습니다. GitHub에 돌던 PoC 상당수가 다른 취약점이 이미 있어야 작동한다는 지적이 나왔고, PHP 보안팀도 공개 시점에 작동하는 공개 익스플로잇은 찾지 못했습니다. 반면 PHP 공식 권고는 Apache mod_php에서 기본 입력·메모리 한도로 인증 없는 원격 코드 실행을 검증했고, 세션 업로드 진행률 경로는 PHP-FPM에서도 검증했습니다.
- 공격 전제 조건이 기본값과 맞물립니다. 필자가 공식 Docker 이미지로 확인해 보니 세션 업로드 진행률 기능은 모든 버전에서 기본으로 켜져 있었고, PHP 8.5는 확인된 공격 경로인
fatal_error_backtraces도 기본으로 켜져 있었습니다.php:apache이미지는 권고가 공격을 검증한 mod_php + prefork 구성 그대로였습니다.- 올릴 버전은 8.2.35, 8.3.36, 8.4.27, 8.5.12 이상입니다(2026년 10월 21일 공개). 8.1 이하는 정식 릴리스가 없어 지원 버전으로 옮겨야 합니다. 업데이트한 뒤에는 PHP를 쓰는 모든 웹 서비스를 재시작해야 하고, 배포판 패키지는 패치돼도 버전 번호가 그대로일 수 있습니다.
- 한국은 노출이 큰 편입니다. 안랩 집계로 국내 취약 호스트는 11만 2,183개로 세계 6위였고, 버전이 드러난 국내 서버 가운데 정식 패치가 나오지 않은 8.1 이하가 82%(시벨앙젤), 89%(말로리)라는 집계가 각각 나왔습니다. 정부는 10월 22일 민간 분야 사이버위기 경보를 '주의'로 올렸습니다.
목차
- PHP2Shell은 무엇이고 어떤 버전을 올려야 하나
- 두 취약점은 어떻게 동작하나
- CVSS 10.0은 어디서 나왔나
- 정말 쉽게 뚫릴까: 반론과 재반론
- 기본 설정을 직접 확인해 보니
- 한국 서버가 특히 급한 이유
- 지금 할 일: 버전 확인부터 재시작, 키 교체까지
- AI가 찾아냈고, 징후는 2024년에 이미 보였다
- 출처
PHP2Shell은 무엇이고 어떤 버전을 올려야 하나
PHP2Shell은 PHP 엔진 자체에 있던 메모리 처리 결함 두 건을 묶어 부르는 이름이고, 고친 버전은 8.2.35, 8.3.36, 8.4.27, 8.5.12입니다. PHP 프로젝트는 2026년 10월 21일 보안 공지에서 이 네 버전이 "인증 없이 원격 코드 실행을 허용할 수 있는 치명적 취약점 두 건"을 고친다며 모든 사용자에게 즉시 업데이트를 권했습니다(php.net 보안 공지). Wiz Research와 Calif는 공개 글에서 이 취약점들을 PHP2Shell이라고 불렀습니다(Wiz Research, Calif).
위험 대상은 웹 요청을 처리하는 거의 모든 PHP 설치입니다. 공지는 "PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed"를 차례로 꼽고, 그중 mod_php의 위험이 가장 높다고 했습니다. 애플리케이션 코드가 한 줄도 실행되기 전에 인증 없는 요청으로 공격이 시작되는 시연이 있었기 때문입니다. 워드프레스, 그누보드, 라라벨 같은 애플리케이션을 고쳐서 막을 수 있는 문제가 아니라는 뜻입니다.
| 쓰는 브랜치 | 고친 버전 | 보안 지원 종료 |
|---|---|---|
| PHP 8.5 | 8.5.12 | 2029년 12월 31일 |
| PHP 8.4 | 8.4.27 | 2028년 12월 31일 |
| PHP 8.3 | 8.3.36 | 2027년 12월 31일 |
| PHP 8.2 | 8.2.35 | 2026년 12월 31일 |
| PHP 8.1 이하 | 정식 릴리스 없음 | 이미 종료 (8.1은 2025년 12월 31일) |
필자가 php.net 릴리스 API를 직접 조회했을 때 네 버전 모두 날짜가 "21 Oct 2026", 태그가 "security"였고, 8.1의 최신 버전은 2025년 12월 18일의 8.1.34에 멈춰 있었습니다. 보안 지원 종료일은 php.net의 지원 버전 표와 지원 종료 목록을 따랐습니다. 8.2는 두 달 뒤 지원이 끝나므로, 지금 손을 대는 김에 8.3 이상으로 올리는 편이 낫습니다. KISA도 대응 수칙에서 같은 이유로 8.3 이상 전환을 권했습니다(KISA 보호나라 대응 수칙).
8.1 이하에는 정식 릴리스가 나오지 않았습니다. php.net은 "PHP 8.1 and older no longer receive updates from the PHP project"라고 밝히면서도, 사안이 심각해 PHP 보안팀이 7.4, 8.0, 8.1용 최소한의 소스 백포트를 따로 냈다고 덧붙였습니다. 직접 빌드하는 사람을 위한 임시 수단이지, 지원 버전으로 옮기는 일을 대신하지는 않습니다.
8.1 이하라고 이번 취약점을 비켜 가지도 않습니다. Calif는 mod_php 기준으로 세션 업로드 진행률 경로가 PHP 5.4.0 이상에서, 메모리 부족 경로가 PHP 7.3 이상에서 통한다고 밝혔습니다(Calif). PHP 보안팀의 소스 백포트는 7.4, 8.0, 8.1용뿐이라, 5.4 이상의 5.x와 7.0~7.3을 쓰는 서버는 배포판이나 호스팅사가 따로 백포트를 내주지 않는 한(예: 우분투 18.04와 20.04는 Ubuntu Pro로, 14.04와 16.04는 Ubuntu Pro의 Legacy Support 추가 기능으로 수정 패키지를 받을 수 있습니다, Ubuntu USN-8948-2) 버전을 올리는 것 말고는 방법이 없습니다.
두 취약점은 어떻게 동작하나
두 취약점 모두 "요청 하나가 끝나는 순간의 뒷정리"가 어긋나는 문제입니다. PHP는 요청마다 메모리를 받아 쓰고, 요청이 끝나면 그 메모리를 정리합니다. 이 정리 과정이 덜 만들어진 데이터나 이미 반납한 메모리를 진짜 데이터로 착각하면, 공격자가 그 자리에 심어 둔 값이 코드 실행으로 이어집니다.
CVE-2026-109618: 메모리가 바닥나는 순간을 노린다
첫 번째는 메모리 부족(OOM) 처리입니다. GitHub 보안 권고에 따르면 PHP는 요청이 memory_limit를 넘으면 longjmp()로 그 자리를 바로 빠져나갑니다. 그런데 어떤 구간에서는 요청 메모리가 아직 다 초기화되지 않은 상태로 노출돼 있고, 하필 그때 할당이 실패하면 반쯤 만들어진 데이터가 남습니다. 이어지는 요청 종료 처리가 그 데이터를 읽으면서 원격 코드 실행까지 갈 수 있습니다(GHSA-w8hw-w9fx-j79w).
공격자가 메모리를 바닥내는 방법은 파일 업로드에 쓰는 multipart 요청입니다. 권고는 세 가지를 꼽았습니다. Content-Length가 없는 요청은 post_max_size 검사를 통과한 뒤에도 본문을 계속 보낼 수 있고, 파트 하나에 헤더를 제한 없이 붙이면 전송 크기보다 훨씬 많은 메모리를 쓰게 만들 수 있으며, 빈 파일 필드는 max_file_uploads에 세지 않으면서 메타데이터 배열을 만듭니다. 아래 지금 할 일에서 프록시로 요청 크기와 파트 수, 헤더 수를 제한하라는 이유가 여기 있습니다. 식당에 비유하면 주문서 한 장에 빈 칸과 덧붙인 메모를 끝없이 달아 주방을 멈추게 하고, 손님이 나간 뒤 반쯤 치운 테이블에 다음 손님을 앉히는 셈입니다.
결정적인 차이는 SAPI입니다. 권고는 Apache mod_php가 요청 시작이 실패해도 종료 처리를 무조건 돌리기 때문에 "애플리케이션 코드가 실행되기 전에" 이 상황이 생긴다고 설명합니다. PHP-FPM은 요청 시작이 실패하면 그 자식 프로세스를 끝내 버려서 같은 경로가 막히고, 권고는 FPM에 대해 "no complete FPM exploit has been demonstrated"라고 적었습니다. FrankenPHP는 mod_php처럼 종료 처리를 무조건 부르므로 취약할 가능성이 높다고 봤습니다.
CVE-2026-109619: 앞 요청이 남긴 흔적을 다음 요청이 밟는다
두 번째는 워커 프로세스에 남는 전역 상태입니다. PHP 워커는 요청이 끝나도 살아서 다음 요청을 받습니다. 에러, 세션, 객체 상태 같은 전역 값이 이미 반납한 요청 메모리를 가리킨 채 남아 있으면, 같은 워커에 들어온 다음 요청이 초기화 전에 그 값을 쓰면서 해제된 메모리를 다시 쓰는(use-after-free) 문제가 생깁니다(GHSA-6wmf-4rv5-p3rp).
권고가 확인했다고 밝힌 경로는 두 가지입니다. 하나는 PHP 8.5의 fatal_error_backtraces로, 치명적 오류의 역추적 정보를 지우지 않고 남겨 두는 문제이며 권고는 이를 "the strongest demonstrated case"라고 불렀습니다. 다른 하나는 세션 업로드 진행률(session upload progress)입니다. 권고는 이 기능이 기본으로 켜져 있고 애플리케이션이 session_start()를 부르지 않아도 동작한다고 적었으며, mod_php와 FPM 모두에서 이 경로로 원격 코드 실행을 검증했습니다. 다만 FPM 쪽은 공격에 맞는 애플리케이션 동작이 필요했습니다.
코드 실행 전에 메모리를 훔쳐 보는 단계도 있습니다. Wiz는 메모리 부족을 일으킨 뒤 같은 워커에 보낸 다음 요청의 PHPSESSID 응답 쿠키로 서버 메모리 일부를 빼낼 수 있다고 설명했고, KISA는 이 때문에 서버 메모리에 있던 비밀번호, 토큰, API 키가 새어 나갈 수 있다고 경고했습니다. 패치만 하고 끝내면 안 되는 이유가 여기 있습니다.
CVSS 10.0은 어디서 나왔나
10.0은 패치 하루 전 해커 뉴스에 올라온 사전 유출 글의 숫자이고, PHP의 공식 점수는 두 건 모두 CVSS v4.0 9.2입니다. 2026년 10월 20일 해커 뉴스에 "CVSS 10.0인 PHP 원격 코드 실행 CVE 2건이 공개될 예정"이라는 글이 올라왔고(Hacker News), 이 글은 GeekNews로 번역돼 국내에도 소개됐습니다(GeekNews). 같은 날 php.net 사전 공지는 두 취약점이 "both rated CVSS v4.0 9.2"라고 밝혔고(php.net 사전 공지), PHP 재단도 유출 정보에 붙은 10.0은 잘못된 점수이고 실제는 9.2라고 적었습니다(PHP 재단).
필자가 GitHub API로 두 권고 원문을 받아 보니 점수와 벡터가 똑같았습니다.
CVE-2026-109618 RCE via OOM corruption critical CVSS v4.0 9.2
CVE-2026-109619 RCE via stale global state critical CVSS v4.0 9.2
벡터: CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
벡터를 풀면 이 취약점의 성격이 보입니다. 네트워크로 공격할 수 있고(AV:N), 로그인도 사용자 조작도 필요 없으며(PR:N, UI:N), 성공하면 기밀성, 무결성, 가용성이 모두 크게 무너집니다(VC:H, VI:H, VA:H). 대신 공격 복잡도가 높고(AC:H) 공격 전제 조건이 있다고(AT:P) 매겼고, 취약한 PHP 밖의 다른 시스템으로 번지는 영향은 없다고(SC:N, SI:N, SA:N) 봤습니다.
흔히 "공격이 어려워서 10점이 안 됐다"고 읽기 쉽지만, 공격 조건만으로는 설명되지 않습니다. 필자가 CVSS v4.0 계산 라이브러리로 검산해 보니, 공격 복잡도와 전제 조건만 가장 쉬운 값(AC:L, AT:N)으로 바꾸면 9.3, 다른 시스템까지 크게 무너진다는 평가(SC:H, SI:H, SA:H)만 붙이면 9.5였고, 둘을 모두 바꿔야 10.0이 나왔습니다. 실제 벡터를 기준으로 공격 조건이 깎은 몫은 0.1점이고, 10.0이 되려면 피해가 그 서버 밖으로 번진다는 평가까지 필요했다는 뜻입니다. Red Hat은 같은 취약점을 CVSS 3.1 기준 8.1(AC:H)로 매겼습니다(Red Hat).
그래서 "CVSS 10이니 Log4Shell급이다"도, "9.2에 공격 복잡도 높음이니 안심해도 된다"도 맞지 않습니다. 9.2는 v4.0 기준 치명적 등급입니다. AC:H는 공격자가 메모리 보호 같은 기존 방어 장치를 피해 가는 추가 작업을 해야 한다는 뜻이고, AT:P는 특정 배포 조건이 갖춰져야 한다는 뜻이지 "실제로는 안 된다"는 뜻이 아닙니다. 필자는 그 조건이 얼마나 쉽게 갖춰지는지가 이 논쟁의 핵심이라고 봅니다.
정말 쉽게 뚫릴까: 반론과 재반론
공개된 PoC만 보면 쉽지 않았고, 취약점을 찾은 연구자들이 만든 익스플로잇을 보면 기본 설정에서도 됐습니다. 두 주장은 서로 다른 대상을 말하고 있어서 동시에 참입니다.
"쉽게 악용할 수 없다"는 쪽
반론은 Elias Taylor라는 이름의 블로그 글에서 나왔고 GeekNews에도 소개됐습니다(Elias Taylor, GeekNews). 글쓴이는 공개 약 10시간 만에 GitHub에 "PoC"가 수천 개 생겼다고 했는데(개수를 센 근거는 밝히지 않았습니다), 스타가 많은 저장소들을 열어 보니 서버에 로컬 파일 포함(LFI) 같은 다른 취약점이 이미 있어야 작동하더라는 것입니다. 글은 이를 "an insane pre-requisite"라고 부르고 "This is too difficult to exploit easily. It isn't a Log4J moment."라고 결론지었습니다.
이 관찰은 쓸모가 있습니다. "PoC가 수천 개"라는 소식을 "누구나 클릭 한 번에 뚫는다"로 읽으면 안 된다는 경고입니다. 다만 글이 검토한 것은 GitHub에 돌던 공개 PoC뿐입니다. PHP 공식 권고나 발견자들의 기술 문서는 다루지 않았고, 시연 영상은 언급만 하고 검토하지 않았습니다. 해커 뉴스 토론에서는 등록된 지 3주가 안 된 도메인의 신원 불명 블로그라는 지적과, 연락처가 있다는 반박이 함께 나왔습니다(Hacker News 토론). 공식 쪽에서도 PHP 보안팀은 공개 시점에 작동하는 공개 익스플로잇을 찾지 못했다고 밝혔습니다(PHP 재단).
"기본 설정에서 된다"는 쪽
반대편에는 취약점을 직접 찾고 고친 사람들이 있습니다.
- PHP 공식 권고: "Complete remote code execution was validated over HTTP against mod_php with default PHP input and memory limits and normal prefork configurations with multiple workers." 워커가 여럿이고 다른 트래픽이 섞이면 시도 횟수가 늘어날 뿐 조건이 사라지지는 않는다고 적었습니다(GHSA-w8hw-w9fx-j79w).
- Calif: 8월 11일 OpenAI와 같은 날 처음으로 이 결함을 PHP에 신고한 보안 회사로, 버전과 SAPI에 따라 통하는 경로는 다르지만 128MiB
memory_limit와 8MiBpost_max_size라는 기본값에서 "always exploitable under default configurations"라고 썼습니다(Calif). - Wiz: 테스트 당시 최신
wordpress,drupal,joomla공식 컨테이너 이미지에서 원격 코드 실행에 성공했다고 밝혔습니다(Wiz Research).
신중한 쪽도 있습니다. Red Hat은 안정적인 공격에 정밀한 힙 조작과 타이밍이 필요하고, RHEL 표준 구성인 PHP-FPM에서는 실제 공격이 어렵다고 평가했습니다(Red Hat 보안 공지). 다만 이 평가는 FPM에서 애플리케이션과 무관한 완전한 공격은 시연되지 않았다는 CVE-2026-109618 권고에만 들어맞습니다. CVE-2026-109619 권고는 세션 업로드 진행률 경로의 원격 코드 실행을 mod_php와 FPM 양쪽에서 검증했고 일반적인 dynamic FPM 풀에서도 동작한다고 적었습니다(GHSA-6wmf-4rv5-p3rp). Calif도 mod_php와 PHP-FPM 양쪽을 겨냥한 안정적인 익스플로잇을 만들었다고 밝혔습니다(Calif). FPM을 쓴다고 안심할 수는 없습니다.
실제 공격은 있었나
공격 시도 신호는 있지만, 필자가 찾은 공개 자료 기준으로 10월 22일까지 확인된 침해 사례는 보이지 않습니다. 연합뉴스는 국내에 접수된 침해 사고나 피해 의심 사례가 아직 없다고 전했습니다(연합뉴스). Cloudflare는 자사 기준 공개 약 9시간 전인 10월 21일 03:02(UTC)부터 무료 요금제를 포함해 WAF 규칙을 걸었다고 밝혔습니다. 정보가 공식 공개 전에 새어 나갔고 고객을 노린 공격 트래픽을 관측했으며, 공격자들이 그 방어를 우회하려고 페이로드를 바꿔 가며 시도했다는 설명도 덧붙였습니다(Cloudflare). 국내에서는 안랩이 수정 버전 배포 전부터 공격 시도를 관측했다고 했습니다(연합뉴스). 반면 Huntress는 10월 21일 오후(미국 동부 기준)까지 고객사에서 실제 악용을 보지 못했고(Huntress), 유럽연합 CERT-EU는 PoC가 돌면서 공격자들이 이미 악용하고 있을 가능성이 커졌다고 봤습니다(CERT-EU).
정리하면 "쉽게 뚫리지 않는다"는 말은 "스크립트 키디가 GitHub에서 받은 PoC로 아무 서버나 뚫는 단계는 아직 아니다"까지만 맞습니다. 메모리 배치를 맞추는 일은 어렵지만, 발견자들은 AI를 써서 이 일을 며칠에서 몇 주 단위로 해냈습니다. 해커 뉴스에서는 Trail of Bits 대표라고 밝힌 사용자가 직원 두 명이 LLM의 도움을 받아 각각 약 4시간 만에 익스플로잇을 썼다는 댓글을 달기도 했습니다(검증되지 않은 댓글이고, 같은 댓글에서 여전히 전문성이 필요하다고 덧붙였습니다)(Hacker News 댓글). 필자는 공격 난도가 시간이 갈수록 내려간다고 보는 편이 안전하다고 판단합니다. 네덜란드 국가사이버보안센터(NCSC-NL)가 패치 전 자기 웹사이트를 예방 차원에서 내리고, 업데이트하지 못한 시스템은 오프라인으로 두라고 권한 것도 같은 방향의 판단으로 읽힙니다(NCSC-NL).
기본 설정을 직접 확인해 보니
공격이 필요로 하는 설정 상당수가 기본으로 켜져 있었습니다. 필자는 2026년 10월 22일 맥(M1 Max)에서 공식 Docker php 이미지를 받아, php.ini 없이 내장 기본값만으로 관련 설정을 읽어 봤습니다. 공격 코드는 쓰지 않았고 ini_get()으로 값만 확인했습니다.
php 이미지 태그 |
패치 여부 | 세션 업로드 진행률 | fatal_error_backtraces |
|---|---|---|---|
8.4.26-cli |
취약 | 켜짐(1) | 설정 없음 |
8.4.27-cli |
패치됨 | 켜짐(1) | 설정 없음 |
8.5.11-cli |
취약 | 켜짐(1) | 켜짐(1) |
8.5.12-cli |
패치됨 | 켜짐(1) | 켜짐(1) |
8.1-cli (8.1.34) |
패치 없음 | 켜짐(1) | 설정 없음 |
다섯 이미지 모두 memory_limit는 128M, post_max_size는 8M였습니다.
여기서 세 가지가 보입니다. 첫째, CVE-2026-109619의 확인된 경로인 세션 업로드 진행률은 모든 버전에서 기본으로 켜져 있었습니다. 패치 버전에서도 켜져 있는데, 패치는 기능을 끄는 대신 남은 상태를 정리하는 방식으로 고쳤기 때문입니다. 둘째, PHP 8.5에서는 권고가 "가장 강한 사례"로 꼽은 fatal_error_backtraces도 기본으로 켜져 있었고, 8.5.12의 php.ini-production 견본에도 ;fatal_error_backtraces = On처럼 기본값 그대로 주석 처리돼 있었습니다. 셋째, 128M와 8M는 Calif가 "기본 설정에서 항상 공격 가능"하다고 할 때 기준으로 든 바로 그 값입니다.
웹 서버 구성도 확인했습니다. php:8.4.26-apache 이미지 안에서 apache2ctl -M을 실행하면 mpm_prefork_module과 php_module이 함께 나옵니다. 공식 권고가 원격 코드 실행을 검증한 "mod_php + prefork" 구성이 공식 이미지의 기본값이라는 뜻입니다. php:8.4.27-fpm 이미지의 풀 설정은 pm = dynamic이었는데, CVE-2026-109619 권고가 세션 업로드 진행률 경로를 검증했다고 밝힌 "일반적인 dynamic FPM 풀"이 바로 이 구성입니다. 이 이미지들을 바탕으로 만든 사내 서비스나 오래된 Dockerfile이 있다면 베이스 이미지를 다시 받아 빌드해야 합니다.
같은 날 Homebrew 공식 저장소 정보도 확인했습니다. php는 8.5.12, [email protected]는 8.4.27, [email protected]은 8.3.36, [email protected]는 8.2.35로 이미 올라와 있었고, [email protected]은 지원 종료(deprecated) 상태였습니다. 정작 필자의 맥에 깔린 PHP는 2024년 11월에 빌드한 8.4.1이었습니다. 웹 서버로 쓰지 않는 개발용이라도 로컬에서 php -S로 띄워 둔 서버가 있다면 같은 이유로 올리는 편이 낫습니다.
한국 서버가 특히 급한 이유
한국은 PHP 웹사이트가 많고, 정식 패치가 나오지 않은 구버전 비율도 높습니다. 과기정통부 관계자는 국내 웹페이지의 약 70~80%가 PHP로 추정된다고 했습니다(머니투데이). 안랩 위협 인텔리전스 플랫폼(TIP) 집계를 인용한 보도에 따르면 국내 취약 호스트는 11만 2,183개로 전 세계의 4.35%, 국가별 6위였습니다(전자신문).
버전 분포는 더 심각합니다. 프랑스 보안업체 시벨앙젤은 버전이 확인된 국내 PHP 서버 가운데 2만 4,591대, 82%가 8.1 이하라고 집계했습니다(전자신문). 말로리(Mallory)가 쇼단 데이터로 센 결과로는 버전이 노출된 한국 PHP 서버 5만 6,423건 중 89%가 8.1 이하였고, 세계 평균은 73.5%였습니다(머니투데이). 두 집계는 세는 대상과 방법이 달라 숫자를 서로 더하거나 비교하면 안 되지만, 한국의 구버전 비율이 높다는 방향은 같습니다. 8.1 이하는 정식 패치가 없으니, 이 서버들은 버전을 올리거나 소스 백포트, 배포판 백포트에 기대야 합니다.
정부도 움직였습니다. 과학기술정보통신부는 10월 22일 민간 분야 사이버위기 경보를 '주의'로 올리고 정보보호 최고책임자(CISO) 신고 기업 약 2만 8,000곳, 웹호스팅사, IDC 등에 대응 방법을 긴급 전파했습니다(지디넷코리아). 행정안전부는 국내 공공 부문은 대부분 자바 기반 전자정부 표준프레임워크를 써서 영향이 상대적으로 크지 않다고 설명했습니다(전자신문). 필자는 위험이 쇼핑몰, 커뮤니티, 기관·기업 홈페이지처럼 워드프레스나 그누보드로 만든 사이트와 이를 올린 웹호스팅에 몰린다고 봅니다. 지디넷코리아도 경보 소식에서 워드프레스·그누보드 등 국내에서 많이 쓰이는 홈페이지 제작 도구를 PHP 기반 도구로 꼽으며 영향을 받을 수 있다고 적었습니다(지디넷코리아).
호스팅사는 빠르게 대응했습니다. 카페24의 공지를 시간순으로 보면 이렇습니다.
- 10월 21일 11시 30분: 서버에
session.upload_progress.enabled = Off를 적용하는 임시 조치. 카페24는 "100% 방어를 보장하지는 않습니다"라고 밝혔습니다(카페24). - 10월 21일 밤 11시 30분: PHP 8.2 이상에 공식 보안 패치 적용. 공지의 업데이트 버전 목록은 8.2.35, 8.4.27, 8.5.12라 8.3 사용자는 적용 여부를 따로 확인하는 편이 좋습니다(카페24).
- 10월 22일 11시 30분: PHP 7.4와 8.1을 쓰는 서비스에 긴급 보안 업데이트 적용. 카페24 자체 표기로 8.1.34p2, 7.4.5p2입니다(카페24).
가비아는 10월 22일 0시부터 3시 사이 PHP 7.4, 8.4, 8.5를 대상으로 웹호스팅 긴급 보안 패치를 진행했습니다(가비아).
공유 호스팅을 쓴다면 내 계정의 PHP 버전이 공지 대상에 들었는지, 실제로 바뀌었는지 호스팅 관리 화면에서 확인하고, 공지에 없는 버전이면 호스팅사에 문의해야 합니다. 가비아는 상위 버전으로 옮기려면 사이트 소스를 고쳐야 하는 경우가 많다고 설명했는데(머니투데이), 그래서 이번처럼 호스팅사가 구버전에 백포트를 내주는 동안 전환 계획을 세워 두는 것이 현실적입니다.
그누보드를 운영하는 SIR도 게시판 답변에서 이번 취약점은 PHP 자체의 문제라 CMS 코드 수정으로는 PHP 패치를 대신할 수 없다고 밝혔습니다(SIR).
지금 할 일: 버전 확인부터 재시작, 키 교체까지
순서는 버전 확인, 업데이트, 모든 PHP 프로세스 재시작, 로그 점검, 비밀값 교체입니다. KISA 대응 수칙과 PHP 공식 공지를 합쳐 정리했습니다(KISA 보호나라 대응 수칙, php.net 보안 공지).
1. 웹 서버가 실제로 쓰는 버전을 확인한다
터미널의 php -v는 CLI 바이너리의 버전입니다. KISA는 이 값과 웹 서비스가 쓰는 버전이 다를 수 있다고 경고했습니다. 서버에 PHP가 여러 개 깔려 있으면 Apache나 PHP-FPM이 터미널과 다른 것을 쓸 수 있기 때문입니다. 웹 쪽 버전과 SAPI는 아래처럼 확인합니다. php_sapi_name()은 mod_php면 apache2handler, FPM이면 fpm-fcgi를 돌려줍니다(PHP 매뉴얼).
php -v # CLI 버전
php-fpm -v # FPM 버전 (배포판에 따라 php-fpm8.3 -v 처럼 이름이 다름)
apache2ctl -M | grep -E 'php|mpm' # mod_php와 prefork를 쓰는지
웹 요청으로 확인해야 한다면 <?php echo PHP_VERSION, ' ', PHP_SAPI;만 담은 파일을 추측하기 어려운 이름으로, 가능하면 접속 IP를 내 주소로 제한한 뒤 잠깐 올려 열어 보고 바로 지웁니다. phpinfo() 페이지를 그대로 남겨 두면 설정 전체가 외부에 드러납니다. 필자는 확인용으로 버전과 관련 설정만 읽는 스크립트를 만들어 위 Docker 이미지들과 맥에서 돌렸습니다.
<?php
// PHP2Shell(CVE-2026-109618, CVE-2026-109619) 패치 여부와 관련 설정을 읽기만 하는 점검 스크립트
$fixed = ['8.2' => '8.2.35', '8.3' => '8.3.36', '8.4' => '8.4.27', '8.5' => '8.5.12'];
$branch = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
echo 'PHP ', PHP_VERSION, ' (', PHP_SAPI, ")\n";
if (!isset($fixed[$branch])) {
echo "결과: {$branch} 브랜치는 이번 패치가 나오지 않았습니다. 지원 브랜치로 올려야 합니다.\n";
} elseif (version_compare(PHP_VERSION, $fixed[$branch], '<')) {
echo "결과: 취약 버전입니다. {$fixed[$branch]} 이상으로 올려야 합니다.\n";
} else {
echo "결과: 패치된 버전입니다.\n";
}
foreach (['session.upload_progress.enabled', 'fatal_error_backtraces', 'memory_limit', 'post_max_size'] as $k) {
$v = ini_get($k);
echo str_pad($k, 34), $v === false ? '(없음)' : var_export($v, true), "\n";
}
필자의 맥(Homebrew PHP 8.4.1)에서는 이렇게 나왔습니다.
PHP 8.4.1 (cli)
결과: 취약 버전입니다. 8.4.27 이상으로 올려야 합니다.
session.upload_progress.enabled '1'
fatal_error_backtraces (없음)
memory_limit '128M'
post_max_size '8M'
2. 배포판 패키지는 버전 번호가 아니라 패키지 이력으로 판단한다
이 스크립트는 업스트림 버전 번호만 비교하므로 배포판 패키지에서는 틀린 답을 낼 수 있습니다. 배포판은 보안 수정을 옛 버전에 덧대는 백포트 방식으로 고치기 때문입니다. 우분투 24.04의 수정 패키지는 8.3.6-0ubuntu0.24.04.13이라, php -v에는 여전히 8.3.6으로 보입니다(Ubuntu USN-8948-1). 데비안은 bookworm의 8.2.35-1~deb12u1, trixie의 8.4.27-1~deb13u1로 고쳤습니다(Debian 보안 추적기). 우분투 22.04의 PHP 8.1도 8.1.2-1ubuntu2.28로 고쳐졌습니다(Ubuntu USN-8948-1). RHEL 계열에서는 AlmaLinux가 8, 9, 10의 모든 PHP 스트림에 패치 패키지를 배포했다고 밝혔습니다(AlmaLinux). Rocky나 CentOS에 remi 저장소를 얹은 구성이라면, 라이믹스 커뮤니티는 remi(RHEL 계열)나 sury(데비안·우분투) 저장소의 PHP 7.4 패키지 버전이 7.4.33-31로 시작하면 패치된 것이라고 안내했습니다(라이믹스). 배포판 패키지를 쓴다면 apt changelog나 rpm -q --changelog로 두 CVE 번호가 반영됐는지 보는 것이 정확합니다. 라이믹스 커뮤니티 글도 우분투 기본 패키지는 업데이트 후에도 8.3.6으로 표시된다며 빌드 날짜로 구분하는 방법을 안내했습니다(라이믹스).
3. 업데이트한 뒤 PHP 프로세스를 모두 재시작한다
KISA 대응 수칙은 업데이트 후 PHP를 실행하는 모든 웹 서비스(PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed)를 반드시 재시작하라고 강조합니다. 재시작하지 않으면 메모리에 남은 이전 버전이 계속 동작해 여전히 취약하다는 것입니다. 컨테이너라면 베이스 이미지를 새로 받아 다시 빌드하고 배포해야 하고, 오래 떠 있는 FPM 풀과 Apache 프로세스도 빠짐없이 다시 띄워야 합니다. 서비스 이름은 배포판마다 다르지만 대개 아래와 같습니다.
sudo systemctl restart php8.3-fpm # 데비안·우분투 FPM (버전에 맞게)
sudo systemctl restart php-fpm # RHEL·AlmaLinux·Rocky FPM
sudo systemctl restart apache2 # 데비안·우분투 mod_php (RHEL 계열은 httpd)
4. 당장 못 올린다면 임시 조치를 하되, 안전해졌다고 믿지 않는다
php.net은 업데이트만이 완전한 해결책이고 아래 조치는 특정 공격 경로를 줄일 뿐 "do not make an installation safe"라고 못박았습니다.
- CVE-2026-109619 쪽:
session.upload_progress.enabled=0을 php.ini나 FPM 풀 설정처럼 요청 해석 전에 적용되는 곳에 넣고 모든 워커를 재시작합니다. php.net은ini_set()이나 FPM의.user.ini에 의존하지 말라고 했습니다. PHP 8.5라면fatal_error_backtraces=0도 함께 넣습니다. - CVE-2026-109618 쪽, PHP 설정:
memory_limit=-1을 애플리케이션이 덮어쓰지 못하게php_admin_value로 지정합니다. 메모리 한도에 닿는 순간이 취약한 경로이므로 그 한도를 없애는 조치입니다. 업로드가 없으면post_max_size = 8M, 있으면32M으로 둡니다. - 메모리 상한은 PHP 바깥에서: Calif는
memory_limit=-1이 메모리 고갈 위험을 키운다고 경고했습니다(Calif). 그래서 권고는 컨테이너나 cgroup 메모리 한도를 가용성 경계로 쓰되 문제의 워커만 끝내는 정책이 낫다고 덧붙였습니다.RLIMIT_AS나RLIMIT_DATA로 대신하면 오히려 취약한 경로를 부르므로 쓰지 말라고 했습니다. - 프록시 제한: 웹 서버나 프록시에서 해석한 요청 크기, 입력 필드 수, multipart 파트 수, 헤더 수, 중첩 깊이를 제한하고, 이 제한이 Content-Length가 없는 요청에도 걸리게 합니다.
- 앞단 WAF: Cloudflare처럼 이 취약점용 관리 규칙을 내놓은 곳도 있습니다(Cloudflare). 다만 공격자들이 규칙을 피하려 페이로드를 바꿔 가며 시도했다는 점을 기억해야 합니다.
- 오류 메시지 숨기기는 소용없습니다.
error_reporting,display_errors,@로 진단 메시지를 감춰도 막히지 않습니다.
php.net은 세션 업로드 진행률을 끄는 조치도 "This blocks the confirmed session paths but not other variants"라고 선을 그었습니다. 라이믹스 커뮤니티의 정리 글도 같은 취지로, php.ini의 업로드 관련 설정만 바꾸라는 정보는 "더이상 유효하지 않습니다"라며 그 설정과 관계없이 공격이 가능하다고 전했습니다(라이믹스). 임시 조치로 버틸 수 있다고 기대하지 말고 업데이트부터 하라는 뜻입니다.
5. 로그를 보고, 메모리에 있던 비밀값을 바꾼다
KISA는 정보가 유출된 10월 20일 이후의 기록을 최소한 전부 점검하라고 권했습니다. Wiz가 제시한 탐지 신호는 이상한 multipart 요청 뒤에 이어지는 메모리 한도 초과 오류나 반복되는 HTTP 500, 비정상적으로 크거나 깨진 PHPSESSID 응답 쿠키, PHP 워커가 유난히 자주 교체되는 현상입니다(Wiz Research). 메모리 노출 경로가 있었던 만큼 DB 비밀번호, API 키, 토큰은 패치 뒤 교체하는 편이 안전합니다.
마지막으로, 인터넷에 도는 PoC를 내 서버에 직접 돌려 보지 않습니다. KISA는 유포되는 PoC 중에 악성코드를 숨긴 가짜가 많다며, 취약 여부는 PoC 실행 대신 버전 확인과 기록 점검으로 판단하라고 했습니다. 외부에서 버전만 확인하고 싶다면 OpenAI가 공개한 php2shell-checker처럼 응답 헤더로 버전을 비교하는 도구가 있지만, 이 도구도 버전을 숨긴 서버는 판정하지 못합니다.
AI가 찾아냈고, 징후는 2024년에 이미 보였다
이번 취약점은 AI를 쓴 연구자들이 몇 주 만에 공격으로 완성했지만, 결함의 징후는 2024년에 PHP 재단이 먼저 봤습니다. PHP 재단은 공개 당일 글에서 2024년 Quarkslab이 수행한 외부 감사 이슈를 고치던 코어 개발자가 메모리 부족 처리 문제의 징후를 처음 봤지만, 당시에는 구체적인 공격 경로를 찾지 못했다고 밝혔습니다. 수정안마다 하위 호환성이 깨지거나 새로운 서비스 거부 위험이 생기거나 서드파티 확장을 고쳐야 했습니다. 합의로 결정하는 방식, 알려진 원격 코드 실행 경로가 없었던 점, 엔진을 깊이 아는 개발자가 적었던 점이 겹쳐 수정 작업은 수개월 동안 멈췄다 다시 시작하기를 반복했습니다. 재단은 이를 "the delay was a structural failure"라고 인정하고 다수결 의사결정과 전담 인력 확보를 개선안으로 내놨습니다(PHP 재단).
2026년에는 세 팀이 거의 동시에 같은 결함에 도달했습니다. Calif가 8월 1일 처음 발견해 8월 11일 신고했고, 같은 날 OpenAI도 따로 신고했습니다. Wiz는 8월 19일 첫 버그를 찾아 8월 24일 신고했습니다. Wiz는 연구 전반에 Gemini 4 Argon, GPT-5.6 Sol, 자체 도구 Wiz Atlas를 썼고, 익스플로잇을 여러 애플리케이션에 맞추는 일을 "in hours" 단위로 해냈으며 연구자 한 명이 일주일에 끝낸 일이 예전에는 최고 수준의 팀이 몇 달 걸렸을 일이라고 썼습니다. Calif는 첫 버그를 찾을 때 별도 하네스나 접근 제한 모델을 쓰지 않았다고만 밝히고 모델 이름은 적지 않았습니다.
공개 과정은 매끄럽지 않았습니다. PHP 재단에 따르면 10월 20일 CVE 번호가 잘못된 CVSS 10.0과 함께 새어 나갔고(실제는 9.2), PHP 프로젝트는 사전 공지를 앞당기면서 원래 22일이던 공개를 21일로 하루 당겼습니다. 그런데 10월 21일 07:41(UTC)에는 Chainguard가 조율된 공개보다 6시간 넘게 앞서 수정된 이미지를 공개해 엠바고가 깨졌습니다. 필자가 보기에 "CVSS 10"이라는 숫자가 널리 퍼진 것도 공식 공지보다 유출 정보가 먼저 돌았기 때문입니다.
이 사건이 남기는 교훈은 패치 주기입니다. 과기정통부 배경훈 부총리는 수동 점검 후 수일 내 패치라는 통상적인 취약점 관리 주기로는 공격을 막을 수 없다고 말했습니다(지디넷코리아). 오래된 결함을 AI가 몇 주 만에 공격으로 바꾸는 시대라면, PHP처럼 오래 쓰인 런타임은 "지원이 끝난 버전을 언젠가 올리자"가 아니라 "보안 패치가 나오면 하루 안에 올릴 수 있는 상태"를 유지하는 것이 기본값이어야 합니다. 이번에 8.1 이하에 묶여 손을 못 쓴 서버가 그 비용을 보여 줍니다.
출처
- php.net 보안 공지: php.net/archive/2026.php
- Wiz Research: wiz.io/blog/php2shell-php-rce
- Calif: calif.io/php2shell
- 지원 버전 표: php.net/supported-versions.php
- 지원 종료 목록: php.net/eol.php
- KISA 보호나라 대응 수칙: boho.or.kr/kr/bbs/view.do?bbsId=B0000133&menuNo=205020&nttId=72230
- Ubuntu USN-8948-2: ubuntu.com/security/notices/USN-8948-2
- W3Techs PHP: w3techs.com/technologies/details/pl-php
- W3Techs PHP 8: w3techs.com/technologies/details/pl-php/8
- GHSA-w8hw-w9fx-j79w: github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
- GHSA-6wmf-4rv5-p3rp: github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
- Hacker News: news.ycombinator.com/item?id=50167754
- GeekNews: news.hada.io/topic?id=35746
- php.net 사전 공지: php.net/archive/2026.php
- PHP 재단: thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-w…
- Red Hat: access.redhat.com/security/cve/CVE-2026-109618
- Elias Taylor: eliastaylor.com/noexploit
- GeekNews: news.hada.io/topic?id=35792
- Hacker News 토론: news.ycombinator.com/item?id=50188948
- Red Hat 보안 공지: access.redhat.com/security/vulnerabilities/RHSB-2026-013
- 연합뉴스: yna.co.kr/view/AKR20261022104100017
- Cloudflare: blog.cloudflare.com/php-vulnerabilities-waf-protection
- Huntress: huntress.com/blog/php2shell-leaked-critical-php-vulnerabilities
- CERT-EU: cert.europa.eu/publications/security-advisories/2026-018
- Hacker News 댓글: news.ycombinator.com/item?id=50191174
- NCSC-NL: ncsc.nl/alerts/kwetsbaarheden-php-patches-beschikbaar-update-nu
- 머니투데이: mt.co.kr/tech/2026/10/22/2026102214461994494
- 전자신문: etnews.com/20261022000351
- 전자신문: etnews.com/20261022000356
- 지디넷코리아: zdnet.co.kr/view/?no=20261022154137
- 카페24: help.cafe24.com/notice/system-notice/20261021_php-security-emergency
- 카페24: help.cafe24.com/notice/system-notice/20261023_php-security-official-pat…
- 카페24: help.cafe24.com/notice/system-notice/20261022_php-security-update
- 가비아: gabia.com/gabia_notice/view.php?seq_no=45886
- SIR: sir.kr/boards/free/1742368
- PHP 매뉴얼: php.net/manual/en/function.php-sapi-name.php
- Ubuntu USN-8948-1: ubuntu.com/security/notices/USN-8948-1
- Debian 보안 추적기: security-tracker.debian.org/tracker/CVE-2026-109618
- AlmaLinux: almalinux.org/blog/2026-10-21-php2shell-cve-2026-109618-cve-2026-109619
- 라이믹스: rhymix.org/free/1959477
- 라이믹스: rhymix.org/free/1959668
- php2shell-checker: github.com/openai/php2shell-checker