로그인 없이 서버가 뚫리는 PHP2Shell, 내 서버도 해당될까
Security23분 읽기

로그인 없이 서버가 뚫리는 PHP2Shell, 내 서버도 해당될까

PHP 엔진 결함 2건(CVSS 9.2)은 로그인 검사가 실행되기 전에 시작됩니다. 공개 경과와 요청이 지나는 길, 실행 방식별 차이, 내 서버 확인, 업데이트와 임시 방어, 업데이트 전 흔적 확인까지 따라갑니다.

10월 21일 오후, 작은 쇼핑몰을 운영하는 사람의 메일함에 호스팅 업체의 “긴급 PHP 업데이트 및 재시작 안내”가 와 있다고 해 보겠습니다(편집부가 만든 예시입니다). 쇼핑몰 플러그인은 지난주에 다 올렸고 관리자 비밀번호도 얼마 전에 바꿨습니다. 그런데 왜 이제 와서 PHP를 올린다는 걸까요. 내 사이트도 해당되는지, 이미 당했다면 어떻게 아는지가 한꺼번에 궁금해집니다. 이 글은 그 질문들에 사건이 흘러간 순서대로 답합니다.

별명은 PHP2Shell, 실체는 PHP 엔진의 결함 2건(CVE-2026-109618, CVE-2026-109619)입니다. 로그인도 플러그인도 상관없이 웹 요청만으로 서버에서 코드가 실행될 수 있어서, 공개 직후 은행·쇼핑몰·공공 포털까지 예방 점검에 들어갔다는 보도가 이어졌습니다. 글 속 시각은 모두 UTC입니다.

서버 터미널에서 치는 php -v의 결과와 웹 요청을 실제로 처리하는 PHP는 서로 다른 프로그램일 수 있습니다. 이 글에서 PHP 버전이라고 하면 항상 웹 쪽(Apache, PHP-FPM 등)이 쓰는 것을 뜻하며, 확인 방법은 여섯 번째 절에 있습니다.

Critical · 9.2 CVE-2026-109618 · CVE-2026-109619 패치 제공
영향 대상
PHP 8.5.0~8.5.11, 8.4.0~8.4.26, 8.3.0~8.3.35, 8.2.0~8.2.34. 8.1 이하는 지원이 끝난 버전이라 같은 결함을 안은 채 정식 수정이 없습니다. 웹 요청을 처리하는 PHP라면 Apache mod_php, PHP-FPM, FrankenPHP, LiteSpeed 모두 범위에 듭니다. 보고한 업체 Wiz는 PHP 5.4 이후 최근 10년의 거의 모든 버전이 둘 중 적어도 하나의 영향을 받는다고 설명합니다.
공격 방식
인증 없는 원격 코드 실행(RCE). 실행 권한은 웹 서버 워커 계정(예: www-data)의 것이라, 그 계정이 읽을 수 있는 설정 파일과 데이터베이스 접속 정보가 곧 노출 범위입니다.
악용 여부
공개 나흘 전인 10월 17일에 Amazon 위협 인텔리전스가 시도를 보고했습니다. 10월 21일부터는 보안업체 watchTowr의 허니팟에서 두 결함의 실제 악용이 관측됐고, 공개 증명 코드가 돈다는 보고도 나왔습니다. 정부 사이버 보안 기관이 관리하는 알려진 악용 취약점(KEV) 카탈로그(10월 23일 19:30 UTC 판)에는 두 건 모두 아직 오르지 않았습니다.
패치
PHP 8.5.12 · 8.4.27 · 8.3.36 · 8.2.35 이상(10월 21일 14:00 공개). 8.1 이하는 PHP 프로젝트가 7.4·8.0·8.1용 최소 소스 패치만 공개했고, 배포판·LTS 업체가 백포트를 내는 경우가 있습니다. 워드프레스나 플러그인을 올려도 해결되지 않고, PHP를 올린 뒤 PHP-FPM이나 Apache를 재시작해야 적용됩니다.

감사에서 묻힌 징후, 10주 걸린 수정, 하루 당겨진 공개

이 결함은 하루 만에 터진 것이 아닙니다. 첫 징후는 2024년에 한 번 나왔다가 묻혔습니다. PHP가 수년 만에 외부 보안 감사를 받던 때(OSTIF 주관, Quarkslab 수행)였고, 감사에서 나온 이슈를 고치던 코어 개발자가 메모리 부족(OOM) 처리의 이상 징후를 찾았습니다. 당시엔 구체적인 공격 경로가 보이지 않았고 더 급한 이슈가 먼저였습니다. 이후 나온 수정안들은 호환성 파괴나 확장 기능 대량 수정이라는 대가가 따라 몇 달씩 멈췄다 재개되기를 반복했습니다. PHP 재단(PHP Foundation)은 이것을 스스로 “구조적 실패”라고 평가합니다.

올해 경과는 아래 시간표가 한눈에 보여 줍니다. 위쪽 파란 점이 조용히 진행된 협조 공개 구간이고, 붉은 점이 그 체제가 흔들린 구간입니다.

2026년 8월 11일 첫 보고부터 10월 23일 세션 안내까지 여덟 개 사건을 UTC 날짜로 늘어놓은 타임라인 그림 1. 첫 보고에서 공개 이후 안내까지(PHP 재단·Wiz·Sophos·watchTowr 공개 자료 기준, 모두 UTC).

