Joyuno's Dev Blog

WordPress 업그레이드 윈도우 TOCTOU RCE: 업데이트 순간을 공격면으로 보지 않기

Security WordPress

KEV 등재가 바꿔 놓은 전제: “취약함”이 아니라 “악용 중”

CVE-2026-119977(WordPress Core)은 CISA Known Exploited Vulnerabilities(KEV) 카탈로그에 2026-11-03에 추가됐고, 연방기관(FCEB) 기준 조치 기한(dueDate)은 2026-11-06으로 잡혀 있습니다. 레이스 컨디션 취약점이 KEV로 올라왔다는 건, PoC 유무나 재현 난이도 논쟁과 무관하게 “누군가는 이걸 운영 환경에서 성사시키고 있다”는 뜻으로 받아들이는 편이 안전합니다. KEV CSV 원문에도 “predictable, web-accessible staging paths used during full-package core upgrades”라는 요지가 명시돼 있습니다. CISA KEV CSV 원문에서 CVE-2026-119977 행을 직접 확인할 수 있습니다.

WordPress 쪽에서도 7.1.4 보안 릴리스(2026-10-28)에 “upgrade window 동안 RCE로 이어질 수 있는 업그레이드 프로세스 약점”이 포함됐다고 공지했습니다. WordPress 7.1.4 릴리스 공지에 보고자(Umt Bayram)가 언급됩니다. Wordfence는 이 이슈를 CVSS 8.1(AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)로 분류하고, 영향을 받는 범위를 4.7.0~7.1.3로 정리했습니다. Wordfence 취약점 엔트리

운영 대응 글을 지금 쓰는 이유가 충분히 생깁니다. “업데이트를 하라”는 당연한 말보다, 업데이트라는 행위 자체가 열어 버리는 창을 어떻게 닫을지(그리고 닫힌 걸 어떻게 검증할지)가 더 중요해졌습니다.

WordPress를 CMS가 아니라 배포 파이프라인으로 보면 공격면이 보입니다

WordPress Core 업데이트는 단순히 파일 몇 개를 덮어쓰는 작업이 아닙니다. 내부적으로는 “업데이트 아티팩트를 가져오고(다운로드) → 작업 디렉터리에 풀고(unzip) → 일부 파일을 먼저 교체해 새 버전 코드로 업데이트 루틴을 실행하고 → 나머지 파일을 복사(copy) → 정리(cleanup)”에 가깝습니다.

Make WordPress Core 핸드북의 코드베이스 설명에는 업데이트가 트리거되면 ZIP을 내려받아 wp-content/upgrade에 풀고, 거기서 wp-admin/includes/update-core.php를 기존 위치로 복사해 실행하는 식으로 “새로 내려받은 코드가 업데이트 과정을 이어받는다”고 설명합니다. Make WordPress Core: The WordPress Codebase

이 관점이 중요한 이유는 두 가지입니다.

첫째, 업데이트는 “운영 중 배포”입니다. 파일 시스템에 쓰기 권한이 열리고, 임시 작업 공간이 생기고, 그 공간에 실행 가능한 PHP가 놓이는 순간이 있습니다.

둘째, 업데이트는 “컨트롤 플레인과 데이터 플레인의 혼재”로 구현돼 있는 경우가 많습니다. 업데이트(컨트롤 플레인)는 관리자 작업이지만, 파일이 풀리는 경로(wp-content/upgrade)는 종종 퍼블릭 트래픽(데이터 플레인)이 접근 가능한 웹 루트 아래에 있습니다.

CVE-2026-119977은 이 혼재가 만들어낸 전형적인 사고입니다. 무중단 업데이트(= 사이트는 계속 서빙됨)라는 편의가, “업데이트가 진행되는 짧은 순간에만 존재하는 신규 엔트리포인트”를 만들어 버렸습니다.

CVE-2026-119977: TOCTOU 레이스가 RCE로 이어지는 조건

Wordfence는 이 이슈를 “Unauthenticated Upgrade-Window Staged-Installer Remote Code Execution”으로 부르고, 원인을 “예측 가능하고 웹으로 접근 가능한 스테이징 경로”로 요약합니다. Wordfence 취약점 엔트리

