Language/PHP

PHP, Windows, IIS) PHP(PHP2Shell)관련 로그(Log) 분석 스크립트 (PowerShell 스크립트 포함)

소소한 늙은 개발자의 메모장 2026. 10. 23. 21:07
반응형

PHP, Windows, IIS) PHP(PHP2Shell)관련 로그(Log) 분석 스크립트 (PowerShell 스크립트 포함)

방어 방법

이전글 참조 : https://userpark.net/175


이전 기록을 확인하기 위한 PowerShell 스크립트

윈도우 서버 기준 경로가 동일할것 같으나, 필요시 경로 변경

  • Root이하 Sub폴더 전체 검색
  • 옵션
    • DaysBack : 최근 기준 보여줄 이전 일수
    • ShowAll : 기록을 화면에 다 보여 줄지 여부?
    • MaxDisplayCount : ShowAll이 아닌 경우 보여줄 최대 개수
# ==============================================================================
# IIS 로그 정밀 분석 스크립트 (전수 출력 / 요약 출력 선택 옵션 버전)
# ==============================================================================

$RootLogDir = "C:\inetpub\logs\LogFiles"

if (-not (Test-Path $RootLogDir)) {
    Write-Warning "[-] 로그 디렉터리를 찾을 수 없습니다: $RootLogDir"
    return
}

# ------------------------------------------------------------------------------
# [사용자 설정 옵션]
# 1) 날짜 조건: 최근 N일
$DaysBack = 3
$StartDate = (Get-Date).Date.AddDays(-$DaysBack)

# 2) 전체 출력 여부 설정:
#    $true  -> 탐지된 모든 건수를 생략 없이 100% 화면에 출력
#    $false -> 파일당 최근 $MaxDisplayCount 건수만큼만 요약 출력
$ShowAll = $true
$MaxDisplayCount = 15
# ------------------------------------------------------------------------------

# 1. 검사 대상 URI 패턴: 루트(/), 서브디렉터리(/.../), 또는 .php 파일만 대상
$TargetUriPattern = "(^\/$|^.*\/$|\.php$)"

# 2. 정밀 공격 쿼리 패턴 (도면번호/한글 오탐 완벽 제외)
$AttackQueryPattern = "(%[aA][dD][dswDSW\+]|(^|&|\+|%20)-[dswDSW](=|\+|%20|$)|allow_url_include|auto_prepend_file|php:\/\/|expose_php)"

$modeText = if ($ShowAll) { "전체 출력 모드" } else { "요약 출력 모드 (파일당 최근 $MaxDisplayCount 건)" }
Write-Host "[+] 검색 시작 날짜: $($StartDate.ToString('yyyy-MM-dd 00:00:00')) 이후" -ForegroundColor Cyan
Write-Host "[+] 출력 설정 상태: $modeText" -ForegroundColor Cyan
Write-Host "[+] 대상 디렉터리 : $RootLogDir" -ForegroundColor Cyan

$targetFiles = Get-ChildItem -Path $RootLogDir -Filter "*.log" -Recurse -File | 
               Where-Object { $_.LastWriteTime -ge $StartDate } | 
               Sort-Object LastWriteTime -Descending

if ($targetFiles.Count -eq 0) {
    Write-Warning "[-] 해당 기간 내에 수정된 로그 파일이 없습니다."
    return
}

$grandTotal = 0

foreach ($file in $targetFiles) {
    $siteFolderName = $file.Directory.Name
    $relativeName = "$siteFolderName\$($file.Name)"
    
    $lines = Get-Content -Path $file.FullName
    if (-not $lines) { continue }

    # #Fields 헤더 동적 매핑
    $fieldLine = $lines | Where-Object { $_ -like "#Fields:*" } | Select-Object -First 1
    if (-not $fieldLine) { continue }

    $headers = ($fieldLine -replace "^#Fields:\s*", "") -split "\s+"
    
    $idxDate      = [array]::IndexOf($headers, "date")
    $idxTime      = [array]::IndexOf($headers, "time")
    $idxIP        = [array]::IndexOf($headers, "c-ip")
    $idxMethod    = [array]::IndexOf($headers, "cs-method")
    $idxURI       = [array]::IndexOf($headers, "cs-uri-stem")
    $idxQuery     = [array]::IndexOf($headers, "cs-uri-query")
    $idxStatus    = [array]::IndexOf($headers, "sc-status")
    $idxSubStatus = [array]::IndexOf($headers, "sc-substatus")

    if ($idxDate -eq -1 -or $idxStatus -eq -1 -or $idxQuery -eq -1 -or $idxURI -eq -1) { continue }

    $validMatches = @()

    foreach ($line in $lines) {
        if ($line.StartsWith("#")) { continue }

        $cols = $line -split "\s+"
        if ($cols.Length -le $idxStatus) { continue }

        $rawURI   = $cols[$idxURI]
        $rawQuery = $cols[$idxQuery]

        if ($rawQuery -eq "-" -or ($rawURI -notmatch $TargetUriPattern)) { continue }

        if ($rawQuery -match $AttackQueryPattern) {
            $logDateTimeStr = "$($cols[$idxDate]) $($cols[$idxTime])"
            $logDate = [datetime]::MinValue

            if ([datetime]::TryParse($logDateTimeStr, [ref]$logDate)) {
                if ($logDate -ge $StartDate) {
                    $statusCode = $cols[$idxStatus]
                    $subStatusCode = if ($idxSubStatus -ne -1) { $cols[$idxSubStatus] } else { "0" }
                    
                    $shortQuery = if ($rawQuery.Length -gt 45) { $rawQuery.Substring(0, 42) + "..." } else { $rawQuery }

                    $validMatches += [PSCustomObject]@{
                        Time      = $logDateTimeStr
                        Status    = "$statusCode.$subStatusCode"
                        Method    = if ($idxMethod -ne -1) { $cols[$idxMethod] } else { "-" }
                        ClientIP  = if ($idxIP -ne -1) { $cols[$idxIP] } else { "-" }
                        URI       = $rawURI
                        Query     = $shortQuery
                    }
                }
            }
        }
    }

    if ($validMatches.Count -gt 0) {
        $grandTotal += $validMatches.Count
        Write-Host "----------------------------------------------------------------------------------------------------" -ForegroundColor DarkGray
        
        if ($ShowAll -or ($validMatches.Count -le $MaxDisplayCount)) {
            Write-Host "[!] 실제 공격 탐지: $relativeName (기간 내 총 $($validMatches.Count)건 - 전체 표시)" -ForegroundColor Red
            $validMatches | Format-Table -Property Time, Status, Method, ClientIP, URI, Query -AutoSize
        } else {
            Write-Host "[!] 실제 공격 탐지: $relativeName (기간 내 총 $($validMatches.Count)건 중 최근 $MaxDisplayCount 건만 표시)" -ForegroundColor Red
            $validMatches | Select-Object -Last $MaxDisplayCount | 
                Format-Table -Property Time, Status, Method, ClientIP, URI, Query -AutoSize
        }
    }
}

Write-Host "====================================================================================================" -ForegroundColor Cyan
if ($grandTotal -eq 0) {
    Write-Host "[결과] 설정된 기간 내 탐지된 공격 시도가 없습니다." -ForegroundColor Green
} else {
    Write-Host "[결과] 실제 공격 시도 총 $grandTotal 건이 확인되었습니다. ($modeText)" -ForegroundColor Yellow
}

 

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

반응형