7월 30일 보안업체 Calif가 ‘Patch the Planet’ 프로그램 아래에서 PHP 감사를 시작했고, 8월 11일 Calif 소속 연구원이 같은 뿌리의 문제로 인증 없이 코드를 실행하는 경로를 보고하면서 수정이 최우선 과제가 됐습니다. Wiz는 8월 19일 첫 취약점을 발견하고 20일 드루팔, 21일 워드프레스에서 인증 없는 RCE를 확인한 뒤 24일 PHP 보안팀에 보고했습니다. 9월 18일에는 OpenAI가 같은 뿌리의 문제를 독립적으로 보고한 데 이어 추가 공격 경로를 보고했습니다.

가장 눈에 띄는 것은 첫 보고(8월 11일)부터 공개까지 약 10주가 걸렸다는 간격입니다. 메모리 관리와 오류 처리를 건드리는 수정이라 하나를 고치면 다른 데서 깨졌기 때문입니다. 그리고 마지막 이틀에 체제가 흔들렸습니다. 재단이 밝힌 10월 20일의 흐름은 이렇습니다. 15:00경 두 CVE 번호가 포럼과 SNS에 새어 나갔고(잘못된 릴리스 시각과 CVSS 10.0이 붙어 있었고 기술 세부는 없었음), 19:30에 PHP가 사전 공지를 냈으며, 20:00경 Calif가 자사 증명 영상을 실수로 공개했습니다. 23:00경에는 주요 기반시설 사업자에게 준 완화 안내도 유출됐을 수 있다고 재단은 밝힙니다. 21일 03:02에 Cloudflare가 WAF 규칙을 가동했고, 07:41에는 컨테이너 이미지 업체 Chainguard가 수정이 든 이미지를 먼저 올려 엠바고가 6시간 넘게 앞당겨졌습니다. 수정 버전은 원래 계획(22일 14:00)보다 하루 이른 21일 14:00에 공개됐습니다.

공개 뒤에도 일은 이어졌습니다. 10월 22일 Amazon Linux 2023 패키지와 Elastic Beanstalk 플랫폼이 나왔고, 23일에는 PHP가 세션 업로드 진행률에 관한 별도 안내를 냈습니다(여덟 번째 절). 26일에는 윈도용 PHP 빌드가 8.5.12-1, 8.4.27-1, 8.3.36-1, 8.2.35-1로 다시 나왔습니다. 번들 라이브러리 Brotli의 별개 결함(CVE-2026-111497) 수정을 담은 재빌드이고 PHP 버전 자체는 그대로이니, 윈도에서 PHP를 쓴다면 이쪽 빌드를 받으면 됩니다.

로그인 검사보다 먼저 일어나는 일: 요청이 지나는 길

왜 앱을 최신으로 올려도 소용이 없는지는 요청 하나가 거치는 길을 따라가 보면 보입니다. 아래 그림에서 로그인 검사가 있는 칸과 결함이 있는 칸의 위치를 비교해 보세요.

클라이언트에서 프록시, PHP 실행 방식, 요청 시작 단계, 앱 코드, 종료 정리로 이어지는 길과 두 결함이 앉은 자리를 그린 그래프 그림 2. 요청이 지나는 길과 결함의 자리(개념도). 실제 공격 절차가 아니라 “어디서 무엇이 남는가”만 보여 줍니다.

결함은 붉은 칸, 곧 요청 시작 단계 근처에서 생깁니다. 요청 본문(POST, 파일 업로드)을 읽어 변수로 만들고, 설정을 적용하고, 업로드 진행률 같은 세션 기능을 준비하는 단계입니다. 로그인 화면과 플러그인 보안은 오른쪽 초록 칸, 앱 코드의 일입니다. 그 칸에 닿기 전에 벌어지는 일은 앱이 막을 수 없습니다. CrowdStrike와 Datadog가 따로 재현한 결과도 같았습니다. 로그인도 세션도 취약한 앱 코드도 필요 없었고, 아무 PHP 파일이 하나만 있으면 경로가 노출됐습니다. 정적인 문의 폼이나 상태 확인용 페이지도 예외가 아닙니다.

왼쪽 회색 칸의 프록시와 WAF는 요청의 크기와 모양을 걸러 줄 수 있는 자리입니다. 뒤에서 보겠지만 그래서 크기·필드 수 제한이 임시 방어의 핵심이 됩니다. 한편 설정 파일도 이 길의 변수입니다. mod_php에서는 .htaccess나 Apache 설정의 php_value 같은 지시어가 요청 시작 전에 적용되는데, 폐기된 설정이나 잘못된 값이 경고를 내면 그 경고가 낡은 상태를 건드리는 기회가 됩니다. PHP 어드바이저리는 경고를 화면에 안 띄우거나 error_reporting을 바꾸거나 @를 붙여 숨기는 것으로는 소용이 없다고 못 박습니다.

같은 실패, 실행 방식마다 다른 길

같은 결함이어도 PHP를 웹 서버에 어떻게 연결했는지에 따라 길이 달라집니다. 아래 격자는 PHP 어드바이저리 두 건의 서술을 편집부가 네 단계로 나눠 놓은 것입니다.

Apache mod_php, PHP-FPM, FrankenPHP, LiteSpeed를 시작 단계 메모리 소진, 이른 설정값, 세션 업로드 세 경로와 맞대어 어드바이저리가 말한 도달 정도를 색으로 보인 히트맵 그림 3. 실행 방식별 경로 도달 정도(편집부 분류). 점선 칸은 어드바이저리가 말하지 않은 칸입니다.