Wordfence 블로그 글은 이 취약점이 계정 없이 가능하지만, 아주 좁은 윈도우(랩 환경에서는 수 초)를 맞춰야 하므로 공격자는 반복 polling으로 그 순간을 노린다고 설명합니다. 또한 Wordfence Premium/Care/Response에는 2026-10-28(릴리스 후 약 3시간)에 룰이 배포됐고, Free는 30일 지연으로 2026-11-27에 동일 룰을 받는다고 못 박습니다. Wordfence 분석 글

여기서 운영자가 실제로 따져야 하는 “성립 조건”은 아래 네 덩어리입니다.

1) 업데이트가 “full-package” 경로로 진행될 것

Wordfence 표현 그대로 “full-package core upgrades”에서 문제가 됩니다. 즉, 업데이트 아티팩트가 통째로 풀리면서(임시 WordPress 트리 생성) 설치/설정 스크립트 같은 파일이 작업 디렉터리에 함께 존재하게 되는 경로입니다. Wordfence 취약점 엔트리

운영상 함정은, 어떤 사이트가 항상 partial update로만 가는지 외부에서 확신하기 어렵다는 점입니다. 플러그인/테마로 코어 파일이 바뀌었거나(무결성 불일치), 파일 권한/디스크 상태/네트워크 상태 때문에 업데이트가 fallback 경로를 타면 full-package로 갈 가능성이 생깁니다. 따라서 “우리 사이트는 partial이니까 괜찮다”는 가정은 방어 설계에서 빼는 편이 낫습니다.

2) wp-content/upgrade 아래가 웹으로 서빙되고, PHP가 실행될 것

취약점의 핵심이 “web-accessible staging paths”입니다. 웹 서버가 /wp-content/upgrade/... 요청을 정적 파일처럼 내려주거나, 더 나쁘게는 그 경로 아래의 PHP를 실행해 버리면 공격자가 “업데이트 중에만 생기는 임시 WordPress 트리”를 직접 때릴 수 있습니다. Wordfence 분석 글

이 조건은 생각보다 흔합니다.

  • WordPress 기본 레이아웃에서 wp-content는 웹 루트 바로 아래에 있고, 많은 설정이 wp-content 하위를 통째로 허용합니다.
  • upgrade/는 “임시 폴더”라서 보안 하드닝의 우선순위에서 자주 밀립니다.

3) 스테이징 경로가 예측 가능하거나(혹은 노출로 발견 가능)할 것

예측 가능성은 공격 자동화를 가능하게 하는 열쇠입니다. 7.1.4에서 WordPress가 이 부분을 직접 언급합니다. wp-admin/includes/class-wp-upgrader.php의 7.1.4 코드에는 “upgrade directory is web-served”이며 디렉터리 listing으로 working directory 이름이 발견되지 않도록 index.php를 유지/생성해야 한다는 주석이 추가돼 있습니다. 7.1.4 class-wp-upgrader.php(raw)

즉, “웹으로 서빙되는 디렉터리 아래에 working directory를 만들고 있었다”는 사실 자체를 코어 코드가 인정한 셈입니다.

4) 공격자가 레이스에서 이길 만큼 충분한 시간과 시도를 가질 것

이 취약점은 CVSS에서 AC:H(Attack Complexity High)로 분류됩니다. Wordfence 취약점 엔트리

하지만 운영 환경에서는 “어려움”이 “안전함”으로 직결되지 않습니다.

  • 자동 업데이트가 정해진 시간대에 몰리면(특히 호스팅 사업자 환경) 공격자가 그 시간대를 학습합니다.
  • 공격자는 한 번의 정교한 요청이 아니라, 수 시간 동안의 반복 polling으로 확률을 끌어올립니다.

Wordfence는 룰 배포 후 9분 만에 첫 매치가 발생했고, 공격자들이 반복 polling으로 그 윈도우를 노리고 있다고 씁니다. Wordfence 분석 글

7.1.4는 무엇을 바꿨나: “우연히” 닫히는 창이 아니라 “설계로” 닫히는 창

