[SECURITY] PHP2Shell — PHP 서버를 인증 없이 장악하는 치명적 RCE

CVE-2026-109618·109619의 공격 원리, 실제 위험 범위, 패치와 침해 점검까지

2026년 10월 22일 · lim.Chuck

[SECURITY]

  1. [SECURITY] PHP2Shell — PHP 서버를 인증 없이 장악하는 치명적 RCE

2026년 10월 21일, PHP 보안팀이 인증 없이 원격 코드 실행이 가능한 취약점 두 건을 긴급 공개했습니다. 이름은 PHP2Shell, CVSS v4.0 점수는 둘 다 9.2(Critical) 입니다. 결론부터 말하면 “PHP 사이트면 곧바로 root 권한을 빼앗긴다”는 설명은 과장이지만, 패치하지 않은 공개 서버에서 웹 서버 프로세스 권한으로 임의 코드를 실행할 수 있다는 점은 사실입니다.

PHP2Shell 대표 이미지. 금이 간 코끼리 안에서 쉘 프롬프트가 새어 나온다


무슨 일이 터졌나

PHP 보안팀은 정기 일정을 벗어난 긴급 보안 릴리스로 다음 두 취약점을 수정했습니다.

취약점 핵심 원인 결과
CVE-2026-109618 메모리 부족 처리 도중 내부 자료구조가 반쯤 만들어진 상태로 남음 메모리 손상, 비인증 RCE
CVE-2026-109619 이전 요청에서 해제된 메모리를 다음 요청이 다시 참조 정보 노출, Use-After-Free, 비인증 RCE

PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed 등 웹 요청을 처리하는 PHP 런타임이 영향 범위입니다. PHP로 작성된 애플리케이션 코드에 SQL Injection이나 파일 업로드 버그가 없어도, 그 아래의 PHP 엔진 자체가 취약합니다.

공식 발표 직전 상세 내용이 유출되었고, Cloudflare는 공개 당일 변형된 공격 트래픽을 관측했다고 밝혔습니다. watchTowr도 허니팟에서 악용 시도를 확인했지만 당시 성공 사례까지 확인한 것은 아니라고 구분했습니다. CISA는 자체 관측 기준으로 실제 악용을 확인하지 못했다고 공지했습니다. 세 발표는 모순이라기보다 관측망과 “시도/성공”의 기준이 서로 다른 것에 가깝습니다.


“PHP 기반이면 서버가 장악된다”는 말의 정확한 뜻

맞는 부분

  • 로그인이 필요 없는 HTTP 요청만으로 공격을 시작할 수 있습니다.
  • WordPress, Drupal, Joomla, Laravel처럼 애플리케이션 종류와 무관하게 PHP 런타임에서 문제가 생깁니다.
  • 공격에 성공하면 서버에서 명령 실행, 파일 읽기·변조, 악성코드 설치, 데이터 탈취가 가능해집니다.
  • 공개된 기술 분석과 PoC가 있어 패치 전 서버를 인터넷에 계속 노출하는 것은 위험합니다.

과장된 부분

  • PHP를 설치했다는 이유만으로 자동 감염되는 것은 아닙니다. 취약한 버전이 웹 요청을 처리하며 외부에 노출되어야 합니다.
  • 최초 실행 권한은 보통 www-data, apache, php-fpm 같은 웹 워커 계정 권한입니다. 즉시 root가 되는 취약점은 아닙니다.
  • 다만 웹 워커가 환경 변수의 DB 비밀번호, 애플리케이션 소스, 업로드 디렉터리, 클라우드 자격 증명을 읽을 수 있다면 피해는 이미 큽니다. 별도의 로컬 권한 상승 취약점이나 잘못된 sudo·컨테이너 설정까지 결합되면 호스트 전체 장악으로 이어질 수 있습니다.

정확한 표현은 다음과 같습니다.

취약한 PHP 웹 서버에 인증하지 않은 원격 공격자가 웹 워커 권한으로 코드를 실행할 수 있고, 서버 구성에 따라 전체 시스템 침해로 확대될 수 있다.


어떻게 뚫리는가

아래 내용은 방어와 이해를 위한 원리 설명입니다. 실제 서버를 공격할 수 있는 요청 페이로드나 익스플로잇 코드는 다루지 않습니다.

배경: PHP 워커는 요청을 여러 번 처리한다

PHP-FPM이나 mod_php의 워커 프로세스는 보통 요청 하나를 처리하고 종료되지 않습니다.

