Language/PHP

PHP,Windows,IIS)패치 없는 PHP(php7,EOL)의 보안취약점(PHP2Shell),URL재작성(Rewrite)으로 방어 및 진단

소소한 늙은 개발자의 메모장 2026. 10. 22. 12:55
반응형

 

PHP, Windows, IIS) PHP(PHP2Shell)취약점 위험성과 대응 방안

최근(2026-10-22 기준) 이슈가 되는 PHP 신규 보안 취약점(CVE-2026-109618, CVE-2026-109619)과 관련하여, 8.5~8.2 버전은 패치가 제공 되나, 지원이 종료(EOL, End-of-Life)된 PHP 7 버전 등은 문제가 있으며, 특히 Windows기반의 IIS를 활용하고 있어 일단 해결책이 필요하여 Gemini의 지원을 받아 해결하여 해당 정보를 공휴하고자 한다.

보안 취약점

  • PHP 보안 취약 점 : CVE-2026-109618, CVE-2026-109619
  • allow_url_include,- allow_url_fopen, expose_php 설정(PHP: Description of core php.ini directives - Manual)의 취약점
  • 취약점 이용 방법이 공개됨에 따라 보안 취약은 알고 있었어도, 방법을 몰랐던 악성 사용자(해커)들도 알게 됨에 따라 취약점 방어는 필수

대응 방법 (Windows 환경에서 )

  • Apache + PHP-CGI(또는 XAMPP 기본 설정) : 모듈 방식(mod_php)이나 FastCGI(php-fpm) 전용 안전 설정으로 전환
  • IIS + PHP : 처리기 매핑에서 모듈 방식 FastCgiModule으로 변경

Windows/IIS 환경 기준 (서비스/사이트별 적용)

직접 수정

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
   <rewrite>
       <rules>
           <rule name="Block PHP-CGI Argument Injection (CVE-2024-4577)" stopProcessing="true">
               <match url=".*\.php$" ignoreCase="true" />
               <conditions logicalGrouping="MatchAny">
                   <!-- 소프트 하이픈 인코딩 및 하이픈 옵션 주입 패턴 차단 -->
                   <add input="{UNENCODED_URL}" pattern=".*(%ad|%AD|-d|-s|-w).*" />
                   <add input="{QUERY_STRING}" pattern=".*(%ad|%AD|-d|-s|-w).*" />
               </conditions>
               <action type="CustomResponse" statusCode="403" subStatusCode="0" statusReason="Forbidden" statusDescription="Blocked by Security Policy" />
           </rule>
       </rules>
   </rewrite>
</system.webServer>
</configuration>​

or(또는)

  • web.config 기반 긴급 차단 룰 적용 (http://localhost/?~~~ 로 PHP확장자와 기본페이지 기준 적용)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Block-PHP-CGI-Arg-Injection-Global" stopProcessing="true">
                    <!-- 루트 빈 경로(^$), 디렉터리 슬래시 끝(/$), .php 파일만 검사 대상 지정 -->
                    <match url="(^$|.*\/$|.*\.php$)" />
                    <conditions logicalGrouping="MatchAny">
                        <!-- 1) 소프트 하이픈(%ad, %AD) 차단 -->
                        <add input="{QUERY_STRING}" pattern=".*(%ad|%AD).*" />
                        <!-- 2) 쿼리 스트링 내 독립된 옵션 플래그(-d, -s, -w) 차단 -->
                        <add input="{QUERY_STRING}" pattern="(^|&|\+|%20)-[dswDSW](\b|=|\+|%20)" />
                        <!-- 3) 인코딩 우회 주입 차단 -->
                        <add input="{UNENCODED_URL}" pattern="(%ad|%AD|(^|&|\+|%20)-[dswDSW](\b|=|\+|%20))" />
                    </conditions>
                    <action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Blocked by Security Policy" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

PowerShell 자동화 스크립트 실행

