OpenAI의 php2shell-checker는 1차 점검에 유용하지만, 서버가 안전하다는 사실을 증명해 주는 도구는 아닙니다. HTTP 헤더나 php -v로 확인한 PHP 버전을 내장된 패치 기준선과 비교하는 방식이기 때문입니다. 따라서 버전 헤더가 숨겨져 있으면 UNKNOWN이 나올 수 있고, 배포판이 보안 패치를 백포트했다면 실제로 수정된 서버가 취약한 것으로 표시될 수도 있습니다.
먼저 결과 상태부터 확인하자
OpenAI 공식 저장소는 의미 있는 상태를 세 가지로 구분합니다. 다만 이 결과는 전체 취약점 진단이 아니라 초기 분류를 위한 신호로 보는 편이 안전합니다.
| 출력 | 종료 코드 | 의미 | 다음 조치 |
|---|---|---|---|
OK | 0 | 인식된 브랜치가 최소 수정 버전 이상임 | 웹 트래픽을 처리하는 PHP 프로세스도 같은 버전을 사용하는지 확인 |
VULNERABLE | 1 | 감지된 버전이 최소 수정 버전보다 낮음 | 서비스 중인 런타임을 패치하고 워커를 재시작한 뒤 두 검사를 다시 실행 |
UNKNOWN | 3 | 인식 가능한 버전이나 응답을 확인하지 못함 | 서버에서 직접 확인하고, 패치가 완료됐다고 간주하지 않기 |
| 잘못된 인수 | 2 | 명령어 형식 자체가 올바르지 않음 | 명령어를 수정해 다시 실행 |
OpenAI가 공개한 구성
공개된 php2shell-checker 저장소에는 Node.js 스크립트와 테스트, 그리고 두 가지 Codex 스킬이 들어 있습니다. 외부에서 접속 가능한 사이트에는 phpcheck-url, 직접 접근할 수 있는 서버에는 phpcheck-server를 사용합니다. 이 도구는 버전 정보를 수집할 뿐, 대상을 익스플로잇하거나 애플리케이션 코드를 스캔하지는 않습니다.
환경에 맞는 방식으로 검사하기
공개 URL 확인
저장소를 클론하거나 내려받은 다음 Node.js 스크립트에 사이트 주소를 전달합니다.
node phpcheck.js "https://example.com"
원격 검사는 먼저 HTTP HEAD 요청을 보냅니다. 서버가 405 또는 501을 반환할 때만 GET으로 전환합니다. 응답의 X-Powered-By와 Server 헤더를 읽고, 리디렉션은 따라가지 않으며 응답 본문도 확인하지 않습니다.
CDN이나 리버스 프록시, expose_php=off, 사용자 정의 서버 설정 때문에 응답이 검사기에 도달하기 전에 PHP 버전이 제거될 수 있습니다. VULNERABLE이 나오면 패치가 필요하지만, 헤더가 없다는 사실만으로는 판단할 수 없으므로 서버 내부의 증거를 확인해야 합니다.
로컬 PHP 바이너리 확인
PHP가 설치된 머신에서는 다음 명령어를 사용합니다.
node phpcheck.js --local
이 명령은 현재 PATH에서 찾은 바이너리에 대해 php -v를 실행합니다. 그렇다고 이 바이너리가 웹사이트를 서비스하는 PHP라는 뜻은 아닙니다. 한 호스트에 CLI, PHP-FPM, CGI, Apache 모듈, 컨테이너, 소스 빌드 설치가 각각 따로 존재할 수 있습니다.
로컬 검사가 끝나면 웹 서버와 연결된 실제 런타임을 확인해야 합니다. 컨테이너 환경이라면 이미지 태그만 보지 말고 실행 중인 컨테이너를 점검하세요. 패키지 기반 서비스라면 해당 PHP-FPM 풀이나 Apache 모듈, 그리고 공급업체 패키지 변경 로그까지 확인하는 것이 좋습니다.
Codex 스킬 설치 및 사용
공식 README에서는 하나의 Codex 명령어로 두 스킬을 모두 설치하도록 안내합니다.
$skill-installer install both skills from https://github.com/openai/php2shell-checker
이후에는 현재 접근 방식에 맞는 인터페이스를 선택하면 됩니다.
$phpcheck-url check https://example.com
$phpcheck-server check the PHP version serving my site
Codex는 필수가 아닙니다. CI 작업이나 최소한의 호스트 환경에서는 Node.js 명령어만으로도 직접 검사할 수 있습니다. 자동화하려면 --json을 추가하세요. 그러면 스크립트가 터미널 문장을 파싱하지 않아도 patch_status 필드에서 ok, vulnerable, unknown 중 하나를 받을 수 있습니다.
다운로드한 검사기 검증
이 저장소는 Node에 내장된 테스트 러너를 사용합니다.
node --test phpcheck.test.js
테스트 모음은 로컬 HTTP 서버를 대상으로 CLI를 실행하고, 로컬 시나리오에서는 가짜 PHP 실행 파일을 사용합니다. README에 따르면 실제 PHP 설치나 외부 웹사이트 접속은 필요하지 않습니다.
UNKNOWN은 디버깅 결과로 받아들여야 한다
UNKNOWN은 정보가 충분하지 않다는 뜻으로 해석하세요. 대상이 PHP 버전을 숨겼거나, 검사기가 인식하지 못하는 브랜치를 사용하거나, 응답에 실패했거나, 더 최신의 프리릴리스를 실행 중일 수 있습니다. 어느 경우도 패치가 적용됐다는 근거가 되지는 않습니다.
실제 사용자 @jvr0x는 이 한계를 다음과 같이 요약했습니다.
“이 검사는 10월 20일 기준선과 버전을 비교할 뿐이라, 헤더가 숨겨져 있으면 UNKNOWN이 나옵니다.”
원격 명령어가 확인하는 것은 리버스 프록시 뒤에서 동작하는 프로세스가 아니라 HTTP 응답입니다. 버전 헤더가 숨겨져 있다면 URL을 계속 다시 조회할 것이 아니라 서버에서 직접 확인해야 합니다.
UNKNOWN이 나온 뒤에는 다음 순서로 점검하세요.
- 사이트를 서비스하는 호스트에서
node phpcheck.js --local을 실행합니다. - 요청이 PHP-FPM, CGI, Apache
mod_php, 컨테이너 중 어디로 전달되는지 확인합니다. - 공급업체 패키지의 변경 로그에서 패치가 백포트됐는지 확인합니다.
- 업데이트 후 관련 PHP-FPM 또는 Apache 워커를 재시작합니다.
- 셸의 기본
php바이너리가 아니라 실제 웹 요청을 처리하는 경로를 통해 검증합니다.
SSH 접근 권한이 없다면 URL 모드가 가능한 첫 번째 점검 방법의 전부일 수 있습니다. 공유 호스팅에서 버전 헤더를 숨기고 있다면 호스팅 업체에 실제 서비스 런타임, 패키지 리비전, 재시작 상태를 확인해 달라고 요청해야 합니다.
버전 표만으로 패치 여부를 결정할 수 없는 이유
검사기는 PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35를 각 브랜치의 최소 수정 업스트림 릴리스로 취급합니다. 공식 테스트에서도 8.4.26은 VULNERABLE, 8.4.27은 OK로 확인됩니다. PHP 역시 8.5.12, 8.4.27, 8.3.36, 8.2.35를 보안 릴리스로 분류합니다.
| 브랜치 | 최소 수정 업스트림 릴리스 | 취약한 것으로 분류되는 이전 릴리스 |
|---|---|---|
| PHP 8.5 | 8.5.12 | 8.5.11 |
| PHP 8.4 | 8.4.27 | 8.4.26 |
| PHP 8.3 | 8.3.36 | 8.3.35 |
| PHP 8.2 | 8.2.35 | 8.2.34 |
| 배포 상황 | 단순 버전 비교가 오해를 부를 수 있는 이유 | 확인할 근거 |
|---|---|---|
X-Powered-By 또는 Server 헤더 숨김 | 원격 검사기가 PHP를 식별할 수 없음 | 로컬 런타임 정보 |
| Debian 또는 Ubuntu 보안 패키지 | 공급업체가 업스트림 버전 문자열을 그대로 둔 채 패치를 백포트할 수 있음 | 공급업체 보안 권고 및 패키지 변경 로그 |
| 한 호스트에서 PHP-FPM과 CLI를 함께 사용 | php -v가 HTTP 워커가 아닌 CLI를 설명할 수 있음 | 웹 서버 및 FPM 설정 |
| 캐시된 컨테이너 또는 고정된 다이제스트 | 레지스트리 태그는 최신이어도 실행 중인 컨테이너는 이전 버전일 수 있음 | 실행 중인 컨테이너의 이미지 ID와 버전 |
| Apache 또는 FPM 워커를 재시작하지 않음 | 디스크의 파일은 수정됐지만 이전 워커가 계속 실행 중일 수 있음 | 프로세스 시작 시간 및 재시작 후 검사 결과 |
| 지원되지 않는 브랜치 또는 프리릴리스 | 검사기가 공급업체별 패치 상태를 인식하지 못할 수 있음 | PHP 또는 공급업체 보안 공지 |
저장소의 제한 사항에도 나와 있듯이, 이 도구는 목록에 포함된 브랜치에서 이후에 출시된 안정 버전에 수정 사항이 포함돼 있다고 가정하며 실제 수정 여부를 독립적으로 검증하지는 못합니다. 배포판은 업스트림 버전 번호를 바꾸지 않고 보안 패치를 적용할 수 있으므로, 화면에 표시된 버전 하나보다 변경 로그가 더 강력한 근거가 될 수 있습니다.
WordPress, Drupal, Laravel 같은 PHP 애플리케이션을 업데이트한다고 PHP 엔진까지 패치되는 것은 아닙니다. 런타임 패키지나 이미지를 업데이트하고 워커를 재시작한 다음, 웹 요청을 실제로 처리하는 프로세스를 확인해야 합니다.
FAQ
어떤 결과가 나오면 운영 환경의 조치 완료 티켓을 닫아도 되나요?
어떤 결과도 단독으로 티켓을 닫을 근거가 되지는 않습니다. OK가 나왔더라도 서비스를 제공하는 SAPI, 공급업체의 백포트 적용 여부, 관련된 모든 vhost 또는 컨테이너, 패치 후 워커 재시작 여부를 함께 확인해야 합니다.