1
2
3
4
같은 PHP 워커
  ├─ 요청 A 처리 → 요청용 메모리 정리
  ├─ 요청 B 처리 → 요청용 메모리 정리
  └─ 요청 C 처리 → ...

요청별 데이터는 매번 정리되어야 하지만, 워커 자체의 전역 상태는 프로세스가 살아 있는 동안 유지됩니다. 이번 취약점은 비정상 종료 시 요청 메모리와 워커 전역 상태의 경계가 무너지는 문제입니다.

CVE-2026-109618: 반쯤 만든 자료구조를 정상 데이터로 착각

PHP는 요청을 처리하면서 배열과 객체 같은 내부 자료구조를 메모리에 만듭니다. 그런데 할당 중 memory_limit에 도달하면 기존 PHP는 zend_bailout()을 통해 현재 작업을 급하게 중단했습니다.

문제는 이 탈출이 너무 갑작스럽다는 점입니다.

1
2
3
4
5
6
1. HTTP 요청을 처리하며 내부 자료구조 생성 시작
2. 항목 개수 같은 메타데이터는 먼저 증가
3. 실제 key/value를 채우기 전에 메모리 부족 발생
4. longjmp 방식으로 실행 흐름을 즉시 탈출
5. 종료 정리 코드는 자료구조가 완성됐다고 믿음
6. 초기화되지 않은 메모리를 정상 값처럼 해석

공격자는 요청 크기와 메모리 배치를 조절해 PHP가 특정 시점에 메모리 부족을 일으키도록 유도합니다. 이후 정리 과정이 공격자가 영향을 준 메모리를 객체나 함수 포인터처럼 해석하게 만들면, 단순한 프로세스 충돌을 넘어 공격자가 원하는 코드 흐름으로 연결할 수 있습니다.

핵심은 “큰 요청으로 서버 메모리를 다 써서 죽인다”는 단순 DoS가 아닙니다. 메모리 부족이 발생한 바로 그 순간 PHP 내부 상태가 깨지고, 깨진 상태를 정리하는 코드가 다시 사용한다는 것이 RCE로 이어지는 지점입니다.

CVE-2026-109619: 이전 요청의 죽은 포인터를 다음 요청이 사용

두 번째 취약점은 요청 경계를 넘는 Use-After-Free입니다.

1
2
3
4
5
6
7
8
요청 A
  ├─ 워커 전역 상태가 요청 A의 메모리를 가리킴
  ├─ 치명적 오류로 정상 초기화·정리 순서가 깨짐
  └─ 요청 메모리는 해제됐지만 전역 포인터는 남음

요청 B — 같은 워커에 도착
  ├─ 정상 초기화 전에 남은 전역 상태에 접근
  └─ 이미 해제된 메모리를 사용 → 정보 노출 / RCE

같은 PHP 워커에서 요청 A가 해제한 메모리를 전역 상태가 계속 가리키고, 요청 B의 공격자 데이터가 그 자리를 차지하는 Use-After-Free 개념도

해제된 메모리 주소를 계속 들고 있는 포인터를 dangling pointer라고 합니다. 그 자리에 공격자가 조절할 수 있는 데이터가 다시 할당되면, PHP는 공격자 데이터가 원래 객체인 것처럼 동작할 수 있습니다.

이 공격은 연속된 요청이 같은 워커에 배정되어야 합니다. 워커 수가 많으면 한 번의 성공 확률은 낮아지지만, 반복 요청이 가능하므로 근본적인 방어가 되지는 않습니다.

확인된 경로에는 다음이 포함됩니다.

  • 세션 업로드 진행률 처리: 대상 PHP 스크립트가 실행되기 전에 동작하며 기본 활성화된 경우가 많음
  • 치명적 오류 backtrace 상태: PHP 8.5의 확인된 경로
  • 요청 초기화 전 발생하는 설정 경고와 종료 진단

그래서 애플리케이션의 인증 미들웨어나 WordPress 플러그인 보안만으로 막을 수 없습니다. 공격 트리거가 사용자 PHP 코드보다 먼저 실행될 수 있기 때문입니다.

공격 흐름을 한 장으로 정리하면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
조작된 비인증 HTTP 요청
          ↓
PHP의 초기 요청·업로드 처리
          ↓
의도한 시점에 OOM 또는 치명적 오류 유발
          ↓
┌──────────────────────┬─────────────────────────┐
│ 반쯤 초기화된 메모리 │ 해제 후 남은 전역 포인터 │
│ CVE-2026-109618      │ CVE-2026-109619          │
└──────────────────────┴─────────────────────────┘
          ↓