붉은 칸이 가장 짙은 곳이 mod_php입니다. 어드바이저리는 mod_php가 요청 시작이 실패해도 종료 정리를 무조건 호출한다고 설명하고, 기본 한도에서 전체 RCE가 확인됐다고 밝힙니다. 앱 코드가 실행되기 전에 공격이 시작될 수 있는 이유입니다. PHP-FPM은 시작이 실패하면 자식 프로세스를 종료해서 이 경로가 끊기지만, 시작을 통과한 뒤 오류가 나는 경로는 열려 있고 세션 업로드 경로는 FPM에서도 확인됐습니다. FrankenPHP는 소스 분석상 mod_php와 비슷하게 종료 정리를 부른다고 하고, LiteSpeed는 FPM과 비슷한 모델이라 조건부로 봅니다.

이 격자를 “FPM이면 안전하다”로 읽으면 안 됩니다. 이른 설정값 칸의 ’NOT FOUND’는 기본 설정에서 그런 경로를 찾지 못했다는 뜻이지 면역이라는 뜻이 아닙니다. 덜 위험하다는 것과 올리지 않아도 된다는 것은 다른 말입니다. 워커가 재사용되기 때문에 한 요청이 남긴 흔적이 다음 요청에 이어지는 구조도 모든 방식에 공통입니다.

오류가 남긴 반쪽 상태를 믿는 구조, 그리고 패치가 바꾼 것

두 결함의 뿌리는 같습니다. 오류가 난 뒤 남은 상태를 PHP가 정상으로 믿는 것입니다.

CVE-2026-109618(OOM 손상) 에서 PHP는 메모리가 모자라면 longjmp()라는 방식으로 지금 맥락에서 곧바로 빠져나옵니다. 요청 시작 단계에서 본문을 읽던 중 할당이 실패하면 방금 만들던 구조가 반쯤 초기화된 채 남고, 요청 종료 정리가 그것을 읽거나 그 값에 따라 분기합니다. 실패한 할당이 그 구조와 무관해도 마찬가지입니다. 여기에 이르는 메모리 소진 방법이 여럿이라는 점이 문제를 키웠습니다. 어드바이저리에는 길이를 알리지 않은 업로드 요청, 헤더가 끝없이 붙는 멀티파트 파트, 중첩된 필드 이름이 배열 작업을 곱절로 불리는 경우가 예로 나옵니다. 또 사용할 수 있는 길이 선언이 없는 멀티파트 요청은 post_max_size의 첫 검사를 통과한 채 데이터를 계속 받을 수 있습니다.

CVE-2026-109619(낡은 전역 상태) 는 오류·세션·객체 상태가 워커의 전역 변수에 걸려 있다가, 치명적 오류로 정리가 건너뛰어지면 이미 해제된 요청 메모리를 계속 가리키는 문제입니다. 같은 워커가 받은 다음 요청의 초기화 이전 동작(설정 적용, 세션 업로드 진행률 처리 등)이 그 낡은 상태를 읽습니다. 한 요청이 상태를 남기고 다른 요청이 쓰기 때문에 두 요청이 같은 워커에 닿아야 성립하고, 워커가 많으면 성공 확률이 낮아질 뿐 조건이 사라지지는 않습니다. 8.5에서는 치명 오류 때 백트레이스 정보를 요청이 가진 메모리에 저장하는 기능(fatal_error_backtraces)이 가장 확실한 경로로 확인됐고, 세션 업로드 진행률 기능은 기본으로 켜져 있습니다.

Wiz는 여기에 코드 실행이 아닌 프로세스 메모리를 읽어 내는 경로를 하나 더 보고했습니다. 메모리 부족으로 요청 힙이 파괴된 뒤 남은 세션 ID 포인터를 다음 멀티파트 요청이 읽으면, 이미 반환된 메모리의 내용이 PHPSESSID 쿠키 값으로 응답에 실려 나올 수 있다는 것입니다. 그 메모리에는 다른 요청의 비밀번호, 토큰, API 키, 인증서가 들어 있을 수 있어서, 코드 실행까지 가지 않아도 정보가 새는 길이 있습니다.

CVSS 점수가 만점이 아닌 이유도 여기서 읽힙니다. 두 결함 모두 공격 경로는 네트워크이고 필요한 권한과 사용자 조작은 없지만, 점수 벡터에 공격 복잡도 High와 공격 요건 있음이 붙어 9.2가 됐습니다(공개 직전 유출된 10.0은 잘못된 값이었습니다). 어드바이저리가 드는 조건은 요청 두 개가 같은 워커에 닿아야 한다는 점과, PHP 빌드·확장·앞단 서버·메모리 배치에 따라 이식성이 달라진다는 점입니다. 다만 “어렵다”가 “불가능하다”는 아닙니다. 어려운 일도 틀이 한 번 만들어지면 자동화되기 때문입니다.

패치의 핵심은 갑작스러운 탈출을 없앤 것입니다. 8.5.12 등에서 PHP는 OOM 때 longjmp()를 쓰지 않고, 소프트 한도(memory_limit)에 닿으면 다음 안전 지점에서 정상 오류로 처리하며, 그보다 높은 하드 한도에 닿으면 곧바로 워커를 종료(_exit())합니다. 낡은 상태 쪽은 요청이 가진 전역 상태를 다시 쓰기 전에 초기화하고 종료 때 오류·예외 상태를 비우는 전체 초기화로 고쳤습니다. 하드 한도에 닿으면 워커가 곧바로 사라지므로 업데이트 직후에는 오류와 워커 종료 로그가 이전과 달라질 수 있습니다. 한편 Datadog는 이번 수정이 같은 종류 버그를 구조적으로 없앴다기보다 오류 처리 방식을 바꾼 것이어서 비슷한 문제가 더 나올 수 있다고 내다봅니다.

점검 공지와 관측 보고를 네 칸으로 읽기