web.confing 직접 수정이 아닌 스크립트로 할 경우

  • PowerShell 자동화 스크립트 (http://localhost/~~~.php 로 끝나는 경우만 적용 할 경우)
# ==============================================================================
# PHP CGI Argument Injection Mitigation for IIS on Windows Server 2022
# ==============================================================================
# 1. 관리자 권한 확인 및 IIS 모듈 로드
Import-Module WebAdministration

$SiteName = "Default Web Site" # 적용할 IIS 사이트명으로 변경
$RuleName = "Block-PHP-CGI-Arg-Injection-extPHP"

Write-Host "[+] IIS URL Rewrite 모듈 설치 여부 확인 중..." -ForegroundColor Cyan
$rewriteModule = Get-WebConfiguration -pspath "MACHINE/WEBROOT/APPHOST" -filter "system.webServer/globalModules/add[@name='RewriteModule']"

if (-not $rewriteModule) {
    Write-Warning "[!] IIS URL Rewrite 모듈이 설치되어 있지 않습니다."
    Write-Warning "    https://www.iis.net/downloads/microsoft/url-rewrite 에서 다운로드 후 설치하십시오."
    return
}

Write-Host "[+] '$SiteName'에 CVE-2024-4577 완화 규칙 적용 중..." -ForegroundColor Cyan

# 중복 룰 방지를 위해 기존 동일 이름 규칙 삭제
Clear-WebConfiguration -pspath "IIS:\Sites\$SiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']" -ErrorAction SilentlyContinue

# 새 규칙 추가
Add-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" -filter "system.webServer/rewrite/rules" -name "." -value @{
    name = $RuleName
    stopProcessing = "True"
}

# Match URL 설정 (*.php)
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/match" -name "url" -value ".*\.php$"

# Action 설정 (403 Custom Response)
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" -name "type" -value "CustomResponse"
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" -name "statusCode" -value 403
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" -name "statusReason" -value "Forbidden"

# Conditions 설정 (%ad, -d 등 주입 탐지)
Add-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/conditions" -name "." -value @{
    input = "{QUERY_STRING}"
    pattern = ".*(%ad|%AD|-d|-s).*"
}

Write-Host "[+] URL Rewrite 보안 차단 규칙이 성공적으로 배포되었습니다." -ForegroundColor Green

or(또는)

  • PowerShell 자동화 스크립트 (http://localhost/?~~~ 로 PHP확장자와 기본페이지 기준 적용)
Import-Module WebAdministration

$SiteName = "Default Web Site" # 실제 IIS 사이트 이름
$RuleName = "Block-PHP-CGI-Arg-Injection-Global"

Write-Host "[+] '$SiteName'의 URL 재작성 규칙을 정밀 필터링으로 갱신 중..." -ForegroundColor Cyan

# 1. 기존 규칙 삭제
Clear-WebConfiguration -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']" -ErrorAction SilentlyContinue

# 2. 새 규칙 등록
Add-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules" -name "." -value @{
        name = $RuleName
        stopProcessing = "True"
    }

# 3. Match URL: *.php 또는 디렉터리 경로(^$, .*\/$)만 매칭 (.js, .css 등 정적 파일 완전 제외)
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/match" `
    -name "url" -value "(^$|.*\/$|.*\.php$)"

# 4. Action: 403 Forbidden 응답 반환
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" `
    -name "type" -value "CustomResponse"
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" `
    -name "statusCode" -value 403
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" `
    -name "statusReason" -value "Forbidden"

# 5. Conditions: MatchAny
Set-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/conditions" `
    -name "logicalGrouping" -value "MatchAny"

# 6. Condition 1: 소프트 하이픈 공격 벡터 차단 (%ad, %AD)
Add-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/conditions" -name "." -value @{
        input = "{QUERY_STRING}"
        pattern = ".*(%ad|%AD).*"
    }