PHP가 공격자 영향권의 메모리를 정상 객체처럼 사용
          ↓
웹 워커 권한으로 원격 코드 실행

비인증 요청에서 OOM·치명적 오류를 거쳐 두 CVE로 갈라진 뒤 웹 워커 권한 코드 실행으로 이어지는 공격 흐름도


영향받는 버전

PHP 공식 릴리스 기준은 다음과 같습니다.

브랜치 취약한 버전 수정 버전
PHP 8.5 8.5.12 미만 8.5.12
PHP 8.4 8.4.27 미만 8.4.27
PHP 8.3 8.3.36 미만 8.3.36
PHP 8.2 8.2.35 미만 8.2.35
PHP 7.4~8.1 공식 지원 종료 배포판/LTS 백포트 확인 또는 지원 버전으로 이전
PHP 5.4~7.3 공식 지원 종료 지원 버전으로 이전

취약한 코드 경로는 PHP 5.4까지 거슬러 올라가는 것으로 분석됐습니다. PHP 보안팀은 사안의 심각성을 고려해 7.4, 8.0, 8.1용 최소 소스 백포트도 제공했지만, 이것이 해당 브랜치의 공식 지원 재개를 의미하지는 않습니다.

또 하나 주의할 점은 php -v의 숫자만 보고 판단하면 안 된다는 것입니다. Ubuntu, Debian, RHEL 같은 배포판은 버전 문자열을 크게 바꾸지 않고 보안 패치만 백포트하기도 합니다. OS 보안 공지와 패키지 changelog까지 확인해야 합니다.


지금 바로 확인할 것

1. 실제 웹 워커 버전 확인

1
2
3
4
5
6
7
8
9
10
11
php -v
php-fpm -v

# Debian / Ubuntu 계열
dpkg -l | grep -E 'php[0-9.]*-(fpm|cgi|apache2)|libapache2-mod-php'

# RHEL / Fedora 계열
rpm -qa | grep -E '^php|php-fpm'

# 컨테이너
docker ps --format ''

CLI와 웹 서버가 서로 다른 PHP 바이너리를 사용할 수 있습니다. php -v만 확인하지 말고 FPM 서비스, Apache 모듈, 컨테이너 이미지, 관리 패널과 NAS처럼 PHP를 내장한 제품도 함께 확인해야 합니다.

2. 수정 버전으로 업데이트

1
2
3
4
5
6
7
8
9
10
# Debian / Ubuntu 예시
sudo apt update
sudo apt upgrade

# RHEL / Fedora 예시
sudo dnf upgrade

# 업데이트 후 실제 사용하는 서비스 재시작
sudo systemctl restart php-fpm
sudo systemctl restart apache2  # 환경에 따라 httpd

Docker 이미지는 실행 중인 컨테이너에서 패키지만 수정하기보다, 수정된 베이스 이미지를 받아 이미지를 다시 빌드하고 컨테이너를 교체해야 합니다.

업데이트 후에는 다음 중 해당하는 수정 버전 이상인지, 또는 배포판이 CVE 백포트를 제공했는지 확인합니다.

1
8.5.12 / 8.4.27 / 8.3.36 / 8.2.35

3. 당장 패치할 수 없다면 외부 노출 차단

공식 입장은 명확합니다. 업데이트만이 완전한 해결책입니다. 임시 설정은 일부 알려진 공격 경로만 줄입니다.

  • 인터넷에서 해당 서비스를 격리하거나 접근 허용 IP를 제한
  • session.upload_progress.enabled=0 적용 후 모든 PHP 워커 재시작
  • PHP 8.5는 fatal_error_backtraces=0 적용 후 워커 재시작
  • 관리자 레벨에서 memory_limit=-1 적용
  • 프록시·웹 서버에서 body 크기, multipart part 수, header 수, 중첩 깊이 제한
  • 업로드가 필요 없다면 post_max_size=8M, 필요하다면 업무 범위에 맞춰 낮게 제한
  • 컨테이너/cgroup 메모리 경계로 영향 범위 격리
  • WAF 적용

memory_limit=-1은 PHP 내부의 공격 가능한 OOM 경로 하나를 줄이는 대신 프로세스가 시스템 메모리를 더 사용할 수 있게 합니다. 반드시 요청 크기 제한과 컨테이너 메모리 제한을 같이 두어야 합니다. RLIMIT_AS, RLIMIT_DATA는 오히려 취약한 할당 실패를 만들 수 있어 공식 공지가 대체 수단으로 권장하지 않습니다.