공개 직후의 헤드라인은 서로 다른 일을 한 줄로 뭉뚱그리기 쉽습니다. 위험이 커지는 단계는 넷이고, 서로 다른 일입니다.

  1. 약점이 존재한다. 수정 전 PHP에 취약점이 있다는 사실입니다. 이번에는 확인됐고 수정 버전도 나왔습니다.
  2. 공격 시도가 있었다. 약점을 노린 통신이 도달했다는 관측입니다. 시도일 뿐입니다.
  3. 침해가 성공했다. 실제로 서버에서 코드가 실행된 경우입니다.
  4. 정보가 유출됐다. 개인정보 등이 실제로 빠져나간 경우로, 서비스별 공식 발표로만 판단할 수 있습니다.

서비스 정지는 대부분 12단계에서 하는 예방 조치입니다. 입구를 닫아 두고 수정과 점검을 안전하게 하려는 것이라서 정지 소식이 곧바로 34단계를 뜻하지는 않습니다. 서비스를 이용하는 입장이라면 공식 앱이나 즐겨찾기로 들어가 운영사의 공지를 확인하는 게 먼저입니다. “긴급 대응이니 지금 비밀번호를 입력하라”는 문자나 메일은 이런 때 가장 많이 늘어나는 피싱이니 링크를 누르지 마세요.

“영향 없음”이라는 발표도 범위를 봐야 합니다. AWS 보안 공지는 AWS 서비스 자체는 영향받지 않는다고 하면서, 이용자가 PHP를 올려 쓰는 서비스에는 이용자 쪽 조치가 필요하다고 안내합니다. Elastic Beanstalk은 플랫폼 업데이트를 적용해야 하고, 직접 만든 PHP 런타임이나 컨테이너 이미지로 돌리는 Lambda는 PHP를 올려 다시 배포해야 하며, Lightsail은 실행 중인 인스턴스를 제자리에서 고칠 수 없어 새 인스턴스로 옮겨야 합니다. 모니터링 도구 Ganglia를 켠 일부 EMR 클러스터에도 영향이 있었습니다. 보안 업체도 예외가 아니어서, Sophos는 자사 제품은 영향이 없지만 지원이 끝난 장비용 라이선스 포털이 영향 버전의 PHP로 돌고 있어 10월 21일 내렸다고 밝혔습니다.

공격 쪽 관측은 주체마다 재는 대상이 달라서 나눠 읽어야 합니다.

관측한 곳 확인된 내용 읽는 법
Amazon 위협 인텔리전스 공개 나흘 전인 10월 17일 시도 보고(Sophos, Datadog가 전함) 이미 노리는 움직임이 있었다는 신호이며 피해 여부와는 별개
watchTowr 10월 21일부터 자사 허니팟 네트워크에서 두 결함의 실제 악용을 관측(10월 22일 갱신) 미끼 서버에 닿은 공격이며 건수는 공개되지 않음
Cloudflare 자사 고객을 겨냥한 공격 트래픽을 봤고 공격자가 페이로드를 바꿔 규칙을 피하려 한다고 밝힘 회사의 설명이며 건수는 공개되지 않음
Datadog 무차별 스캔을 파악했고 공개 증명 코드가 여러 개이며 일부는 더 일반적인 공격 트래픽을 보낸다고 함. Patchstack은 여러 악용 사례를 보고했다고 전함 시도와 스캔의 규모를 보여 주지만 침해 성공 건수는 아님
CrowdStrike 자체 실험에서 단일 요청만으로 mod_php 워커가 명령을 실행했고 x86-64와 ARM 모두 재현, 워커 재시작에도 안정적 실험실 결과이며 실환경 악용을 관측했다고 밝힌 것은 아님
Sophos 공개 증명 코드가 있다고 전하면서도 자사 환경에서는 스캔과 소수의 실패한 시도만 봤고 성공한 침해 증거는 못 찾음(어드바이저리 v4, 10월 22일). 방화벽·엔드포인트용 탐지 서명을 냄 한 환경의 관측 결과
Sansec 공개 뒤 15분 안에 대규모 공격이 시작될 것이라고 경고 예측이지 관측이 아님
알려진 악용 취약점(KEV) 카탈로그 10월 23일 19:30 UTC 판에 두 건 모두 없음 등재 여부는 악용 여부와 같은 말이 아님

이 표를 합치면 “당장 널리 터졌다”도 “과장이다”도 아닙니다. 공식 어드바이저리의 “공격 복잡도 High”와 CrowdStrike의 안정적 재현은 모순이 아니고, 허니팟이 본 공격과 Sophos가 못 찾은 성공 침해도 모순이 아닙니다. 서로 다른 곳에서 다른 것을 쟀을 뿐입니다. 그리고 CrowdStrike와 Datadog가 공통으로 짚는 점이 있습니다. 성공한 공격도 워커가 죽지 않고 HTTP 응답이 200으로 나가며 애플리케이션 로그에 이상이 없을 수 있다는 것입니다. 흔적이 없다고 공격이 없었다고 단정할 수 없고, 수정 버전을 설치한 것과 그 이전에 침해가 없었다는 것은 별개의 확인입니다.

우리 서버는 어느 칸인가: 읽기 전용 확인

먼저 웹이 쓰는 PHP가 무엇인지부터 봅니다. 아래는 전부 값을 읽기만 하는 명령입니다.

php -v                            # 터미널(CLI)의 PHP 버전. 웹 서버가 쓰는 PHP와 다를 수 있습니다
php-fpm -v                        # PHP-FPM을 쓴다면 이쪽 버전도 확인(배포판에 따라 php-fpm8.3 처럼 이름이 다릅니다)
apachectl -M | grep -i php        # Apache에 PHP 모듈(mod_php)이 붙어 있는지

