프로덕션 방어 플레이북: 무중단 Nginx 규칙, WAF 가상 패치, PHP-FPM 샌드박싱
프로덕션 방어 플레이북: 무중단 Nginx 규칙, WAF 가상 패치, PHP-FPM 샌드박싱
<time datetime="2026-10-23">검증일: 2026년 10월 23일</time>
이 플레이북은 CVE-2026-109618과 CVE-2026-109619(PHP2Shell)에 대해 무중단으로 적용 가능한 방어 구성을 다룹니다. 원칙은 명확합니다. 패치가 최우선이고, WAF와 샌드박스는 패치 전까지 시간을 벌어주는 보조 계층입니다. 각 계층은 독립적으로 배포 가능하며, 롤백 계획을 함께 제공합니다.
1. 다계층 심층 방어 아키텍처
인터넷
│
▼
┌──────────────────────────────────────────────┐
│ Tier 0: CDN / Edge WAF (Cloudflare 등) │
│ - 속도 제한, 알려진 시그니처, 지리 차단 │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 1: Nginx 경계 강화 │
│ - 업로드 경로 분리, multipart 415/403 │
│ - 본문/버퍼 상한, 타임아웃 │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 2: ModSecurity / Coraza 가상 패치 │
│ - 멀티파트 필드 검사, 이상 점수 기반 차단 │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 3: PHP-FPM 설정 강화 │
│ - session.upload_progress Off │
│ - pm.max_requests, 재시작 임계값 │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 4: systemd 샌드박스 + Seccomp │
│ - ProtectSystem, PrivateTmp, NoNewPrivileges│
│ - execve 차단 (업무상 허용 시 제외) │
└──────────────────────────────────────────────┘
2. 즉시 적용 가능한 구성 스니펫
2.1 Tier 1: Nginx 경계 강화
# /etc/nginx/conf.d/php2shell-hardening.conf
# 대용량 본문이 ZMM 힙 그루밍을 쉽게 만들지 않도록 상한 설정
client_max_body_size 8m;
client_body_buffer_size 128k;
client_body_timeout 15s;
client_header_timeout 10s;
send_timeout 20s;
# 업로드가 필요 없는 공개 경로
location ~ ^/(api|health|static|assets)/ {
if ($http_content_type ~* "multipart/form-data") {
return 415;
}
client_max_body_size 256k;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 20s;
}
# 업로드 전용 경로는 별도 location으로 격리
location ^~ /upload/ {
client_max_body_size 64m;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
sudo nginx -t && sudo systemctl reload nginx
2.2 Tier 2: ModSecurity / Coraza 가상 패치
# /etc/modsecurity.d/php2shell.conf
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 67108864
SecRequestBodyInMemoryLimit 131072
# 멀티파트 필드 이름에 업로드 진행 제어 파라미터가 등장하면 차단
SecRule MULTIPART_FILENAME|ARGS_NAMES|REQUEST_HEADERS \
"@rx (?i)PHP_SESSION_UPLOAD_PROGRESS" \
"id:1096181,phase:2,deny,status:403,log,msg:'PHP2Shell upload progress field'"
# 업로드가 아닌 경로로 향하는 multipart 요청
SecRule REQUEST_HEADERS:Content-Type "@rx (?i)multipart/form-data" \
"id:1096182,phase:1,pass,nolog,chain"
SecRule REQUEST_URI "@rx ^/(api|health|static|assets)/" \
"t:lowercase,deny,status:415,msg:'multipart to non-upload path'"
sudo nginx -t && sudo systemctl reload nginx
sudo tail -f /var/log/modsec_audit.log | grep -E '109618[12]'
Coraza(WASM/Go)를 사용하는 경우 동일한 룰 ID 체계를 유지하면 SIEM 상관 분석이 쉬워집니다.
2.3 Tier 3: PHP-FPM 설정
; /etc/php/8.3/fpm/php.ini
session.upload_progress.enabled = Off
session.upload_progress.cleanup = On
expose_php = Off
; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
pm = dynamic
pm.max_children = 24
pm.max_requests = 300
emergency_restart_threshold = 5
emergency_restart_interval = 1m
request_terminate_timeout = 30s
sudo php-fpm -t
sudo systemctl reload php8.3-fpm
2.4 Tier 4: systemd 샌드박싱
# /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
# 권한 상승 차단
NoNewPrivileges=yes
# 핵심 시스템 디렉터리 읽기 전용
ProtectSystem=strict
ProtectHome=yes
# 웹 루트와 세션 경로만 쓰기 허용
ReadWritePaths=/var/lib/php/sessions /var/www/uploads
# 격리된 /tmp (재시작 시 웹셸 소멸)
PrivateTmp=yes
# 커널 공간 보호
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
# 주소 공간 제한
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
MemoryDenyWriteExecute=yes
sudo systemctl daemon-reload
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p ProtectSystem -p PrivateTmp -p NoNewPrivileges
MemoryDenyWriteExecute=yes는 JIT나 일부 확장과 충돌할 수 있습니다. 스테이징에서 먼저 검증하십시오.
3. 배포 순서와 롤백
권장 순서는 가장 위험도가 낮고 효과가 큰 조치부터입니다.
- Tier 3 (
session.upload_progress.enabled=Off) — 수 분 내 적용, 공격면 즉시 감소 - Tier 1 (Nginx 경계 규칙) — 트래픽 패턴 검증 필요
- Tier 2 (WAF 가상 패치) — 오탐 모니터링 후
deny전환 - Tier 4 (systemd 샌드박스) — 스테이징 검증 필수
# 변경 전 백업
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-$(date +%F)
sudo cp /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/php.ini.bak-$(date +%F)
# 롤백
sudo cp /etc/php/8.3/fpm/php.ini.bak-2026-10-23 /etc/php/8.3/fpm/php.ini
sudo systemctl reload php8.3-fpm
sudo nginx -t && sudo systemctl reload nginx
4. 검증 체크리스트
# 1) PHP 콜백 비활성화 확인
php -r 'echo ini_get("session.upload_progress.enabled"), PHP_EOL;'
# 2) 패치 버전 확인
php -v | head -1
# 3) Nginx 문법 및 규칙 로드
sudo nginx -T 2>/dev/null | grep -A2 'multipart/form-data'
# 4) WAF 룰 로드
sudo tail -n 50 /var/log/modsec_audit.log
# 5) systemd 샌드박스 적용
systemctl show php8.3-fpm -p ProtectSystem -p PrivateTmp -p NoNewPrivileges
# 6) 정상 업로드 회귀 테스트
curl -sS -F "file=@/etc/hostname" https://example.com/upload/ -o /dev/null -w '%{http_code}\n'
5. 운영 FAQ
Q1. ProtectSystem=strict를 적용하면 기존 업로드 파일에 문제가 생기나요?
ReadWritePaths에 업로드 디렉터리를 명시하면 기존 파일은 읽기/쓰기 가능합니다. 다만 애플리케이션이 /tmp에 임시 파일을 쓰는 경우 PrivateTmp=yes로 인해 경로가 격리되므로, 세션 저장소와 캐시 경로를 점검해야 합니다.
Q2. Docker/Kubernetes에도 동일하게 적용할 수 있나요?
개념은 동일하지만 구현이 다릅니다. Kubernetes에서는 securityContext.readOnlyRootFilesystem, allowPrivilegeEscalation: false, seccompProfile, NetworkPolicy로 대응합니다.
Q3. WAF가 정상 멀티파트 업로드를 차단하지 않나요?
PHP_SESSION_UPLOAD_PROGRESS 필드는 일반 파일 업로드에서 사용되지 않습니다. 다만 레거시 진행률 표시 애플리케이션이 있다면 예외 경로를 분리해야 합니다. 먼저 pass 모드로 로그를 수집한 뒤 차단으로 전환하십시오.
Q4. 패치 전에 이 구성을 얼마나 유지해야 하나요?
패치가 완료될 때까지 유지하는 것이 안전합니다. 패치 후에도 session.upload_progress를 사용하지 않는다면 비활성 상태를 유지하는 편이 공격면 최소화에 유리합니다.
6. 결론
PHP2Shell 방어는 단일 제품으로 해결되지 않습니다. 엔진 설정, 경계 규칙, WAF, 샌드박스가 서로 다른 실패 모드를 커버합니다. 가장 중요한 것은 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 이상으로의 업그레이드이며, 나머지는 그때까지의 완충 장치입니다.