# 7. Condition 2: PHP CLI 옵션 플래그 정밀 탐지 (경계값 기준 -d, -s, -w 매칭)
Add-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/conditions" -name "." -value @{
        input = "{QUERY_STRING}"
        pattern = "(^|&|\+|%20)-[dswDSW](\b|=|\+|%20)"
    }

# 8. Condition 3: 인코딩 우회 원본 URL(UNENCODED_URL) 정밀 검사
Add-WebConfigurationProperty -pspath "IIS:\Sites\$SiteName" `
    -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/conditions" -name "." -value @{
        input = "{UNENCODED_URL}"
        pattern = "(%ad|%AD|(^|&|\+|%20)-[dswDSW](\b|=|\+|%20))"
    }

Write-Host "[+] 사이트 수준 정밀 차단 규칙이 성공적으로 배포되었습니다." -ForegroundColor Green

Windows/IIS 환경 기준 (전체/전역 적용 & 확장자 상관 없이)

서비스/사이트별 적용 또는 전체 적용 및 PHP확장자만 적용할지는 관리자의 선택 영역

    • applicationHost.config 기반 차단 룰 적용

C:\Windows\System32\inetsrv\config\applicationHost.config 파일에 포함

  •  
<system.webServer>
    <rewrite>
        <globalRules>
            <rule name="Global-Block-PHP-CGI-Arg-Injection" stopProcessing="true">
                <!-- 1) 루트 빈 문자열(^$), 2) 슬래시로 끝나는 디렉터리(/$), 3) .php 로 끝나는 파일만 검사 -->
                <match url="(^$|.*\/$|.*\.php$)" />
                <conditions logicalGrouping="MatchAny">
                    <!-- 소프트 하이픈 공격 벡터 차단 -->
                    <add input="{QUERY_STRING}" pattern=".*(%ad|%AD).*" />
                    <!-- 독립된 CGI 파라미터 플래그(-d, -s, -w)만 엄격히 차단 -->
                    <add input="{QUERY_STRING}" pattern="(^|&amp;|\+|%20)-[dswDSW](\b|=|\+|%20)" />
                </conditions>
                <action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Blocked by Security Policy" />
            </rule>
        </globalRules>
    </rewrite>
</system.webServer>
  • PowerShell을 자동화 스크립트 (전역 설정)
Import-Module WebAdministration

$GlobalRuleName = "Global-Block-PHP-CGI-Arg-Injection"

Write-Host "[+] IIS 전역 규칙 정밀 필터링으로 갱신 중..." -ForegroundColor Cyan

# 1. 기존 전역 규칙 삭제
Clear-WebConfiguration -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']" -ErrorAction SilentlyContinue

# 2. 전역 규칙 생성
Add-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules" -name "." -value @{
        name = $GlobalRuleName
        stopProcessing = "True"
    }

# 3. Match URL: *.php 또는 디렉터리 경로(/ 또는 빈 문자열)만 매칭 (.js, .css, 이미지 등 제외)
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']/match" `
    -name "url" -value "(^$|.*\/$|.*\.php$)"

# 4. Action: 403 Forbidden
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']/action" `
    -name "type" -value "CustomResponse"
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']/action" `
    -name "statusCode" -value 403
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']/action" `
    -name "statusReason" -value "Forbidden"

# 5. Conditions: MatchAny
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']/conditions" `
    -name "logicalGrouping" -value "MatchAny"

# 6. Condition 1: 소프트 하이픈 인코딩 탐지 (%ad, %AD)
Add-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']/conditions" -name "." -value @{
        input = "{QUERY_STRING}"
        pattern = ".*(%ad|%AD).*"
    }

# 7. Condition 2: PHP 실행 옵션 플래그 정밀 탐지 (쿼리 시작(^), 공백(+), 또는 구분자(&) 바로 뒤의 -d, -s, -w 만 매칭)
Add-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/rewrite/globalRules/rule[@name='$GlobalRuleName']/conditions" -name "." -value @{
        input = "{QUERY_STRING}"
        pattern = "(^|&|\+|%20)-[dswDSW](\b|=|\+|%20)"
    }