이 이슈를 운영 관점에서 다룰 때 중요한 건, 패치가 단순히 “한 줄 수정”이 아니라 “공격 체인에서 여러 링크를 끊는다”는 점입니다. 7.1.4는 최소 세 군데를 건드립니다.

1) working directory 이름을 예측 불가능하게 만듭니다

7.1.4의 WP_Upgrader::unpack_package()는 working directory를 get_working_dir_name()으로 만들고, 이 함수는 패키지 이름에 16글자 랜덤 문자열을 덧붙입니다.

이 변화는 공격자의 “경로 예측 → polling”을 크게 비싸게 만듭니다. 예전엔 wordpress-7.1.4 같은 규칙 기반 추정이 가능했지만, 이제는 랜덤 suffix를 모르면 맞추기 어렵습니다.

2) wp-content/upgrade에 index.php를 강제해 directory listing 기반 발견을 막습니다

7.1.4 코드는 upgrade 디렉터리가 웹으로 서빙된다는 전제 하에 index.php를 유지/생성합니다.

  • “The upgrade directory is web-served. Keep it from being listed …” 주석이 들어가고
  • 폴더가 없으면 만들며
  • index.php가 없으면 Silence is golden. 파일을 씁니다. 7.1.4 class-wp-upgrader.php(raw)

여기서 운영자가 놓치면 안 되는 포인트는, 코어의 index.php는 “listing 방지”일 뿐 “직접 접근/실행 방지”가 아니라는 점입니다. 공격은 listing이 아니라 정확한 파일 경로(예: .../wp-admin/setup-config.php)를 직접 요청하는 형태로 진행될 수 있습니다. 따라서 서버 레벨 차단은 여전히 필요합니다.

3) working directory에서 생성될 수 있는 wp-config.php를 production에 복사하지 않도록 합니다

7.1.4의 wp-admin/includes/update-core.php에는 아래 주석과 함께 skip 목록에 wp-config.php가 추가돼 있습니다.

이건 “업데이트 working directory에서 누군가 wp-config.php를 만들어 두면, 그게 promotion(복사)될 수 있었다”는 위협 모델을 공식 코드가 받아들였다는 의미로 읽힙니다.

(참고) 패치의 맥락을 한 번 더 확인하는 용도

GitHub 미러의 커밋 메시지로는 “Improve the handling of the upgrade working directory”로 정리돼 있습니다. WordPress/WordPress 커밋 b625b2d49c6a

방어 설계 (1): 업그레이드 작업을 내부망/관리망으로 격리한다

WordPress를 애플리케이션으로 보고 배포 파이프라인을 분리하면, 가장 먼저 나눠야 하는 건 “누가 업데이트를 트리거할 수 있는가”입니다.

  • 데이터 플레인: 퍼블릭 트래픽(사용자)
  • 컨트롤 플레인: 업데이트 트리거(관리자/자동화)

CVE-2026-119977은 데이터 플레인이 컨트롤 플레인 작업의 산출물(working directory)과 부딪히면서 생긴 사고입니다. 따라서 격리 설계의 목표는 “업데이트가 진행되는 동안만이라도 퍼블릭 트래픽이 그 산출물에 닿지 못하게 하는 것”입니다.

현실적인 선택지는 보통 셋입니다.

선택지 A: 업데이트는 관리 호스트에서만 수행(WP-CLI), 웹은 항상 차단 규칙 유지

WP-CLI는 wp core upgrade/wp core update로 코어 업데이트를 수행할 수 있고, 업데이트 중 lock 에러가 나는 경우 core_updater.lock 옵션을 언급합니다. WP-CLI: wp core upgrade

운영 형태는 이렇게 가져갑니다.

  • 웹 서버는 /wp-content/upgrade/에 대해 영구적으로 deny
  • 업데이트 트리거는 SSH 가능한 관리 호스트(혹은 SSM)에서만 수행
  • wp-admin에서 “Update Now” 버튼을 쓰지 않는 쪽으로 통제(권한/정책)

이렇게 하면 “업데이트 창”이 열리더라도 퍼블릭에서 그 창을 관측/접근하기가 어려워집니다.

