Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extender/ScanMonitor script #468

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
88 changes: 88 additions & 0 deletions extender/ScanMonitor.js
Original file line number Diff line number Diff line change
@@ -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
kingthorin marked this conversation as resolved.
Show resolved Hide resolved

// 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") +
kingthorin marked this conversation as resolved.
Show resolved Hide resolved
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);
kingthorin marked this conversation as resolved.
Show resolved Hide resolved
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();
}
3 changes: 3 additions & 0 deletions other/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I should have included this in a prev PR :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was :)


### 2024-02-06
- Added af-plans/FullScanBrokenCrystals.yaml
- Added af-plans/ScriptEnvVarAccess.yaml
Expand Down