WAF 역시 보조 수단입니다. Cloudflare도 보호 규칙을 배포했지만 변형 공격을 관측했고, 패치가 필요하다고 강조했습니다.


이미 뚫렸는지 확인하는 방법

패치를 적용했다고 이전 침해 흔적이 사라지는 것은 아닙니다. 공개 서버였다면 다음을 함께 점검합니다.

로그와 프로세스

  • 공개 직전과 이후의 비정상적으로 큰 POST, multipart 업로드 요청
  • 짧은 시간에 반복된 500 응답, PHP fatal error, OOM, worker crash
  • 업로드·import·cleanup 경로에서 발생한 timeout과 비정상 종료
  • PHP/Apache/Nginx 워커가 sh, bash, curl, wget, python 같은 자식 프로세스를 실행한 흔적
  • PHP 워커에서 시작된 낯선 외부 연결과 DNS 요청

파일과 계정

  • 웹 루트, 업로드 디렉터리, /tmp에 최근 생성된 PHP·실행 파일
  • 알 수 없는 cron, systemd 서비스, SSH 키, 사용자 계정
  • WordPress 관리자·플러그인·테마의 예기치 않은 변경
  • 애플리케이션 비밀키, DB 비밀번호, 클라우드 토큰의 사용 기록

의심 흔적이 있으면 서버 안에서 파일 몇 개만 삭제하고 끝내지 않습니다. 네트워크에서 격리하고 증거를 보존한 뒤, 신뢰할 수 있는 이미지로 재구축하고 서버가 접근할 수 있었던 자격 증명을 전부 교체하는 편이 안전합니다.


WordPress도 플러그인 업데이트만 하면 되나

아닙니다. 이번 취약점은 WordPress 코어·테마·플러그인보다 아래인 PHP 런타임 문제입니다.

1
2
3
4
5
6
7
WordPress / Laravel / Drupal / Magento
                    ↓
                 PHP 엔진  ← 이번 취약점
                    ↓
          PHP-FPM / mod_php / CGI
                    ↓
               운영체제

따라서 WordPress 관리자 화면에서 “모든 플러그인이 최신”이라고 떠도 안전하다는 뜻이 아닙니다. 호스팅 업체가 PHP를 관리한다면 다음을 문의해야 합니다.

  1. 현재 웹 요청을 처리하는 PHP 버전은 무엇인가
  2. CVE-2026-109618과 CVE-2026-109619 패치 또는 백포트가 적용됐는가
  3. 적용 시각 이전의 공격·침해 로그를 조사했는가
  4. 컨테이너, 관리 도구, 어플라이언스에 내장된 PHP도 확인했는가

이번 패치는 무엇을 바꿨나

핵심 수정은 PHP가 OOM을 만났을 때 즉시 longjmp로 빠져나가 내부 상태를 방치하지 않도록 만든 것입니다.

  • OOM 시 즉시 bailout 하는 대신 안전한 지점까지 처리를 지연
  • 실패 가능한 메모리 할당 경로를 정상적으로 되감기
  • 요청 시작·종료 시 접근될 수 있는 전역 상태를 명시적으로 초기화
  • 업로드 진행률과 역직렬화 경로에서 사용자 코드가 예상치 못한 시점에 실행되지 않도록 보강

메모리 안전 언어가 아닌 C로 작성된 런타임에서는 “오류가 났으니 중단한다”만으로 충분하지 않습니다. 중단하는 순간과 정리 코드가 기대하는 불변 조건이 맞지 않으면 오류 처리 코드 자체가 공격 표면이 됩니다.


정리

이번 이슈는 “PHP 애플리케이션에 버그가 있다”가 아니라 PHP 런타임의 요청 처리와 오류 정리 과정이 깨진 사건입니다.

1
2
3
4
5
6
취약점: CVE-2026-109618, CVE-2026-109619
별칭:   PHP2Shell
등급:   CVSS v4.0 9.2 Critical
조건:   취약한 PHP가 웹 요청을 처리하며 외부에 노출
결과:   인증 없이 웹 워커 권한으로 원격 코드 실행 가능
조치:   8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 이상으로 즉시 업데이트

“바로 root 탈취”라고 단정할 수는 없지만, 웹 워커 권한만으로도 소스·DB 자격 증명·환경 변수·사용자 데이터에 접근할 가능성이 높습니다. 이미 공격 시도가 관측됐고 기술 정보도 공개된 상태이므로, 외부에 열린 PHP 서버라면 점검보다 패치를 먼저 하고 침해 조사를 이어가는 것이 맞습니다.


참고자료