선택지 B: blue/green 또는 immutable 배포로 “in-place 업데이트”를 없애기

WordPress가 전통적으로 in-place 업데이트를 제공하는 이유는 단일 서버/공유 호스팅 모델에 최적화돼 있기 때문입니다. 반대로, 컨테이너/VM 이미지 기반으로 WordPress 코어를 아티팩트화한 팀이라면 in-place 업데이트 자체를 금지하는 편이 더 낫습니다.

  • 새 이미지를 빌드(코어 7.1.4 포함)
  • wp-content는 별도 볼륨/오브젝트 스토리지로 분리
  • 트래픽 스위치로 cutover

이 모델에서는 wp-content/upgrade에 “실행 가능한 임시 트리”가 생길 일이 없습니다. 업데이트 윈도우 자체가 사라집니다.

선택지 C: 운영 중 업데이트는 유지하되, 업데이트 시간대에는 외부 트래픽을 드레인

무중단이 반드시 필요하고, 구조를 바꾸기 어렵다면 “업데이트 구간에만” 외부 트래픽을 제한하는 방식이 가능합니다.

  • L7 LB에서 해당 인스턴스 드레인
  • WAF에서 /wp-content/upgrade/를 강제 차단
  • 업데이트 수행
  • 검증 후 트래픽 복귀

핵심은 “WordPress가 만드는 유지보수 모드(.maintenance)”에 기대지 않는 것입니다. .maintenance는 WordPress 앱의 동작을 제한하는 장치이지, 웹 서버 레벨에서 특정 경로의 PHP 실행을 막는 장치가 아닙니다.

방어 설계 (2): 스테이징 경로 웹 노출을 차단한다(가장 ROI가 큰 레이어)

이 취약점 대응에서 가장 비용 대비 효과가 큰 조치는 /wp-content/upgrade/에 대한 HTTP 접근을 서버 레벨에서 차단하는 것입니다. 코어는 그 경로를 파일시스템 작업 공간으로 쓰기 때문에, 웹에서 접근만 막으면 업데이트 기능 자체는 유지됩니다.

이 포인트는 운영자가 불안해하는 지점이기도 한데, “차단하면 업데이트가 실패하지 않나”가 가장 흔한 반론입니다. WordPress 업데이트 로직은 PHP 프로세스가 로컬 파일시스템을 읽고 쓰는 방식이어서, HTTP로 그 경로를 접근할 수 없게 만드는 것과는 별개입니다. (업데이트 실패 여부는 각 환경에서 검증은 필요합니다.)

nginx 예시: /wp-content/upgrade/ 전체 deny

아래는 가상호스트(server 블록) 내부에 들어갈 수 있는 가장 단순한 형태입니다.

# /etc/nginx/conf.d/wordpress-hardening.conf

# upgrade 작업 디렉터리는 웹에서 접근할 이유가 없습니다.
location ^~ /wp-content/upgrade/ {
    deny all;
    return 403;
}

# 보수적으로는 PHP만 막는 선택지도 가능하지만,
# 이번 케이스는 디렉터리 전체를 막는 편이 운영 검증이 쉽습니다.

적용/검증:

nginx -t
systemctl reload nginx

# 기대 결과: 403
curl -I https://example.com/wp-content/upgrade/

Apache 예시: vhost 또는 Directory로 차단

.htaccess로도 가능하지만, 운영에서는 vhost 단에서 강제하는 편이 일관성이 좋습니다.

# Apache vhost 또는 conf.d
<Directory "/var/www/html/wp-content/upgrade">
    Require all denied
</Directory>

경로는 배포 구조에 맞게 잡습니다(심볼릭 링크/멀티 사이트에서 특히 확인).

CDN/WAF 예시: 경로 기반 block

표현식은 제품마다 다르지만, 의도는 같습니다.

  • URI path가 /wp-content/upgrade/로 시작하면 block
  • 가능하면 origin에 도달하기 전에 차단

