a8d635ef49
Скрипт bin/zap-active-scan.ps1 лежит в gitignored bin/, форс-добавлен для воспроизводимости: отчёт docs/security/2026-06-18-zap-active-scan-report.md ссылается на него. Демон через вшитую java bin/_runtimes/jdk-17, jar относительным именем, ASCII-only под PowerShell 5.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
127 lines
6.1 KiB
PowerShell
127 lines
6.1 KiB
PowerShell
#requires -Version 5
|
|
<#
|
|
.SYNOPSIS
|
|
ZAP active scan (full DAST) against the LOCAL portal copy. Guard IS8 - NOT against prod.
|
|
|
|
.DESCRIPTION
|
|
Self-contained orchestrator for tool #68 (security-go-live step 4.2):
|
|
1. Starts "php artisan serve" on 127.0.0.1:8000 if not already up.
|
|
2. Starts the ZAP daemon (zap-2.17.0.jar) on :8090, API key disabled (local only).
|
|
3. Spiders the target URL, polls to 100%.
|
|
4. Active scan (full DAST), polls to 100%.
|
|
5. Exports JSON + HTML report into docs/security/.
|
|
6. Prints an alerts summary by risk level.
|
|
7. Shuts down ZAP and (if it started one) the serve process.
|
|
|
|
Target is hardcoded to 127.0.0.1:8000 - no public addresses (IS8).
|
|
ASCII-only on purpose: Windows PowerShell 5.1 mangles non-ASCII in UTF-8 (no BOM) scripts.
|
|
Java is the bundled JRE under bin/_runtimes/ (NOT on PATH), launched by full path so the
|
|
script does not depend on the caller's PATH. ZAP jar is launched directly (bypasses zap.bat).
|
|
#>
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$repo = Split-Path -Parent $PSScriptRoot # bin/ -> repo root
|
|
$target = 'http://127.0.0.1:8000'
|
|
$zapPort = 8090
|
|
$zapBase = "http://127.0.0.1:$zapPort"
|
|
$stamp = '2026-06-18'
|
|
$reportDir = Join-Path $repo 'docs/security'
|
|
$jsonReport = Join-Path $reportDir "$stamp-zap-active-scan.json"
|
|
$htmlReport = Join-Path $reportDir "$stamp-zap-active-scan.html"
|
|
$zapDir = Join-Path $repo 'bin/ZAP_2.17.0'
|
|
# relative jar name on purpose: the absolute repo path contains spaces ("портал crm"),
|
|
# and Start-Process -ArgumentList does not quote a space-containing arg, so java would
|
|
# get a truncated path. WorkingDirectory=$zapDir resolves the relative name (как zap.bat).
|
|
$zapJar = 'zap-2.17.0.jar'
|
|
$zapOut = Join-Path $env:TEMP 'zap-daemon.out.log'
|
|
$zapErr = Join-Path $env:TEMP 'zap-daemon.err.log'
|
|
|
|
# Resolve Java: bundled JRE first (not on PATH), then any bundled, then PATH.
|
|
$javaExe = Join-Path $repo 'bin/_runtimes/jdk-17.0.19+10-jre/bin/java.exe'
|
|
if (-not (Test-Path $javaExe)) {
|
|
$bundled = Get-ChildItem (Join-Path $repo 'bin/_runtimes') -Recurse -Filter java.exe -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($bundled) { $javaExe = $bundled.FullName }
|
|
elseif (Get-Command java -ErrorAction SilentlyContinue) { $javaExe = 'java' }
|
|
else { throw "java.exe not found (bundled JRE missing under bin/_runtimes and not on PATH)" }
|
|
}
|
|
|
|
function Test-Up([string]$url) {
|
|
try { Invoke-WebRequest -UseBasicParsing -TimeoutSec 3 -Uri $url | Out-Null; return $true }
|
|
catch { return $false }
|
|
}
|
|
|
|
if (-not (Test-Path $reportDir)) { New-Item -ItemType Directory -Path $reportDir | Out-Null }
|
|
|
|
# --- 1. Local app on :8000 ---
|
|
$serveProc = $null
|
|
if (-not (Test-Up $target)) {
|
|
Write-Host "[zap] starting php artisan serve ..."
|
|
$serveProc = Start-Process -PassThru -WindowStyle Hidden `
|
|
-WorkingDirectory (Join-Path $repo 'app') `
|
|
-FilePath 'php' -ArgumentList 'artisan','serve','--host=127.0.0.1','--port=8000'
|
|
for ($i = 0; $i -lt 30 -and -not (Test-Up $target); $i++) { Start-Sleep -Seconds 1 }
|
|
if (-not (Test-Up $target)) { throw "App did not come up at $target" }
|
|
}
|
|
Write-Host "[zap] target up: $target"
|
|
|
|
# --- 2. ZAP daemon (java launched by full path; WorkingDirectory = ZAP dir) ---
|
|
Write-Host "[zap] starting ZAP daemon on :$zapPort (java: $javaExe) ..."
|
|
$zapProc = Start-Process -PassThru -WindowStyle Hidden -FilePath $javaExe `
|
|
-WorkingDirectory $zapDir `
|
|
-RedirectStandardOutput $zapOut -RedirectStandardError $zapErr `
|
|
-ArgumentList @(
|
|
'-Xmx512m','-jar',$zapJar,
|
|
'-daemon','-port',"$zapPort",'-silent',
|
|
'-config','api.disablekey=true',
|
|
'-config','api.addrs.addr.name=.*',
|
|
'-config','api.addrs.addr.regex=true'
|
|
)
|
|
for ($i = 0; $i -lt 150 -and -not (Test-Up "$zapBase/JSON/core/view/version/"); $i++) { Start-Sleep -Seconds 2 }
|
|
if (-not (Test-Up "$zapBase/JSON/core/view/version/")) {
|
|
if ($zapProc -and -not $zapProc.HasExited) { Stop-Process -Id $zapProc.Id -Force -ErrorAction SilentlyContinue }
|
|
throw "ZAP daemon did not come up at $zapBase (see $zapErr / $zapOut)"
|
|
}
|
|
$ver = (Invoke-RestMethod "$zapBase/JSON/core/view/version/").version
|
|
Write-Host "[zap] ZAP up: $ver"
|
|
|
|
try {
|
|
# --- 3. Seed proxy with target + Spider ---
|
|
Invoke-RestMethod "$zapBase/JSON/core/action/accessUrl/?url=$target" | Out-Null
|
|
$spiderId = (Invoke-RestMethod "$zapBase/JSON/spider/action/scan/?url=$target&recurse=true").scan
|
|
Write-Host "[zap] spider id=$spiderId"
|
|
do {
|
|
Start-Sleep -Seconds 2
|
|
$st = (Invoke-RestMethod "$zapBase/JSON/spider/view/status/?scanId=$spiderId").status
|
|
} while ([int]$st -lt 100)
|
|
Write-Host "[zap] spider 100%"
|
|
|
|
# let the passive scanner drain its queue
|
|
do { Start-Sleep -Seconds 1; $rec = (Invoke-RestMethod "$zapBase/JSON/pscan/view/recordsToScan/").recordsToScan } while ([int]$rec -gt 0)
|
|
|
|
# --- 4. Active scan ---
|
|
$ascanId = (Invoke-RestMethod "$zapBase/JSON/ascan/action/scan/?url=$target&recurse=true&inScopeOnly=false").scan
|
|
Write-Host "[zap] active scan id=$ascanId"
|
|
do {
|
|
Start-Sleep -Seconds 5
|
|
$as = (Invoke-RestMethod "$zapBase/JSON/ascan/view/status/?scanId=$ascanId").status
|
|
Write-Host "[zap] active scan $as%"
|
|
} while ([int]$as -lt 100)
|
|
Write-Host "[zap] active scan 100%"
|
|
|
|
# --- 5. Reports ---
|
|
Invoke-RestMethod "$zapBase/OTHER/core/other/jsonreport/" -OutFile $jsonReport
|
|
Invoke-RestMethod "$zapBase/OTHER/core/other/htmlreport/" -OutFile $htmlReport
|
|
Write-Host "[zap] report saved: $jsonReport"
|
|
|
|
# --- 6. Summary by risk ---
|
|
$sum = (Invoke-RestMethod "$zapBase/JSON/alert/view/alertsSummary/?baseurl=$target").alertsSummary
|
|
Write-Host "[zap] ALERTS SUMMARY: $($sum | ConvertTo-Json -Compress)"
|
|
}
|
|
finally {
|
|
# --- 7. Shut down ZAP and serve ---
|
|
try { Invoke-RestMethod "$zapBase/JSON/core/action/shutdown/" | Out-Null } catch {}
|
|
if ($zapProc -and -not $zapProc.HasExited) { Stop-Process -Id $zapProc.Id -Force -ErrorAction SilentlyContinue }
|
|
if ($serveProc) { Stop-Process -Id $serveProc.Id -Force -ErrorAction SilentlyContinue }
|
|
Write-Host "[zap] done"
|
|
}
|