#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" }