# 실제로 떠 있는 웹 쪽 프로세스(어떤 방식으로 도는지)
ps -eo user,pid,ppid,args | grep -E 'php-fpm|apache2|httpd|php-cgi|frankenphp|lsphp' | grep -v grep

# 컨테이너로 돌린다면 호스트가 아니라 컨테이너 안에서 확인
docker ps --format '{{.Names}}  {{.Image}}'
docker exec <컨테이너 이름> php -v

# 메모리·업로드 관련 설정값(CLI 기준이라 웹의 값과 다를 수 있습니다)
php -i | grep -E '^(memory_limit|post_max_size|upload_max_filesize|session.upload_progress.enabled|Loaded Configuration File)'

예를 들어 우분투 VPS에서 PHP-FPM 8.3을 쓰는 서버라면 화면이 아래처럼 나옵니다(편집부가 만든 예시 출력입니다). 첫 줄 버전이 8.3.36 미만이면 아직 고쳐지지 않은 서버이고, mod_php 줄에 아무것도 안 나오면 PHP-FPM 방식입니다.

우분투 VPS 예시 화면에서 PHP-FPM 버전 확인, mod_php 모듈 확인, 프로세스 목록, 변경 기록 검색 네 명령을 차례로 실행한 터미널 이미지 그림 4. 한 서버에서 네 명령을 이어 치는 예시(출력은 편집부 예시). 마지막 줄은 백포트 확인 방법이며 아래에서 설명합니다.

버전 번호가 그대로인데 수정만 들어간 경우도 있습니다. 배포판 업체가 수정을 옛 버전에 이식(백포트)하면 번호가 바뀌지 않기 때문에 변경 기록에 이번 CVE 번호가 있는지 봅니다. 패키지 이름은 환경에 맞게 바꿉니다.

apt changelog php8.3-common 2>/dev/null | grep -m3 'CVE-2026-10961'      # 데비안·우분투 계열
rpm -q --changelog php-common | grep -m3 'CVE-2026-10961'                # 레드햇 계열

워드프레스라면 관리자 화면의 도구 → 사이트 상태 → 정보 → 서버에서 PHP 버전과 PHP SAPI를 볼 수 있습니다. SAPI가 fpm-fcgi면 PHP-FPM, apache2handler면 mod_php입니다. 설정값은 터미널의 CLI 값이 아니라 웹을 거쳐 본 값이 기준이라서, Sophos도 설정을 적용한 뒤 CLI가 아닌 실제 웹 요청으로 확인하라고 권합니다.

설정값을 볼 때 알아 둘 점이 있습니다. CrowdStrike 실험에서는 memory_limit이 post_max_size의 대략 4배보다 작을 때 메모리 소진 경로가 열렸고 기본값(8M, 128M)은 그 조건에 들지 않았다고 합니다. 하지만 PHP 어드바이저리는 기본 입력·메모리 한도에서도 전체 RCE가 확인됐다고 밝히므로 한도 값만 보고 안전하다고 판단하지 마세요. 디렉터리별 .htaccess나 가상 호스트 설정으로 값이 달라질 수 있어서 공유 호스팅의 사이트 하나가 따로 취약해질 수도 있습니다.

바깥에서 본 현황도 참고가 됩니다. 보안업체 CybelAngel이 인용한 10월 20일의 Shodan 배너 집계에서 PHP라고 밝히는 호스트는 약 224만 개였고, 버전을 알려 주는 약 150만 개를 나눠 보면 이렇습니다.

버전을 밝힌 PHP 호스트를 8.28.5 61만 2천 개, 5.x 36만 9천 개, 7.x 32만 9천 개, 8.08.1 18만 6천 개로 비교한 가로 막대 차트 그림 5. 버전 계열별 호스트 수(단위 천 개). 붉은 세 막대의 합 88만 3,717개가 전체의 59%입니다.

붉은 세 계열은 정식 수정이 나오지 않는 쪽입니다. 읽을 때 주의할 점이 셋 있습니다. 호스트 수이지 사이트 수가 아니고(공유 호스팅은 한 주소 뒤에 사이트가 많음), 버전을 숨긴 약 74만 개는 빠져 있으며, 파란 8.2~8.5 계열 안에도 수정판으로 올리지 않은 호스트가 섞여 있습니다. 배포판이 수정을 이식한 오래된 번호의 호스트가 안전할 수도 있습니다.

판정이 막히기 쉬운 상황 셋은 이렇게 풉니다.

  • 렌털(공유) 호스팅의 워드프레스 사이트. PHP 업데이트는 업체의 일입니다. 사이트 상태에서 버전을 보고 업체 공지의 대응 날짜와 대상 버전을 확인하세요. 모르겠다면 “PHP2Shell(CVE-2026-109618/109619) 수정 버전으로 업데이트됐나요? 내 계정의 PHP 버전도 해당되나요?“라고 그대로 물으면 됩니다. 관리화면에서 PHP 버전을 직접 고르는 방식이라면 선택한 버전이 수정 계열인지 확인해야 합니다.
  • 혼자 운영하는 VPS(우분투, nginx, PHP-FPM 8.1). 8.1은 공식 수정이 없는 계열입니다. 배포판 업체의 백포트가 있는지 위 변경 기록 명령으로 보고, 없다면 지원되는 버전(8.3이나 8.4)으로 옮기는 것이 근본 해법입니다. 이전하는 동안은 관리자 경로를 VPN이나 IP 허용 목록으로 좁히고, 업로드가 필요 없다면 막아 두세요. 이전은 스냅샷, 스테이징에서 플러그인·확장 호환 확인, 전환 순서로 합니다.
  • 도커 서비스(php:8.x-apache 같은 베이스 이미지). 호스트의 php -v는 의미가 없고 컨테이너 안의 PHP가 기준입니다. 이미지를 수정판으로 다시 빌드해 실행 중인 컨테이너를 교체해야 합니다. 이름에 -apache가 붙은 이미지는 대개 mod_php 방식이어서 위험이 큰 쪽입니다.