이번 취약점은 WordPress 앱이 로드되기 전에 “스테이징된 PHP 파일”을 직접 hit하는 방식이므로, 앱 레벨 WAF(플러그인)만 믿으면 빈 구간이 생길 수 있습니다. Wordfence도 공격이 staged PHP 파일로 직접 가기 때문에 WordPress를 로드하지 않을 수 있으며, Wordfence의 경우 Extended Protection이 중요하다고 강조합니다. Wordfence 분석 글

방어 설계 (3): 자동 업데이트 시간대를 제어한다(“창”을 예측 불가능하게 만들기)

CVE-2026-119977이 불편한 이유는 “패치를 적용하는 행위가 마지막으로 취약한 창을 여는 행위”가 될 수 있기 때문입니다. 그래서 나는 자동 업데이트를 무조건 끄기보다는, “통제 가능한 시간대”로 몰아 넣고 그 시간대에만 네트워크/로그/권한을 강화하는 쪽을 더 선호합니다.

WordPress는 자동 업데이트 제어를 공식적으로 지원합니다.

  • AUTOMATIC_UPDATER_DISABLED로 전체 비활성화
  • WP_AUTO_UPDATE_CORE로 코어 업데이트 유형 제어
  • 필터(allow_minor_auto_core_updates, allow_major_auto_core_updates, automatic_updater_disabled 등)로 세밀 제어

이 내용은 WordPress Developer 문서(Upgrading WordPress)에 정리돼 있고, 필터는 must-use plugin에 넣는 것을 권장하며 wp-config.php에 add_filter()를 넣지 말라고 명시합니다. Upgrading WordPress – Developer.WordPress.org

MU plugin으로 업데이트 가능 시간대 제한(예시)

아래 예시는 “KST 03:00~04:59 사이에만 자동 업데이트 허용”을 강제합니다. 운영에서는 이 시간대에 WAF 차단 규칙이 이미 적용돼 있고, 관제/알림이 강화돼 있다는 전제가 붙습니다.

파일 경로:

  • wp-content/mu-plugins/auto-update-window.php

코드:

<?php
/**
 * Auto-update window control.
 *
 * Place this file under wp-content/mu-plugins/ so it always loads.
 */

add_filter('automatic_updater_disabled', function ($disabled) {
    // 운영 표준 시간대를 명시합니다.
    $tz = new DateTimeZone('Asia/Seoul');
    $now = new DateTimeImmutable('now', $tz);

    // 03:00~04:59 허용
    $hhmm = (int)$now->format('Hi');
    $allowed = ($hhmm >= 300 && $hhmm < 500);

    // allowed=true면 disabled=false
    return !$allowed;
});

검증(의도 확인용):

  • 허용 시간대 밖에서 자동 업데이트가 발생하지 않아야 합니다.
  • 업데이트 메일/로그가 특정 구간에만 생성되는지 확인합니다.

이 방식의 단점은, 보안 패치가 “즉시 적용”되지 않고 “다음 윈도우”까지 밀릴 수 있다는 점입니다. KEV처럼 조치 기한이 촉박한 경우에는 윈도우를 넓히거나, 해당 CVE에 한해서는 수동 업데이트 런북으로 전환하는 게 맞습니다.

fleet 운영에서 중요한 건 “동시성”입니다

Wordfence가 설명한 공격 모델은 “여러 사이트를 반복 polling 하면서 누군가 업데이트하는 순간을 잡는다”입니다. Wordfence 분석 글

사이트가 수십/수백 개면, 자동 업데이트를 같은 시간대에 몰아 넣는 순간 공격자에게 유리해집니다. 그래서 시간대 제어는 “업데이트를 한 시간대에 몰아넣기”가 아니라 “통제 가능한 구간 안에서 랜덤 지터로 분산하기”까지 포함해야 합니다.

  • 윈도우는 예: 03:00~05:00
  • 각 사이트는 그 안에서 랜덤 0~30분 지연 후 업데이트

WordPress 기본 자동 업데이트만으로 이 수준의 지터를 만들기 어렵다면, 관리망에서 WP-CLI 기반으로 잡아주는 편이 낫습니다.

방어 설계 (4): WAF/권한/파일시스템 옵션으로 “창”을 닫는 구현 디테일

