From 846c68ee6207cfd8930307d400949cced87e251b Mon Sep 17 00:00:00 2001 From: Simon Bennetts Date: Fri, 13 Sep 2024 14:32:50 +0100 Subject: [PATCH] Add extender/ScanMonitor script Signed-off-by: Simon Bennetts --- CHANGELOG.md | 1 + build.gradle.kts | 2 +- extender/ScanMonitor.js | 88 +++++++++++++++++++++++++++++++++++++++++ other/CHANGELOG.md | 3 ++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 extender/ScanMonitor.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 6465774a..6603c230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Standalone script 'PrivateMethodAccess.js' - Variant script 'AddUrlParams.js' +- Extender script 'ScanMonitor.js' ### Changed - Add cautionary note to help and readme. ### Fixed diff --git a/build.gradle.kts b/build.gradle.kts index 61d202c9..b2ac284c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -125,7 +125,7 @@ spotless { } javascript { target("**/*.js") - targetExclude("extender/HTTP Message Logger.js", "standalone/domainFinder.js") + targetExclude("extender/HTTP Message Logger.js", "extender/ScanMonitor.js", "standalone/domainFinder.js") // get the npm executable path from gradle-node-plugin val npmDir = (tasks.named("npmSetup").get().property("npmDir") as TransformBackedProvider<*, *>).get().toString() val npmExecutable = if (System.getProperty("os.name").lowercase().contains("windows")) "/npm.cmd" else "/bin/npm" diff --git a/extender/ScanMonitor.js b/extender/ScanMonitor.js new file mode 100644 index 00000000..8520085e --- /dev/null +++ b/extender/ScanMonitor.js @@ -0,0 +1,88 @@ +// This script monitors the active scanner and ends the scan if certain conditions are met. +// By default it just ends the scan for high: +// * Connection failures +// * Authentication failures +// * Response times +// You can easily chane the script to end the scan for other conditions, such as high 4xx / 5xx response codes, +// but these tend to be application specific so they are not enabled by default. + +var SessionStructure = Java.type("org.zaproxy.zap.model.SessionStructure"); +var Timer = Java.type("java.util.Timer"); +var TimerTask = Java.type("java.util.TimerTask"); +var URI = Java.type("org.apache.commons.httpclient.URI"); + +var extAscan = control.getExtensionLoader().getExtension("ExtensionActiveScan"); +var inMemoryStats = control + .getExtensionLoader() + .getExtension("ExtensionStats") + .getInMemoryStats(); + +var timer = new Timer(); +var timerSecs = 10 * 1000; // Check every 10 secs + +// Set to true to see the stats reported live +var log = false; + +function install(helper) { + timer.scheduleAtFixedRate( + new TimerTask() { + run: function () { + runchecks(); + }, + }, + 0, + timerSecs + ); +} + +function getStat(site, stat) { + var val = + site == null + ? inMemoryStats.getStat(stat) + : inMemoryStats.getStat(site, stat); + return val == null ? 0 : val; +} + +// Response times are recorded in logarithmic millisec time slices +function getLongRespStats(site) { + return ( + getStat(site, "stats.responseTime.16384") + + getStat(site, "stats.responseTime.32768") + + getStat(site, "stats.responseTime.65536") + ); +} + +function runchecks() { + if (log) print("Running checks.."); + ascans = extAscan.getActiveScans(); + ascans.forEach((as, i) => { + // For the full set of stats that can be monitored see https://www.zaproxy.org/docs/internal-statistics/ + var site = SessionStructure.getHostName(new URI(as.getDisplayName(), true)); + if (log) print("Site: " + site); + // Connection failures are global rather than site specific + var connFails = getStat(null, "stats.network.send.failure"); + // All HTTP response codes are recorded, so you can add checks for 401, 403 etc etc + var stats401 = getStat(site, "stats.code.401"); + var stats500 = getStat(site, "stats.code.500"); + // Auth fails are only relevant for authenticated scans + var authFails = getStat(site, "stats.auth.failure"); + var longResp = getLongRespStats(site); + + if (log) { + print(" 401 resps:\t" + stats401); + print(" 500 resps:\t" + stats500); + print(" conn fails:\t" + connFails); + print(" auth fails:\t" + authFails); + print(" long resps:\t" + longResp); + } + // Change this test to meet your requirements as needed. + if (connFails > 1000 || authFails > 1000 || longResp > 1000) { + if (log) print("Stopping ascan " + site); + as.stopScan(); + } + }); +} + +function uninstall(helper) { + timer.cancel(); +} diff --git a/other/CHANGELOG.md b/other/CHANGELOG.md index 27e729e4..d70821d9 100644 --- a/other/CHANGELOG.md +++ b/other/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to the 'other' section of this repository will be documented The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +### 2024-08-30 +- Added af-plans/ApiScanExample.yaml + ### 2024-02-06 - Added af-plans/FullScanBrokenCrystals.yaml - Added af-plans/ScriptEnvVarAccess.yaml