2026년 10월 21일 PHP의 원격 코드 실행 취약점 PHP2Shell이 공개되자, 한 B2B 신용정보 업체는 Drupal 사이트를 중단하고 Alera 기반으로 전환했다. 이미 프록시 뒤에서 진행 중이던 마이그레이션을 활용해 가장 많이 쓰이는 계정 보고서를 먼저 교체했고, 첫 지시 후 2시간 4분 만에 운영 환경의 PHP를 중단했다.
취약점은 CVE-2026-109618과 CVE-2026-109619 두 건이며 CVSS 점수는 최고 9.2다. Wiz는 인증 없이 Drupal에서 원격 코드 실행을 시연했고, PHP 5.4까지 모든 버전이 영향을 받는다고 밝혔다. 취약점은 Drupal이 아니라 PHP 엔진에 있어 Drupal 업데이트로 해결할 수 없었다. PHP는 당일 패치 버전을 배포했다.
이 업체는 회원 사이트에 Drupal 7을, 신용 신청 양식에 Drupal 8을 사용했다. 두 사이트를 안전하게 패치할 시간이 없다고 판단한 커티스(Kurtis)는 Drupal을 끄기로 했다. 페이지별로 Alera로 옮기던 중이었고, 프록시가 일부 경로를 Alera로 보내고 나머지를 Drupal로 전달하고 있었다. 당시 Drupal에 남아 있던 기능은 가장 많이 쓰이는 계정 보고서와 수십 가지 작은 기능, 별도의 신용 신청 사이트였다.
Alera에서 작업한 에이전트는 Drupal 기능 대부분을 대체했다. Drupal 종료의 전제였던 계정 보고서는 그날 오후 새로 만들었다. 커티스의 첫 메시지 후 2시간 4분, Wiz의 공개 후 약 8시간 만에 운영 환경에서 PHP를 중단했다. 나머지 기능도 밤사이 옮겼다. 10월 22일 13:24 UTC에는 신용 신청 양식 82개가 모두 검사를 통과했고, 이후 전부 운영 환경에 배포됐다. Drupal은 완전히 꺼졌다.
일부 기능은 의도적으로 옮기지 않았다. 업체가 폐기하기로 한 기능과, 두 달 동안 필요하지 않고 전면 개편이 예정된 모임 기능(모임 신청과 편집기)이다. 작업에는 Claude Opus 5.5와 Claude Sonnet 5.5를 사용한 에이전트 실행 767회가 들었다. 정가로 계산한 토큰 비용은 약 1,000달러였으며, 구독으로 실제 기록된 지출은 0달러였다.
전환 일정과 방식
모든 시각은 UTC 기준이며, 팀은 미국 중부 시간(UTC−5)으로 작업했다. Wiz는 10월 21일 13:59에 PHP2Shell을 공개했고 PHP는 같은 날 패치 버전을 배포했다. 계정 보고서 재구축 계획은 취약점 공개 전에 이미 시작돼 있었지만 코드는 아직 없었다. 19:55에 계획 담당 에이전트가 작업을 시작했고, 20:05 커티스는 계정 보고서가 Drupal을 대체할 수 있는 즉시 Drupal을 끄라고 결정했다. 12초 뒤 데이터베이스 에이전트가 회원들이 실제로 사용하는 Drupal 기능을 측정하기 시작했으며, 결과는 20:11에 작성됐다.
새 계정 보고서의 첫 커밋은 20:49에 나왔다. 커티스는 21:04에 전환 계획을 승인했다. 21:22에는 Drupal 8 신용 신청 서비스를 0개로 축소하고 해당 호스트 이름에 ‘곧 돌아옵니다’ 페이지를 연결했다. 21:23부터 21:30까지 운영·개발·QA·스테이징의 모든 Drupal 원본 서버는 로드밸런서와 방화벽에서 프록시 주소 네 개만 허용하도록 제한했고, 에이전트가 외부에서 이를 확인했다.
계정 보고서 첫 배포는 21:27에 커밋됐지만 아직 경로에 연결되지는 않았다. 이어 Drupal 없이 로그인·로그아웃·직원 대리 로그인을 처리하는 전환 도구와 임시 안내 페이지, 프록시 변경이 준비됐다. 22:03:56 전환 배포로 프록시의 Drupal 기본 경로가 제거됐다. 아직 재구축되지 않은 경로는 모두 HTTP 503 임시 안내 페이지로 향했다. 22:05부터 22:09까지 실제 테스트 계정으로 로그인, 강제 비밀번호 변경, 대시보드, 검색, 계정 보고서, 로그아웃, 대리 로그인, 연결된 앱 두 곳 로그인과 Drupal로 전달되는 요청이 없는지를 점검했다. 22:09:48에는 운영 및 개발 환경의 Drupal 7 PHP를 0개로 축소했다. 이는 커티스의 결정으로부터 2시간 4분 뒤다.
22:19부터 22:22 사이 QA와 마지막 개발 PHP 서비스도 중단했다. 파이프라인이 PHP를 다시 배포하지 않는지 30분마다 확인하는 감시 작업도 시작했다. 다만 22:24:17부터 22:36:50까지 공유 루틴을 변경한 뒤 로그인과 검색이 중단됐다. 23:59에는 계정 보고서에 플래시 입력과 항목별 메모를 추가했다. 10월 22일 01:33에는 제3자 신용 보고서 주문, 02:17에는 협회 페이지를 배포했다. 02:30에는 서비스를 중지하기 전에 먼저 새 버전을 빌드하도록 배포 방식을 바꿨다. 에이전트 측정에 따르면 운영 환경의 502 오류 발생 구간은 약 66초에서 3.8초로 줄었다. 02:40부터 03:38까지 모임 책자, 결제 경험 입력, 계정 이름 요청과 상시 목록, 플래시 알림 페이지를 배포했다. 13:24에는 프로젝트 관리자가 신용 신청 양식 82개 전부의 검사 통과를 확인했다.
같은 날 종료를 가능하게 한 조건은 세 가지였다. 먼저 마이그레이션이 이미 진행 중이어서 대시보드·검색·로그인이 Alera에 있었고, 운영 트래픽도 기존 프록시를 거쳤다. 따라서 DNS를 옮기지 않고 프록시의 기본 경로만 바꾸면 Drupal을 차단할 수 있었다. 둘째, 데이터는 Drupal에 저장되지 않았다. 데이터베이스는 그대로 두고 PHP 애플리케이션 서버만 중단하면 됐다. 셋째, 사용량 자료가 있었다. Drupal 자체 접근 로그는 꺼져 있었지만 업무 데이터베이스에 기능별 방문 기록이 수년간 남아 있었다. 최근 90일 동안 계정 보고서는 사용자 735명이 42,014회 이용했고, 대시보드와 검색은 이미 옮겨져 있었다. 사용자 지정 Drupal 경로 157개 가운데 96개는 기록된 사용량이 없었다. 이 자료를 바탕으로 전체를 한꺼번에 다시 만드는 대신, 먼저 전환의 전제인 페이지 하나를 교체하고 나머지는 사용량 순서에 따라 옮겼다.
프로젝트 구성과 검증
작업은 고객사의 Alera 에이전트 스웜에서 네 프로젝트로 진행됐다. 각 프로젝트에는 간단한 결정 문서가 있었고, 조정 에이전트가 개발 에이전트에게 업무를 나눴다. 이들 위에는 프로젝트 관리 에이전트가 있었다.
- 계정 보고서 프로젝트는 기존 URL과 기밀성 규칙을 유지한 회원 계정 보고서를 재구축했다. 10월 21일 22시 03분 UTC부터 운영됐고, 메모·플래시 입력·제3자 보고서 주문 기능은 밤사이 추가됐다.
- Drupal 종료 프로젝트는 모든 PHP 원본 서버를 제한하고, 프록시에서 Drupal을 제거하고, PHP를 중단한 뒤 트래픽에 따라 기능을 복원하는 일이었다. Drupal은 모든 환경에서 꺼졌고 기능 대부분이 교체됐다. 감시 작업은 계속 실행됐으며 프록시 폐기는 다음 주로 계획됐다.
- 협회 페이지 프로젝트는 협회 홈페이지, 문서 보관소, 명단, 업로드, 모임 책자, 경험 입력, 요청, 상시 목록을 맡았다. 10월 22일 03:02 UTC까지 운영됐다.
- 신용 신청 프로젝트는 Drupal 8 사이트와 공급업체 양식 82개를 대체했다. 양식은 제작·검사 후 모두 운영됐다.
계정 보고서 재구축은 긴급 상황이 되기 10분 전, 일반적인 마이그레이션 작업으로 시작됐다. 취약점 공개로 이 페이지의 배포가 Drupal 종료의 조건이 됐다. 별도 계획 담당 에이전트가 커티스의 취약점 지시를 받아 종료 계획을 작성했다. 계획은 원본 서버를 즉시 제한하고, 계정 보고서 작업과 병행해 전환 도구를 만들고, 프록시를 전환하고, 이후 며칠 동안 기능을 복구하며, 다음 주 프록시를 폐기하는 다섯 단계였다. 프로젝트 관리자는 계정 보고서와 종료 작업을 병렬로 진행하되 두 프로젝트가 공유하는 경로에서 충돌하지 않도록 조정했다.
전환 도구에는 겉으로 드러나지 않던 작업이 많았다. 로그인, 로그아웃, 로그인 사용자의 확인, 직원 대리 로그인은 모두 Drupal 세션에 의존했다. 이를 Alera 자체 토큰으로 옮겼다. 검색 엔진에 임시 페이지가 색인되지 않도록 HTTP 503으로 응답하게 했고, 이전 공개 URL의 리디렉션도 추가했다. 관련 없는 이유로 개발 환경에서 최신 코드를 빌드할 수 없어 운영 데이터베이스와 프록시 경로를 공유하는 스테이징에서 도구를 검증했다.
신용 신청 대체 작업은 커티스의 설계를 바탕으로 그날 저녁부터 다음 날 아침까지 진행됐다. 공급업체별 양식은 버전 관리 저장소의 자유 형식 마크다운 명세로 바뀌었다. 빌더 에이전트는 명세와 변경 내역으로 재판매 증명서, 소유자, 추천인, 주소, 서명, 파일 업로드 등 공통 영역으로 구성된 웹 컴포넌트를 만들었다. 직원이 미리 본 뒤 게시하는 방식이다. 에이전트는 기존 양식 82개의 정의를 명세로 변환했다. 계약 조건 블록 707개를 기존 양식과 비교해 모두 일치하는지 확인하고, 전체 양식을 빌드한 뒤 실제 백엔드를 대상으로 시험 제출을 했다.
원본 서버 차단은 외부에서 검사했다. Drupal 원본 서버에 직접 요청하면 모두 403을 반환했고, 전환 전에는 프록시를 통한 기존 페이지가 계속 동작했다. 전환 후 프록시 로그에는 Drupal로 전달된 요청이 없었다. 운영 스모크 테스트에는 실제 로그인을 사용했다. 전용 테스트 계정의 비밀번호를 임시로 바꿨다가 복구한 뒤 바이트 단위로 확인했다. 이 계정으로 로그인, 강제 비밀번호 변경, 대시보드와 내보내기, 검색, 계정 보고서, 로그아웃, 양식 기반 대리 로그인, 연결된 앱 두 곳의 로그인을 시험했다. 이전 경로가 임시 안내 페이지로 연결되는지도 확인했다. 22:24의 장애 이후에는 페이지가 열리는지만 보는 대신 검색 결과까지 검사하도록 스모크 테스트를 바꿨다.
계정 보고서의 수치는 기존 데이터와 맞았다. 개발 에이전트가 기존 실시간 API의 추세 타일과 비교한 결과, 테스트 사용자 계정에서 계정 35개와 공유 권한이 서로 다른 테스트 사용자 5명을 대상으로 한 191건의 비교에서 불일치는 없었다. 유럽 계정의 기밀성 규칙과 회원별 공유 권한도 첫 배포부터 유지됐다. 신용 양식은 실제 백엔드의 시험 제출 경로로 확인했다. 최종 점검에서 82개 양식 전체에 161건을 시험 제출했고 거부된 건은 없었다. 검사 과정에서 실제 신청을 막을 수 있는 버그 두 건을 찾아 양식이 준비됐다고 알리기 전에 수정했다. 테스트 데이터는 지정된 테스트 고객과 테스트 메일함에 기록한 뒤 삭제하고 삭제 여부를 다시 확인했다. 다만 실제 받은 편지함을 사용하지 않아 비밀번호 찾기 이메일은 검증하지 않았다.
사람의 판단과 장애
커티스는 20:05부터 03:16까지 다섯 개 상위 에이전트 대화에 약 34건의 메시지를 보냈다. 확인 메모에 설명된 산정 방식에 따르면 메시지에는 코드가 아니라 범위와 우선순위에 관한 결정이 담겼다. 그는 계정 보고서의 핵심 기능만 먼저 옮기고 PDF, 메모 페이지, 유료 보고서 주문은 첫 배포에서 제외했다. 전환 계획의 일곱 항목에 답하며 아직 만들지 않은 페이지로 가는 헤더 링크는 임시 안내 페이지로 보내라고 승인했다.
전환 약 10분 뒤 운영 페이지를 살펴보고 여섯 가지 회귀 문제를 지적했다. ‘All Members’와 ‘My Experience’ 위젯은 기존보다 눈에 띄게 나빠졌다는 지적도 포함됐다. 문제는 한 시간 안에 수정돼 운영에 반영됐다. 이후 기존 페이지를 시각적 기준으로 삼는 규칙도 세웠다. 다음 복원 대상으로 트래픽 순위의 다음 기능 대신 협회 페이지를 선택하며 “그 페이지들이 없으면 사이트가 망가진 것처럼 느껴진다”고 결정했다.
신용 신청 사이트의 대체 방식도 직접 정했다. 짧은 범용 양식과 Git 기반 명세 저장소를 거부하고, 명세는 마크다운으로 작성하되 정확한 내용이 필요하면 코드 펜스나 백틱으로 표현하라고 했다. 작업에 며칠이 걸린다는 추정에는 “며칠이 아니라 몇 시간 더 하는 일”이라고 답했다. 약 01:44에는 임시 안내 페이지가 보여야 할 경로를 확인하다가 운영 전반에서 502 오류가 발생한 사실도 발견했다. 프로젝트 관리 에이전트는 모든 경로가 잠시 실패한 원인을 배포 중 서버 재시작 가능성으로 추적했다. 이후 배포 전에 먼저 빌드하도록 바꿔 오류 구간을 줄였다.
로그인과 검색은 공유 루틴 변경 뒤 12.5분 동안 중단됐다. 22:24 UTC 배포에서 일부 공유 루틴을 내부 전용으로 바꿨지만, 일반 HTTP를 통해 호출하던 약 40개 호출 지점이 깨졌다. 제한을 프록시로 옮겼고 22:36:50에 복구됐다. 배포 후 스모크 테스트는 검색 페이지가 열리는지만 확인해 검색 결과가 없는 문제를 잡지 못했다. 이후 공유 기능을 바꾸면 실제 로그인, 실제 검색 결과, 연결 앱의 실제 로그인을 시험하도록 규칙을 정했다.
또 다른 오류는 양식 세 개를 너무 일찍 준비 완료로 보고한 일이었다. 10:20에 커티스에게 준비됐다고 보낸 양식 35개 가운데 세 개가 11:31에 추가된 더 엄격한 검사에서 실패했다. 승인 전인 12:20에 보고를 철회했고 13:24까지 수정했다. 검사 기준이 바뀌면 준비 완료로 보고한 항목도 다시 확인해야 한다는 교훈을 남겼다.
결과와 작업 비용
- 커티스의 종료 지시부터 운영 PHP 중단까지 2시간 4분(20:05~22:09:48 UTC)이 걸렸다. Wiz의 취약점 공개부터 중단까지는 약 8시간 10분이었다.
- 승인부터 모든 Drupal 원본 서버를 프록시만 허용하도록 제한하기까지 약 26분(21:04~21:30 UTC)이 걸렸다.
- 22:09 UTC 이후 운영·개발·QA·스테이징에서 인터넷을 통해 접근 가능한 PHP는 발견되지 않았다. DNS를 조사해도 다른 PHP 호스트는 나오지 않았다.
- 10월 22일 03:38 UTC까지 계정 보고서와 메모·플래시 입력·제3자 보고서 주문, 협회 홈페이지·보관소·명단·업로드, 모임 책자, 경험 입력과 편집, 계정 이름 요청, 상시 목록, 플래시 알림 페이지를 Alera로 옮겼다. 기능은 거의 모두 대체됐다.
- 신용 신청 양식은 82개 가운데 82개가 만들어지고 검사돼 운영됐다. 의도적으로 옮기지 않은 것은 폐기 예정 기능과, 두 달 동안 필요하지 않고 전면 개편 예정인 모임 신청·편집기다.
- 배포 중 운영 환경의 502 오류 구간은 에이전트 측정으로 약 66초에서 3.8초로 감소했다.
- 에이전트 실행은 모두 767회였다. Claude Opus 5.5는 계획·관리·조정·검토·문서화·피드백에 257회, Claude Sonnet 5.5는 개발과 양식 제작에 510회 사용됐다. 모델 호출은 14,726회였고 입력 토큰 7,960만 개, 출력 토큰 1,520만 개, 캐시 읽기 17억 3,000만 개, 캐시 쓰기 1억 560만 개였다.
- 정가 기준 추산 비용은 약 1,000달러였다. 프로젝트 에이전트에 약 590달러, 신용 양식 빌더 실행에 186달러, 문서화 에이전트에 178달러, 피드백 에이전트에 46달러로 계산했다. Anthropic 정가와 5분 캐시 쓰기 요금을 적용했으며, 1시간 요금을 적용하면 약 1,220달러다. 구독에서 기록된 실제 지출은 0달러였다.
- 10월 21일 20:00 UTC부터 10월 22일 14:00 UTC까지 운영 브랜치에 배포 태그가 붙은 커밋은 53개였다.
이 사례에서 제시한 권고는 패치가 늦어질 수 있는 서비스 앞에 단계적 교체용 프록시를 두고, 재구축 전에 기능별 사용량을 측정하며, 개발에 들어가기 전에 원본 서버 접근부터 제한하라는 것이다. 프록시가 이미 있으면 기본 경로를 바꾸는 것만으로 빠르게 차단할 수 있고, 되돌리기도 명령 한 번으로 가능하다. 또한 검사 기준을 바꾼 뒤에는 이미 준비 완료로 판정한 항목도 다시 확인해야 한다.
관련 자료: Wiz Research의 “PHP2Shell: AI uncovers critical RCE vulnerabilities affecting 70% of the Internet”(2026년 10월 21일), 에이전트가 실행된 플랫폼 Alera, 이전 사례 연구 oneyaml-go와 oneyaml-csharp 구축 및 oneyaml-rust 구축.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요