여기부터는 “패치 적용”과 별개로, 업데이트 파이프라인 자체를 하드닝하는 얘기입니다. CVE-2026-119977만 막는 걸 넘어서, 다음에 비슷한 클래스(업데이트/설치 과정에서의 staging 노출)가 나와도 방어선이 남도록 만드는 게 목표입니다.

1) WAF 룰은 path 단위로 단순하게 가져갑니다

이번 케이스는 파라미터 트릭보다 경로 자체가 문제였습니다.

  • block: /wp-content/upgrade/ 하위 전체
  • (선택) block: /wp-admin/setup-config.php에 대한 외부 접근(프로비저닝 때만 허용)

특히 두 번째 룰은 신규 설치 자동화가 있는 조직에서는 예외 처리가 필요합니다. 하지만 “이미 운영 중인 사이트”라면 setup-config.php가 외부에서 호출될 이유가 거의 없습니다.

2) 권한 모델: 업데이트 권한은 “운영 권한”과 동급으로 취급

WordPress에서 코어 업데이트는 서버 파일시스템 쓰기 권한을 수반합니다. 즉, 업데이트 권한은 곧 “서버 위에서 실행되는 PHP 코드/파일의 교체 권한”입니다.

  • 조직 권한 모델에서 “콘텐츠 편집자”와 “배포 권한자”는 분리돼야 합니다.
  • wp-admin 계정 수를 줄이는 게 아니라, 업데이트 트리거 경로 자체를 통제해야 합니다.

이 부분은 예전에 정리했던 “외부 노출 인스턴스 운영 런북”들과 결이 같습니다. 취약점마다 UI/제품은 달라도, 운영에서 중요한 건 배포 권한을 별도 평면으로 격리하는 것입니다.

3) 파일시스템 옵션은 “PHP 실행 제어”로 생각해야 합니다

Linux mount 옵션의 noexec는 ELF 실행을 막는 데는 도움이 되지만, PHP-FPM이 스크립트를 해석하는 걸 막아주지는 않습니다. 즉, 이번 케이스 같은 “PHP 파일 직접 hit” 계열에는 mount 옵션이 결정타가 아닙니다.

대신 웹 서버/PHP 라우팅 계층에서 “특정 경로 아래의 .php는 절대 실행하지 않는다”를 강제해야 합니다.

  • nginx: 해당 location에서 deny all
  • Apache: Directory 단 deny 또는 PHP engine off
  • PHP-FPM: pool 분리로 security.limit_extensions를 강화하는 방식도 가능하지만, 운영 복잡도가 올라갑니다

4) 플러그인 기반 방어는 빈 구간이 생길 수 있습니다

Wordfence는 공격이 staged PHP 파일로 직접 접근하며 WordPress를 로드하지 않을 수 있다는 점을 강조하고, Wordfence의 경우 Extended Protection 설정을 언급합니다. 그리고 Free 룰 배포가 2026-11-27로 늦는다는 일정까지 고지합니다. Wordfence 분석 글

이건 Wordfence 제품 얘기이기도 하지만, 더 일반화하면 “앱 내부에 붙은 보안 미들웨어는 앱이 로드되지 않는 경로를 커버하지 못한다”는 고전적인 문제입니다. 업데이트/설치/백업/임포트처럼 “정상 라우팅 바깥”에서 일어나는 일은 가능한 한 L7 프록시/CDN/WAF 또는 웹 서버에서 막는 편이 강합니다.

운영 런북: 업데이트 전에 닫고, 업데이트 후에 확인한다

CVE-2026-119977은 “패치 전까지 취약”이 아니라 “패치 적용 순간에도 취약 창이 열릴 수 있음”이 문제입니다. 그래서 순서가 중요합니다.

1) /wp-content/upgrade/ 웹 차단(영구 적용 권장) 2) WordPress 코어 업데이트 수행 3) 업데이트 직후 관측(로그/파일/계정)으로 “업데이트 윈도우 동안 무슨 일이 있었는지” 확인

1) 버전 확인(업데이트 전/후)

