약 6분

PHP2Shell(CVE-2026-109618) 우리 서버 PHP가 패치됐는지 확인하기

이 글의 순서 6개 절
  1. 웹에서 도는 PHP 찾기
  2. 배포판 PHP는 버전 번호가 그대로다
  3. 올리고 나서 워커를 다시 띄운다
  4. 바로 못 올릴 때
  5. mod_php라면 인증서 키도 본다
  6. 참조

2026-10-21에 PHP 보안 릴리스가 나왔다. 로그인 없이 웹 요청만으로 서버에서 코드를 실행할 수 있는 취약점 두 건을 고쳤다. 둘 다 CVSS v4.0 9.2이고, 둘을 묶어 PHP2Shell이라고 부른다.

CVE-2026-109618은 메모리 부족(OOM) 처리 문제다. 메모리가 모자라 PHP가 중간에 끊기면 요청 데이터가 반쯤 초기화된 채로 남는다. 요청을 정리하는 단계가 그 데이터를 정상으로 알고 쓴다. CVE-2026-109619는 요청이 끝난 뒤에도 해제된 메모리를 가리키는 상태가 남는 문제다. 같은 워커가 다음 요청을 받으면 초기화 전에 그 상태를 쓴다.

고친 버전은 8.5.12, 8.4.27, 8.3.36, 8.2.35다. php.net은 웹 요청을 받는 PHP 전부를 위험 대상으로 봤다. PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed가 다 들어간다. 가장 위험한 쪽은 mod_php다. 인증 없는 요청 하나로 애플리케이션 코드가 돌기도 전에 공격이 일어나는 것이 시연됐다.

웹에서 도는 PHP 찾기

php -v가 보여 주는 것은 명령줄(CLI) PHP다. 웹 요청을 처리하는 PHP는 패키지도 버전도 다를 수 있다.

# mod_php: php_module 이 보이면 Apache 안에서 PHP가 돈다
apachectl -M 2>/dev/null | grep -i php

# PHP-FPM: 서비스 이름에 버전이 붙는다
systemctl list-units --type=service --all | grep -i fpm

RHEL 계열은 httpd -M이고 FPM 서비스 이름은 php-fpm이다.

배포판 PHP는 버전 번호가 그대로다

Ubuntu와 Debian은 보안 수정을 원래 버전에 덧대어 낸다. Ubuntu 24.04는 패치 전에도 후에도 php -v가 8.3.6이다. 바뀌는 것은 패키지 버전의 뒷부분이다. 2026-10-22 각 배포판 보안 추적 페이지 기준으로 고친 패키지는 이렇다.

배포판 소스 패키지 고친 패키지 버전
Ubuntu 26.04 LTS php8.5 8.5.4-0ubuntu1.5
Ubuntu 24.04 LTS php8.3 8.3.6-0ubuntu0.24.04.13
Ubuntu 22.04 LTS php8.1 8.1.2-1ubuntu2.28
Ubuntu 20.04 LTS php7.4 7.4.3-4ubuntu2.29+esm5 (Ubuntu Pro)
Debian 13 trixie php8.4 8.4.27-1~deb13u1
Debian 12 bookworm php8.2 8.2.35-1~deb12u1

설치된 패키지 버전은 dpkg -l로 보고, 수정 여부는 패키지 변경 기록에 CVE 번호가 있는지로 본다.

dpkg -l | grep -E '^ii +(libapache2-mod-php|php[0-9.]+-(fpm|cgi|common))'
zgrep -c CVE-2026-109618 /usr/share/doc/php8.3-common/changelog.Debian.gz

# RHEL 계열
rpm -q --changelog php-common | grep CVE-2026-109618

zgrep -c 결과가 0이면 아직 고치지 않은 패키지다. Docker 이미지처럼 /usr/share/doc을 비워 둔 환경에서는 apt changelog php8.3-common으로 본다.

PHP 프로젝트는 8.1 이하를 더 고치지 않는다. 그래도 Ubuntu 22.04의 php8.1처럼 배포판이 고쳐 내는 경우가 있다. 20.04의 php7.4는 Ubuntu Pro(ESM)로만 나온다. 소스로 빌드한다면 php.net이 7.4, 8.0, 8.1용 최소 백포트 브랜치를 따로 열었다.

Remi 저장소(rpms.remirepo.net)로 PHP를 깔았다면 php.net은 배포판 패키지나 소스 빌드로 옮기라고 했다. 공지 당일 그 저장소에는 업데이트가 없었다.

올리고 나서 워커를 다시 띄운다

디스크의 바이너리를 바꿔도 이미 떠 있는 워커는 옛 코드로 돈다. CVE-2026-109619는 같은 워커가 요청을 이어 받는 데서 생기는 문제라 더 그렇다.

sudo systemctl restart php8.3-fpm   # FPM
sudo systemctl restart apache2      # mod_php (RHEL: httpd)
systemctl status php8.3-fpm | grep Active

Active: 줄의 시각이 패키지를 올린 뒤인지 본다.

바로 못 올릴 때

php.net이 임시 조치를 적었지만 이것으로 설치가 안전해지지는 않는다고 못 박았다. 업데이트까지 시간을 버는 용도다.

CVE-2026-109618 쪽은 memory_limit=-1이 주된 조치다. 앱이 덮어쓰지 못하게 FPM 풀이나 Apache 설정에서 관리자 값으로 건다. 요청 하나가 서버 메모리를 다 쓸 수 있으니 메모리를 지켜보고, 패치 뒤에 원래 값으로 돌린다. post_max_size는 업로드가 없으면 8M, 있으면 32M로 낮추고 upload_max_filesize는 그보다 작게 둔다. RLIMIT_AS나 RLIMIT_DATA로 메모리를 묶으면 오히려 취약한 경로를 탄다.

CVE-2026-109619 쪽은 session.upload_progress.enabled=0이다. php.ini나 FPM 풀처럼 요청을 해석하기 전에 적용되는 곳에 넣는다. ini_set()이나 .user.ini로는 소용없다. 이 기능을 쓰지 않으면 패치 뒤에도 꺼 두라고 권고문에 적혀 있다. PHP 8.5라면 fatal_error_backtraces=0도 건다.

; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_flag[session.upload_progress.enabled] = off

OOM을 일으키는 경로로 꼽힌 것은 multipart/form-data 요청이다. 파일 업로드가 없는 사이트라면 앞단에서 이 형식을 막는 방법도 권고문에 있다.

# server 블록 안. 업로드 기능이 없는 사이트만
if ($http_content_type ~* "multipart/form-data") { return 415; }

설정을 바꾼 뒤에도 워커를 다시 띄운다.

mod_php라면 인증서 키도 본다

mod_php에서는 PHP가 Apache 워커 프로세스 안에서 돈다. 그 프로세스는 TLS 핸드셰이크를 하려고 인증서 개인키를 메모리에 들고 있다. CVE-2026-109619 권고문은 영향으로 메모리 노출과 원격 코드 실행을 함께 적었다. 패치 전에 공격을 받은 흔적이 있으면 키도 나갔다고 본다.

PHP-FPM은 PHP가 따로 떨어진 프로세스에서 돌고 TLS는 Nginx 같은 앞단이 맡는다. 이때는 웹 사용자 (www-data)가 키 파일을 읽을 수 있었는지가 갈림길이다. 확인 방법과 교체 순서는 서버가 뚫렸을 때 인증서 개인키 교체와 폐기에 적었다.

참조