PHP를 CLI 스크립트로만 돌리고 웹 요청을 받지 않거나, PHP가 번들된 제품 없이 서비스를 이용만 한다면 직접 영향은 없습니다(운영사 공지 확인은 별개).

업데이트와 재시작, 환경마다 다른 손길

순서는 단순하지만 사고는 세 번째 단계에서 납니다. 업데이트를 하고도 재시작을 빼먹으면 실행 중인 프로세스는 낡은 코드를 계속 씁니다.

  1. 스냅샷 — 백업이나 서버 스냅샷을 먼저 떠 둡니다.
  2. 업데이트 — 수정 버전 또는 배포판의 수정 패키지를 설치합니다.
  3. 재시작 — PHP-FPM, Apache를 재시작합니다. 이 단계를 빼면 옛 코드가 그대로 돕니다.
  4. 웹에서 확인 — 터미널이 아니라 웹을 거쳐 버전과 설정을 확인합니다.
  5. 기능 점검 — 화면, 로그인, 문의 폼이 이전과 같은지 봅니다.

환경마다 업데이트의 주체와 길이 다릅니다. AWS 쪽은 공식 보안 공지(2026-143-AWS)와 Amazon Linux 보안 센터의 권고 번호로 확인한 내용입니다.

  • 렌털(공유) 호스팅: 업체가 업데이트합니다. 업체 공지의 대상 버전과 완료 시각을 확인합니다.
  • Amazon Linux 2023: 10월 22일 php8.2~8.5 수정 패키지가 공개됐습니다. 권고 번호는 ALAS2023-2026-3301(8.2), 3302(8.3), 3303(8.4), 3304(8.5)이고, 저장소 버전을 고정하는 방식이라 각 권고의 명령을 쓰는 것이 안전합니다.
  • Elastic Beanstalk: PHP 8.2~8.5 플랫폼 새 버전이 나왔습니다(8.5·8.4는 10월 21일, 8.3·8.2는 22일). 관리형 플랫폼 업데이트를 켜 두면 다음 유지보수 시간에 자동 적용되고, 아니면 환경의 플랫폼 버전을 직접 올립니다.
  • Lambda · Lightsail: Lambda는 관리형 PHP 런타임이 없어서 직접 만든 런타임이나 컨테이너 이미지의 PHP를 올려 다시 배포합니다(Bref를 쓴다면 Bref 보안 릴리스를 따릅니다). Lightsail은 새 청사진으로 새 인스턴스를 만들어 옮깁니다.
  • 컨테이너와 8.1 이하: 베이스 이미지를 수정판으로 다시 빌드해 교체합니다. 8.1 이하는 배포판·LTS 업체의 백포트나 소스 패치를 확인하고, 근본적으로는 지원되는 버전으로 이전합니다.
# Amazon Linux 2023 예시(8.3). 8.2는 3301, 8.4는 3303, 8.5는 3304 권고의 명령을 쓴다
sudo dnf update php8.3 --releasever 2023.12.20261021
sudo systemctl restart php-fpm

8.2를 쓴다면 일정도 챙겨 두세요. php.net은 PHP 8.2가 2026년 12월 31일에 지원이 끝나 그 뒤로는 보안 수정이 없다고 공지했습니다. 이번 수정판은 8.2의 마지막 구간에 들어온 셈이라, 올리면서 다음 계열로 옮길 계획도 같이 세우는 게 좋습니다. 어느 서버에 어떤 OS와 PHP가 누구 관리로 돌고 있는지 바로 답할 수 있는 목록이 있으면 대응이 가장 빨라지고, 업체가 대신 관리하는 서버도 같은 목록에 넣어 두면 좋습니다.

올릴 수 없는 동안 쓰는 임시 방어와 한계

당장 올릴 수 없는 사정이 있다면 PHP 공식 안내의 임시 완화를 쓸 수 있습니다. 먼저 못 박아 둘 것이 있습니다. 공식 안내는 이 조치들이 특정 공격 경로를 줄일 뿐 설치를 안전하게 만들지는 않는다고 분명히 말합니다.

대상 결함 설정 막는 것 한계
109618 memory_limit = -1 메모리 한도 실패 지점을 없앱니다 php_admin_value처럼 앱이 덮어쓸 수 없게 걸고, 어느 풀·디렉터리 설정이 유한값을 되살리지 않는지 확인해야 합니다. 요청 하나가 서버 메모리를 다 쓸 수 있고 할당 자체가 실패하면 경로가 남습니다
109618 post_max_size 줄이기 큰 요청을 일찍 끊습니다 업로드가 필요 없으면 8M, 필요하면 32M으로 두고 upload_max_filesize는 그보다 작게 합니다. 웹 서버·프록시에서도 디코딩 후 크기, 필드·파트·헤더 수, 중첩 깊이를 제한하고 길이 표시가 없는 요청도 포함해야 합니다
109619 session.upload_progress.enabled = 0 확인된 세션 업로드 경로 php.ini, FPM 풀, 서버 설정처럼 요청 파싱 전에 적용되는 곳에 쓰고 모든 워커를 재시작합니다. ini_set()이나 .user.ini는 소용이 없고 다른 변형은 못 막습니다
109619 (PHP 8.5) fatal_error_backtraces = 0 8.5에서 확인된 치명 오류 백트레이스 경로 하나 마찬가지로 모든 워커를 재시작해야 적용되고, 요청이 가진 다른 상태는 그대로입니다