Wordfence는 영향을 받는 버전과 패치 버전을 브랜치별로 나열합니다. 예를 들어 최신 라인은 7.1.4이며, 4.7까지 backport가 있습니다. Wordfence 취약점 엔트리

WordPress도 7.1.4가 보안 릴리스이며 즉시 업데이트를 권고합니다. WordPress 7.1.4 릴리스 공지

추가로 WordPress 문서는 4.6 및 그 이전 브랜치는 더 이상 보안 업데이트를 받지 못한다고 못 박습니다. WordPress 7.1.4 문서(Version 7.1.4)

2) WP-CLI로 업데이트(예시) + 기대 출력

아래는 “관리 호스트에서 SSH로 붙어서 업데이트”하는 가장 단순한 예시입니다.

전제:

  • 웹 서버에서 /wp-content/upgrade/는 이미 차단됨
  • WP-CLI가 설치돼 있음
  • 웹 서버 사용자 권한으로 실행(루트로 돌리는 운영은 피하는 편이 낫습니다)
# (1) 현재 버전 확인
wp core version --path=/var/www/html

# 기대 출력 예시
# 7.1.3

# (2) 코어 업데이트
wp core upgrade --path=/var/www/html

# (3) 업데이트 후 버전 확인
wp core version --path=/var/www/html

# 기대 출력 예시
# 7.1.4

업데이트 중 “Another update is currently in progress.” 같은 lock 관련 안내는 WP-CLI 문서가 언급하는 경로로 처리합니다(무작정 option을 지우기 전에 실제 동시 업데이트가 없는지 확인이 먼저입니다). WP-CLI: wp core upgrade

3) 로그 헌팅(업데이트 시간대 주변)

이번 이슈에서 최소한 확인해야 하는 흔적은 아래 두 종류입니다.

  • /wp-content/upgrade/ 하위 접근 시도
  • 특히 setup-config.php 같은 설치/설정 파일 접근 시도

예시(nginx access log):

# upgrade 경로 접근
zgrep -h "wp-content/upgrade" /var/log/nginx/access.log*

# setup-config.php 흔적(정상 운영 중에는 거의 0이길 기대)
zgrep -h "setup-config\.php" /var/log/nginx/access.log*

경로 기반의 탐지는 단순하지만, 이번 케이스에서는 효과가 큽니다. 공격자가 레이스를 노릴 때도 결국은 그 파일을 hit해야 하기 때문입니다.

4) 파일 무결성 점검 포인트

7.1.4는 update-core.php에서 “working directory에 쓰였을 수 있는 wp-config.php를 절대 복사하지 않는다”를 명시합니다. 7.1.4 update-core.php(raw)

따라서 운영 점검은 이렇게 가져가는 편이 낫습니다.

  • 업데이트 직후 wp-config.php의 mtime/sha256 확인
  • wp-content/upgrade/ 아래에 예상 외 파일이 남아 있는지 확인(정상이라면 index.php 정도만 남는 구성을 목표로)

트레이드오프와 도입 판단 기준

이 글의 방어 설계는 “WordPress를 배포 파이프라인을 가진 애플리케이션”으로 취급할 때 자연스럽게 따라오는 선택들입니다. 하지만 모든 환경에서 동일하게 적용되지는 않습니다.

  • 공유 호스팅에서 nginx/apache 설정을 직접 만질 수 없다면, CDN/WAF에서 path block을 강제하는 쪽이 먼저입니다.
  • WAF도 없고 서버 설정도 못 만지는 환경이라면, 업데이트 윈도우 동안만이라도 사이트를 외부에서 차단하는(maintenance 페이지를 CDN에서 서빙) 방식이 차선입니다.
  • immutable 배포로 갈 수 있는 팀이라면, in-place 업데이트 자체를 없애는 게 가장 깔끔합니다.

KEV에 올라온 RCE 계열은 “패치 적용”만으로 끝내면 아쉬운 경우가 많습니다. 이번 건은 특히 업데이트 순간 자체가 공격면이었기 때문에, 나는 /wp-content/upgrade/ 차단을 “임시 mitigation”이 아니라 “기본 하드닝”으로 승격하는 게 맞다고 봅니다.

참고 자료