인증 없이 서버를 장악할 수 있는 PHP 엔진 결함 PHP2Shell(CVE-2026-109618, 109619)의 원리와 고친 버전, 우리가 서버에서 확인한 순서를 정리했습니다. AI가 찾은 취약점 앞에서 개발자의 자리도 생각해 봤습니다.
Ian5 min read17
엔진 자체의 결함
10월 21일, PHP가 치명적인 취약점 두 개(CVE-2026-109618, CVE-2026-109619)를 고친 버전을 내놨습니다. PHP2Shell이라는 이름이 붙은 이 취약점은 인증 없이 원격에서 서버의 명령을 실행할 수 있습니다. 두 취약점 모두 CVSS v4.0 기준 9.2점입니다.
이번 취약점이 특히 위험한 이유는 결함이 애플리케이션이 아니라 PHP 엔진 안에 있다는 점입니다. PHP는 요청 본문을 파싱해 $_POST를 채운 뒤에야 우리 코드를 실행합니다. 공격은 그보다 앞 단계에서 일어납니다. 그래서 로그인 검사도, 입력값 검증도 소용이 없습니다. Wiz는 "Hello World"만 출력하는 PHP 앱도 취약할 수 있다고 설명합니다. WordPress, Drupal, Joomla의 최신 컨테이너 이미지에서도 원격 코드 실행을 확인했다고 밝혔습니다.
메모리가 바닥나는 순간
두 취약점의 뿌리는 같습니다. PHP는 요청을 처리하다가 memory_limit를 넘기면, 하던 일을 그 자리에서 멈추고 오류 처리 지점으로 건너뜁니다(bailout). 문제는 "하던 일"이 반쯤 끝난 상태로 남는다는 것입니다.
CVE-2026-109618이 그런 경우입니다. Datadog의 분석에 따르면, PHP는 폼 필드를 해시 테이블에 넣을 때 항목 개수부터 늘리고 키를 저장할 메모리는 그다음에 할당합니다. 공격자가 아주 긴 필드 이름을 보내 이 할당을 실패하게 만들면, 개수는 늘었는데 내용은 비어 있는 항목이 남습니다. 이어지는 정리 코드는 이 항목을 정상 데이터로 믿고 처리합니다. 공격자는 이 틈으로 메모리를 원하는 대로 바꿀 수 있습니다.
다음 요청에 남는 흔적
CVE-2026-109619는 워커가 요청을 여러 번 처리한다는 점을 노립니다. PHP-FPM이나 Apache의 mod_php에서는 프로세스 하나가 요청을 차례로 받습니다. 이때 워커 전체가 쓰는 상태 일부가 "이번 요청의 메모리"를 가리키는 경우가 있습니다. 요청이 비정상적으로 끝나 메모리는 해제됐는데 가리키는 포인터는 그대로 남으면, 다음 요청이 이미 해제된 메모리를 씁니다(use-after-free).
가장 현실적인 경로는 세션 업로드 진행률 기능입니다. 이 기능은 기본으로 켜져 있고, 앱이 session_start()를 부르지 않아도 동작합니다. Wiz는 이 경로로 해제된 메모리 내용을 PHPSESSID 쿠키에 담아 빼내는 방법을 공개했습니다. 다른 사용자의 요청 데이터가 섞여 나올 수 있고, 메모리 주소를 알아내면 첫 번째 취약점의 공격 성공률도 올라갑니다. 두 취약점을 엮으면 명령 실행까지 이어집니다.
고친 버전
브랜치
고친 버전
8.5
8.5.12
8.4
8.4.27
8.3
8.3.36
8.2
8.2.35
정식 지원이 끝난 7.4~8.1은 PHP 팀이 최소한의 소스 패치만 공개했습니다. 영향 범위는 PHP 5.4까지 거슬러 올라가는 것으로 알려져 있습니다. 당장 업데이트할 수 없다면 session.upload_progress.enabled = 0 같은 임시 완화책이 있습니다. 하지만 Datadog도 Imperva도 이 방법으로는 완전히 막을 수 없고, 패치만이 해결책이라고 말합니다.
우리 서버에서 한 일
플린랩스는 Node.js 위주로 개발하지만, 운영 중인 사이트들 가운데에는 PHP로 돌아가는 사이트들도 있습니다. 공개 직후 서버를 하나씩 확인했습니다.
PHP가 어디에 있는지: 엣지 서버에는 PHP가 없었습니다. 홈서버에는 호스트에 설치된 php8.3-fpm이 있었고, nginx 설정을 따라가 보니 PHP 사이트들이 이 FPM으로 요청을 넘기고 있었습니다. 컨테이너 안에는 PHP가 없었습니다.
패치가 들어갔는지: Ubuntu 자동 보안 업데이트가 10월 22일 새벽에 8.3.6-0ubuntu0.24.04.13 패키지를 설치해 두었습니다. 패키지 changelog에 CVE-2026-109618_109619.patch가 들어 있는 것을 확인했습니다.
패치된 코드로 돌고 있는지: 패키지만 바뀌고 프로세스가 예전 바이너리로 돌고 있으면 소용이 없습니다. FPM 마스터 프로세스의 시작 시각이 패키지 설치 16초 뒤인 것을 확인했습니다.
이미 뚫리지는 않았는지: 공격 시도가 공개 전부터 있었다는 보고가 있어 로그를 거슬러 봤습니다. FPM 로그에 워커 크래시(signal)가 없었고, nginx 로그에 메모리 초과 오류나 upstream 비정상 종료도 없었습니다. 웹 루트에서 최근에 바뀐 PHP 파일은 0개였고, 웹 서버 계정이 띄운 셸 프로세스도 없었습니다.
버전 번호는 그대로
점검하면서 헷갈리기 쉬운 부분이 하나 있었습니다. 패치된 서버의 php -v는 여전히 8.3.6입니다. 고친 버전인 8.3.36이 아닙니다. Ubuntu 같은 배포판은 버전을 올리지 않고 보안 수정만 기존 버전에 옮겨 넣습니다(백포트). 버전 번호만 보면 취약하다고 오판하고, 반대로 컨테이너 안의 PHP처럼 배포판 패치가 닿지 않는 곳은 놓치기 쉽습니다.
bash
# 버전 번호가 아니라 패키지 changelog로 확인zcat /usr/share/doc/php8.3-fpm/changelog.Debian.gz | grep -m2 CVE-2026-10961
AI가 찾은 취약점
이번 취약점에서 기술만큼 화제가 된 것은 발견 과정입니다. Fortune 보도에 따르면 AI 레드팀 회사 Calif가 8월 1일 OpenAI의 GPT-5.6 Sol로 첫 단서를 찾았습니다. Wiz도 8월 19일 Gemini 4 Argon 등을 써서 독립적으로 같은 결함을 찾았습니다. 문제의 코드는 10년 넘게 그 자리에 있었습니다. Wiz는 "AI의 변곡점"이라고 표현했고, 복잡한 공격 기법이 더는 높은 장벽이 되지 못할 수 있다고 썼습니다.
공격자 쪽도 빨랐습니다. Cloudflare CEO에 따르면 소식이 퍼지고 45분 만에 공격 시도가 수만 건, 그날 저녁에는 수백만 건으로 늘었습니다. OpenAI도 같은 능력이 공격 도구를 빠르게 만드는 데 쓰일 수 있다고 경고했습니다.
개발자의 자리
솔직히 이런 소식을 볼 때마다 마음이 복잡합니다. 10년 넘게 수많은 사람이 지나친 결함을 AI가 찾아냅니다. 공격 코드도 AI가 다듬습니다. 보안 취약점이 매일 새로 발표되고, 그 속도는 사람이 따라 읽기도 버거울 만큼 빨라졌습니다. 코드를 짜고 버그를 찾는 일이 개발자의 일이라면, 그 자리가 조금씩 줄어드는 느낌을 지우기 어렵습니다.
그런데 이번 대응을 돌아보면, AI가 대신해 주지 못한 일도 분명히 있었습니다. 우리 서버 어디에 PHP가 돌고 있는지는 우리만 알았습니다. 처음 검색에서는 nginx 설정이 심볼릭 링크라서 PHP를 쓰는 사이트를 놓칠 뻔했습니다. "패키지가 바뀌었다"와 "패치된 코드로 돌고 있다"가 다르다는 것을 알고 프로세스 시작 시각까지 확인하는 것도 사람의 판단이었습니다. 그리고 결과에 책임을 지는 것도 결국 사람입니다.
우리는 개발자의 일이 사라진다기보다 무게중심이 옮겨 가고 있다고 봅니다. 코드를 직접 쓰는 손보다, 무엇이 어디서 돌고 있는지 아는 눈과 확인할 것을 정하는 판단이 더 중요해지고 있습니다. 그 판단을 AI에게 맡길 수 있는 날이 올지도 모릅니다. 다만 그날이 오기 전까지는, 기본을 지키는 팀이 살아남는다고 생각합니다.
마치며
PHP2Shell은 애플리케이션 코드로는 막을 수 없는 엔진의 결함입니다. PHP를 쓴다면 해야 할 일은 하나, 패치입니다. 그리고 패치했다고 믿는 대신 확인해야 합니다. 패키지 changelog, 프로세스 시작 시각, 그리고 컨테이너 안에 숨은 PHP까지요.
취약점을 찾는 속도는 AI 덕분에 계속 빨라질 것입니다. 그만큼 패치를 자동으로 받는 구조와, 내 서버에 무엇이 있는지 아는 습관이 더 중요해졌습니다.