공식 안내에는 하면 안 되는 방법도 있습니다. 프로세스 수준의 RLIMIT_AS나 RLIMIT_DATA로 메모리를 막는 것은 오히려 결함이 기대는 할당 실패를 일으키므로 쓰지 말라고 합니다. 컨테이너·cgroup 메모리 한도는 가용성의 경계로는 쓸 수 있지만, 컨테이너 전체가 죽으면 마스터와 모든 워커가 함께 죽을 수 있어 워커마다 별도 그룹일 때만 안전합니다. 성능이 허용한다면 pm.max_requests=1로 워커 재사용을 줄이는 방법도 있습니다.

WAF는 이 표의 설정 완화와 같은 층의 보조 방어입니다. Cloudflare는 10월 21일 03:02 UTC부터 관련 규칙을 가동해 WAF가 있는 모든 요금제(무료 포함)에서 기본으로 켜 두었고, 유료 이용자는 규칙 모음이 차단 모드인지 확인해야 합니다. 요청만으로는 공격과 정상을 가리기 어려워 응답까지 함께 보는 탐지를 처음 도입했다고 합니다. AWS WAF의 PHP 관리형 규칙 그룹도 두 CVE를 막도록 갱신 중이고 기본 버전은 자동으로 보호됩니다. 하지만 두 회사 모두 PHP를 올리는 것이 근본이라고 말하고, Cloudflare는 공격자가 페이로드를 바꾸고 있다고 밝혔습니다. 원본 서버가 WAF를 우회해 직접 접근될 수 있다면 규칙은 소용이 없으니 그 길도 막아 두세요.

지금 올릴 수 없다면 접근을 좁히는 것이 가장 확실한 임시 대책입니다. 관리자 화면과 업로드 경로를 VPN이나 IP 허용 목록 뒤로 옮기고, 업로드·멀티파트 요청이 필요 없는 서비스는 웹 서버에서 막으세요. 설정 한두 개를 바꾸는 것보다 요청이 PHP에 닿는 길 자체를 줄이는 쪽이 더 많은 경로를 한꺼번에 줄입니다.

10월 23일에 PHP가 낸 별도 안내도 이 맥락에 있습니다. 다음 정기 버그 수정 릴리스까지 session.upload_progress.enabled=0을 유지하라는 것인데, 특이한 설정 조합에서만 성립하는 또 다른 RCE 경로(CVE-2026-115560)를 막기 위해서입니다. 앱이 설정에서 session.serialize_handler를 php_binary나 php_serialize로 두고, 런타임에 ini_set()으로 php로 바꾼 뒤 session_start()를 부르면서 업로드 진행률이 켜져 있을 때가 조건입니다. 업로드 진행률 표시를 쓰지 않는다면 꺼 두어도 불편이 거의 없습니다.

업데이트 전에 이미 지나갔는지 흔적 보기

업데이트는 앞으로를 막을 뿐 이미 일어난 일을 되돌리지 않습니다. 공개 전인 10월 17일부터 시도가 있었고 PHP가 첫 보고를 받은 것이 8월 11일이므로, Sophos는 8월 1일 이후 활동을 살피라고 권합니다. 흔적은 한 곳에 모이지 않아서 여러 곳을 겹쳐 봐야 합니다.

어디를 보나 의심할 신호 비고
PHP·웹 서버 오류 로그 “Allowed memory size of … exhausted” 메시지에 “in Unknown on line 0”이 붙은 줄 어떤 PHP 파일도 실행되기 전 시작 단계에서 실패했다는 표시. 한 워커에서 2~4건이 짧게 몰릴 수 있음
웹 서버 오류 로그 “exit signal Segmentation fault (11)” 추측에 실패한 시도의 흔적. 이 뒤 조용해질 수 있음
WAF·프록시 로그 PHPSESSID나 PHP_SESSION_UPLOAD_PROGRESS 필드가 든 멀티파트 POST, 수백 개의 파트, 깊게 중첩된 필드 이름 일반 접근 로그에는 요청 본문이 없어 보통 WAF나 프록시 로그에서만 보임
응답 헤더 값이 비정상적으로 길거나 인코딩·이진 데이터가 섞인 PHPSESSID 쿠키 메모리 노출 시도의 징후(Wiz, Datadog)
프로세스 웹 워커(apache2, php-fpm 계정)가 sh·bash를 띄우고 바깥으로 연결 가장 직접적인 증거. 명령 이름만 믿지 말 것
파일·계정 웹 루트의 새 PHP 파일이나 수정된 PHP, 새 관리자 계정, 예상 못 한 크론 작업, 처음 보는 외부 연결 내가 한 작업과 맞는지 대조(watchTowr도 같은 항목을 꼽음)

메모리 소진 메시지 하나로 공격이 있었다고 단정할 수는 없습니다. 정상 작업도 메모리를 다 쓰는 경우가 있으니 비정상적으로 큰 POST나 워커 종료, 업스트림 연결 끊김과 함께 봐야 합니다. 읽기 전용 명령으로 시작할 수 있고, 경로와 계정 이름은 환경에 맞게 바꿉니다.

# 시작 단계 메모리 소진 흔적 (로그 경로는 환경에 맞게)
sudo grep -rh "Allowed memory size" /var/log/apache2 /var/log/nginx /var/log/php* 2>/dev/null | grep "Unknown on line 0" | tail -20