Write-Host "[+] 오탐이 방지된 전역 인바운드 보안 규칙이 적용되었습니다." -ForegroundColor Green

※ .php만 적용할지, 전체 모든 입력에 대하셔 적용할지는 관리자의 선택 영역


PHP.INI 수정

위의 URL 재작성 룰로 우선 방어 가능하니, 확인 후 진행 필요.
특히 allow_url_fopen의 경우 클롤링 및 외부 API호출을 위하여 편의상 fopen(), file_get_contents() 사용되었을 수 있으니, 꼭 확인 후 적용을 추천

  • php.ini FastCGI 매핑 추가 점검 사항 (소스 확인 후 적용)
    • 기본 환경 설정
allow_url_include = Off
allow_url_fopen = Off
expose_php = Off
  • 세션 환경 설정
    • session.upload_progress.enabled : 실시간 업로드 진행률 설정 (기본적으로 Off)
    • session.use_strict_mode : 세선 공격을 방어하기 위한 설정
session.upload_progress.enabled = 0
session.use_strict_mode = 1

점검절차 (curl 이용)

  • 정상 요청 확인 (기대값: HTTP 200 OK)
curl -I -s -o /dev/null -w "%{http_code}\n" "http://localhost/index.php?test=normal"
  • CVE-2024-4577 악용 패턴 테스트 (기대값: HTTP 403 Forbidden)
# 1) 소문자 소프트 하이픈(%ad) 주입 테스트
curl -i "http://localhost/index.php?%add+allow_url_include%3don"

# 2) 대문자 소프트 하이픈(%AD) 주입 테스트
curl -i "http://localhost/index.php?%ADd+allow_url_include%3don"

# 3) 직접 인자 플래그(-d) 주입 테스트
curl -i "http://localhost/index.php?-d+allow_url_include=1"

진단 스크립트 (PowerShell)

  • 접속 확인
# 실제 환경에 맞게 주소 변경 (포트나 HTTPS 여부 확인)
$baseUrl = "http://localhost/index.php" 

$tests = @(
    @{ Name = "정상 요청"; Query = "?page=1" },
    @{ Name = "소프트 하이픈(%ad)"; Query = "?%add+allow_url_include=1" },
    @{ Name = "대문자 하이픈(%AD)"; Query = "?%ADd+auto_prepend_file=php://input" },
    @{ Name = "직접 인자 주입(-d)"; Query = "?-d+expose_php=0" }
)

foreach ($test in $tests) {
    $targetUrl = $baseUrl + $test.Query
    try {
        $response = Invoke-WebRequest -Uri $targetUrl -Method Head -UseBasicParsing -TimeoutSec 5
        Write-Host "[$($response.StatusCode)] $($test.Name) - 통과 (200 OK)" -ForegroundColor Yellow
    } catch {
        if ($_.Exception.Response) {
            $statusCode = [int]$_.Exception.Response.StatusCode
            if ($statusCode -eq 403) {
                Write-Host "[$statusCode] $($test.Name) - 정상 차단됨 (보안 룰 작동)" -ForegroundColor Green
            } else {
                Write-Host "[$statusCode] $($test.Name) - HTTP 오류 발생" -ForegroundColor DarkYellow
            }
        } else {
            Write-Host "[연결 실패] $($test.Name) - 서버에 도달하지 못함: $($_.Exception.Message)" -ForegroundColor Red
        }
    }
}

PowerShell 보안 오류 발생시

  • 1회성 Bypass 실행 (가장 권장)
powershell -ExecutionPolicy Bypass -File .\[파일명]
  • 현재 열려 있는 Powershell창에서만
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force
.\[파일명]
  • 시스템 전역 허용 (관리자 권한)
Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy RemoteSigned -Force
.\[파일명]

※ 본 글은 직접 작성하였으며, 점검 절차 및 PowerShell 소스는 제미나이(Gemini) 3.8 Flash의 도움을 받았습니다.

반응형