# 지금 웹 서버 계정이 띄운 셸이 있는지 (계정명은 환경에 맞게. 현재 떠 있는 것만 보입니다)
ps -eo user,pid,ppid,args | awk '$1=="www-data" && $4 ~ /(^|\/)(sh|bash|dash)$/'

# 8월 1일 이후 바뀐 PHP 파일, 워드프레스 업로드 폴더 안의 PHP(보통은 없어야 정상)
find /var/www -type f -name '*.php' -newermt 2026-08-01 -ls
find /var/www/html/wp-content/uploads -type f -name '*.php' -ls

결과가 나왔다고 곧바로 침해는 아닙니다. 업데이트나 배포로 바뀐 파일도 있으니 내가 한 작업과 맞지 않는 항목이 있는지를 봅니다. 반대로 아무것도 안 나와도 안전하다는 증명은 못 됩니다. PHP 공식 어드바이저리도 로그가 깨끗하다고 안전한 것은 아니라고 경고합니다. 과거 로그가 필요한 수준을 기록하지 않았을 수 있고, PHP가 메시지를 쓰기 전에 실패할 수 있으며, 점검은 실제로 지나간 경로만 보기 때문입니다. 의심스러운 항목이 있다면 순서는 이렇습니다.

  1. 격리 — 서버를 외부와 분리하고 스냅샷을 떠 둡니다. 서버를 바로 지우거나 되돌리지 말고 증거(로그, 파일)를 보존합니다.
  2. 자격 증명 교체 — 웹 서버 계정이 읽을 수 있던 데이터베이스 비밀번호, API 키, 설정 파일 속 비밀을 모두 바꿉니다. 실행 권한이 곧 그 계정의 읽기 권한이라서 설정 파일의 비밀이 가장 먼저 노출됩니다.
  3. 상의 — 호스팅 업체의 보안 담당이나 전문 업체와 상의하고, 이용자 정보가 있다면 신고·통지 의무를 확인합니다.

그리고 하나 더. ’PoC(증명 코드)’라며 돌아다니는 파일을 받아 실행하지 마세요. 이번 대응에 PoC는 필요하지 않습니다. 화제의 취약점을 미끼로 악성코드를 심는 수법은 흔하고, 진위를 확인할 수 없는 코드를 내 서버나 PC에서 돌리면 오히려 침해 경로가 됩니다.

공개까지 10주, 공격 코드는 몇 시간

이 사건의 시간표에는 서로 다른 시계가 셋 들어 있습니다. 코드는 오래됐습니다. CrowdStrike 분석에 따르면 CVE-2026-109618과 관련된 해시 테이블 순서 변경은 2017년 10월의 최적화로 PHP 7.3.0(2018년 12월)부터 들어갔고, CVE-2026-109619의 직렬화 경로는 PHP 7.1부터, 세션 경로는 PHP 5.4부터 있었습니다. 노출 기간이 7년을 넘는다는 겁니다. 그동안 이용되지 않은 이유는 메모리 배치를 원하는 모양으로 맞추려면 여러 결함을 이어 붙이고 대상마다 시험해야 해서 현실적이지 않다고 여겨졌기 때문입니다.

Wiz는 이 장벽을 최전선 AI 모델이 크게 낮췄다고 설명합니다. 자사 도구 Wiz Atlas에 Gemini 4 Argon, GPT-5.6 Sol을 함께 써서 발견, 공격 코드 개발, 환경별 조정, 패치 검증까지 했고, 여러 애플리케이션에서 동작하는 공격 코드를 몇 시간 단위로 만들었다고 합니다. 표현은 조심해서 읽어야 합니다. AI가 혼자 결함을 찾았다기보다 연구자들이 AI를 도구로 썼다는 쪽이 정확하고, 이번에는 Calif·Wiz·OpenAI가 서로 다른 시기에 같은 뿌리에 닿았습니다.

두 번째 시계는 사람의 속도입니다. 재단이 밝힌 숫자에 따르면 올해 7월 1일 이후 PHP 보안팀이 낸 CVE는 15건인데 그 사이 100건이 넘는 보고가 무효·중복·재현 불가로 걸러졌습니다. 올해 초 Alpha-Omega가 Anthropic의 Claude Mythos로 PHP를 훑었지만 재단은 판단과 패치는 모두 사람이 한다고 밝혔습니다. AI 덕에 보고서는 폭증했어도 하나하나 판단하고 고치는 쪽은 여전히 사람이라, 이번 사태의 병목은 발견이 아니라 판단하고 고쳐서 배포하는 쪽이었습니다. 재단은 5월에 Ecosystem Security Team을 만들었고 보안팀의 결정 방식을 합의에서 다수결로 바꾸겠다고 했습니다.

세 번째가 서버 운영자의 시계입니다. 수정 버전이 공개되는 순간 패치를 비교해 공격을 만드는 일이 빨라지므로 Sansec는 15분 안에 대규모 공격이 시작된다고 경고했습니다. 첫 보고에서 공개까지 10주를 쓰며 지켜 온 비밀은 마지막 이틀에 새어 나갔고, 공격은 공개 나흘 전부터 시작됐습니다. 결국 방어하는 쪽이 줄일 수 있는 구간은 하나뿐입니다. 수정이 나온 시각에서 내 서버가 올라가고 재시작되기까지의 간격이며, 그 간격이 몇 시간인지 몇 주인지는 내 서버가 어디서 어떤 방식으로 돌고 누가 맡는지를 얼마나 빨리 답할 수 있느냐에 달려 있습니다.

#PHP#PHP2Shell#CVE-2026-109618#CVE-2026-109619#원격 코드 실행