From b83d0a4ef59981ee764ab7db515f285c550bb627 Mon Sep 17 00:00:00 2001 From: thc202 Date: Wed, 4 Dec 2024 07:13:00 +0000 Subject: [PATCH] Move/add passive scanner and use add-on extension Move the passive scanner to the `pscan` add-on along with required classes. Change all other add-ons to use the extension from the add-on. Part of zaproxy/zaproxy#7959. Signed-off-by: thc202 --- addOns/alertFilters/CHANGELOG.md | 1 + addOns/alertFilters/alertFilters.gradle.kts | 8 + .../alertFilters/ExtensionAlertFilters.java | 4 +- .../alertFilters/internal/ScanRulesInfo.java | 7 +- addOns/authhelper/CHANGELOG.md | 3 +- addOns/authhelper/authhelper.gradle.kts | 4 + .../addon/authhelper/AuthTestDialog.java | 6 +- .../addon/authhelper/ExtensionAuthhelper.java | 4 +- addOns/pscan/CHANGELOG.md | 7 +- .../addon/pscan/ExtensionPassiveScan2.java | 354 ++++++++++++---- .../zaproxy/addon/pscan/PassiveScanApi.java | 81 ++-- .../addon/pscan/PassiveScannersManager.java | 72 ++++ .../internal/PscanRulesTableModel.java | 12 +- .../automation/jobs/PassiveScanConfigJob.java | 50 +-- .../automation/jobs/PassiveScanWaitJob.java | 27 +- .../pscan/internal/AddOnScanRulesLoader.java | 11 +- .../pscan/internal/PassiveScannerOptions.java | 325 ++++++++++++++ .../pscan/internal/RegexAutoTagScanner.java | 341 +++++++++++++++ .../addon/pscan/internal/ScanRuleManager.java | 23 +- .../scanner/PassiveScanController.java | 302 +++++++++++++ .../internal/scanner/PassiveScanTask.java | 231 ++++++++++ .../scanner/PassiveScanTaskHelper.java | 233 ++++++++++ .../internal/ui/DialogAddAutoTagScanner.java | 2 +- .../ui/DialogModifyAutoTagScanner.java | 2 +- .../pscan/internal/ui/OptionsPassiveScan.java | 20 +- .../ui/OptionsPassiveScanTableModel.java | 2 +- .../ui/PassiveScannerOptionsPanel.java | 11 +- .../addon/pscan/PassiveScanApiUnitTest.java | 12 +- .../jobs/PassiveScanConfigJobUnitTest.java | 180 ++++---- .../jobs/PassiveScanWaitJobUnitTest.java | 91 +--- .../DefaultRegexAutoTagScannerTest.java | 191 +++++++++ .../PassiveScannerOptionsUnitTest.java | 84 ++++ .../internal/RegexAutoTagScannerUnitTest.java | 114 +++++ .../internal/ScanRuleManagerUnitTest.java | 202 +++++++++ .../PassiveScanControllerUnitTest.java | 397 ++++++++++++++++++ addOns/quickstart/CHANGELOG.md | 3 + addOns/quickstart/quickstart.gradle.kts | 4 + .../quickstart/ExtensionQuickStart.java | 4 +- .../zap/extension/quickstart/ZapItScan.java | 6 +- .../ajaxspider/AjaxSpiderExplorer.java | 10 +- .../ExtensionQuickStartAjaxSpider.java | 4 +- addOns/retest/CHANGELOG.md | 1 + addOns/retest/retest.gradle.kts | 4 +- .../retest/RetestPlanGeneratorUnitTest.java | 16 + addOns/scripts/CHANGELOG.md | 4 + addOns/scripts/scripts.gradle.kts | 4 + .../extension/scripts/ExtensionScriptsUI.java | 8 +- .../ScriptAutoCompleteKeyListener.java | 6 +- .../scanrules/PassiveScriptSynchronizer.java | 12 +- .../scanrules/ScriptSynchronizerUtils.java | 10 +- .../scanrules/ScriptsPassiveScanner.java | 4 +- .../ActiveScriptSynchronizerUnitTest.java | 11 +- .../PassiveScriptSynchronizerUnitTest.java | 24 +- .../ScriptsPassiveScannerUnitTest.java | 4 +- addOns/wappalyzer/CHANGELOG.md | 3 +- .../wappalyzer/ExtensionWappalyzer.java | 22 +- addOns/wappalyzer/wappalyzer.gradle.kts | 4 + addOns/zest/CHANGELOG.md | 1 + .../zap/extension/zest/ExtensionZest.java | 6 +- .../zap/extension/zest/ZestScriptWrapper.java | 4 +- .../zest/ZestTreeTransferHandler.java | 4 +- addOns/zest/zest.gradle.kts | 4 + 62 files changed, 3130 insertions(+), 471 deletions(-) create mode 100644 addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScannersManager.java create mode 100644 addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptions.java create mode 100644 addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScanner.java create mode 100644 addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanController.java create mode 100644 addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTask.java create mode 100644 addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTaskHelper.java create mode 100644 addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/DefaultRegexAutoTagScannerTest.java create mode 100644 addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptionsUnitTest.java create mode 100644 addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScannerUnitTest.java create mode 100644 addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/ScanRuleManagerUnitTest.java create mode 100644 addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanControllerUnitTest.java diff --git a/addOns/alertFilters/CHANGELOG.md b/addOns/alertFilters/CHANGELOG.md index ed306538f56..fa8ec99868d 100644 --- a/addOns/alertFilters/CHANGELOG.md +++ b/addOns/alertFilters/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased ### Changed - Fields with default or missing values are omitted for the `alertFilter` job in saved Automation Framework plans. +- Depend on Passive Scanner add-on (Issue 7959). ## [22] - 2024-10-07 ### Fixed diff --git a/addOns/alertFilters/alertFilters.gradle.kts b/addOns/alertFilters/alertFilters.gradle.kts index 2a2f25a4af8..bfe8b470fbc 100644 --- a/addOns/alertFilters/alertFilters.gradle.kts +++ b/addOns/alertFilters/alertFilters.gradle.kts @@ -9,6 +9,13 @@ zapAddOn { manifest { author.set("ZAP Dev Team") url.set("https://www.zaproxy.org/docs/desktop/addons/alert-filters/") + dependencies { + addOns { + register("pscan") { + version.set(">= 0.1.0 & < 1.0.0") + } + } + } extensions { register("org.zaproxy.zap.extension.alertFilters.automation.ExtensionAlertFiltersAutomation") { classnames { @@ -35,6 +42,7 @@ zapAddOn { dependencies { zapAddOn("automation") zapAddOn("commonlib") + zapAddOn("pscan") testImplementation(project(":testutils")) } diff --git a/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/ExtensionAlertFilters.java b/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/ExtensionAlertFilters.java index 8bb4ef8d95a..4016480011c 100644 --- a/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/ExtensionAlertFilters.java +++ b/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/ExtensionAlertFilters.java @@ -44,6 +44,7 @@ import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.Session.OnContextsChangedListener; import org.parosproxy.paros.view.View; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.ZAP; import org.zaproxy.zap.eventBus.Event; import org.zaproxy.zap.eventBus.EventConsumer; @@ -52,7 +53,6 @@ import org.zaproxy.zap.extension.alert.PopupMenuItemAlert; import org.zaproxy.zap.extension.alertFilters.internal.ScanRulesInfo; import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.model.Context; import org.zaproxy.zap.model.ContextDataFactory; import org.zaproxy.zap.model.SessionStructure; @@ -140,7 +140,7 @@ public static ScanRulesInfo getScanRulesInfo() { getExtAscan(), Control.getSingleton() .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class)); + .getExtension(ExtensionPassiveScan2.class)); } return scanRulesInfo; } diff --git a/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/internal/ScanRulesInfo.java b/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/internal/ScanRulesInfo.java index dd438336d1e..dbc69f42e1b 100644 --- a/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/internal/ScanRulesInfo.java +++ b/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/internal/ScanRulesInfo.java @@ -34,10 +34,10 @@ import org.parosproxy.paros.core.scanner.Plugin; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpRequestHeader; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.alert.ExampleAlertProvider; import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; import org.zaproxy.zap.extension.ascan.ScanPolicy; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.pscan.PassiveScanData; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; @@ -49,7 +49,7 @@ public class ScanRulesInfo extends AbstractList { private Map entriesById; public ScanRulesInfo( - ExtensionActiveScan extensionActiveScan, ExtensionPassiveScan extensionPassiveScan) { + ExtensionActiveScan extensionActiveScan, ExtensionPassiveScan2 extensionPassiveScan) { entries = new ArrayList<>(); entriesById = new HashMap<>(); ScanPolicy sp = extensionActiveScan.getPolicyManager().getDefaultScanPolicy(); @@ -57,7 +57,8 @@ public ScanRulesInfo( addEntry(scanRule, scanRule.getId(), scanRule.getName()); } if (extensionPassiveScan != null) { - for (PluginPassiveScanner scanRule : extensionPassiveScan.getPluginPassiveScanners()) { + for (PluginPassiveScanner scanRule : + extensionPassiveScan.getPassiveScannersManager().getScanRules()) { addEntry(scanRule, scanRule.getPluginId(), scanRule.getName()); } } diff --git a/addOns/authhelper/CHANGELOG.md b/addOns/authhelper/CHANGELOG.md index 897bfce109f..e5bc18f58d7 100644 --- a/addOns/authhelper/CHANGELOG.md +++ b/addOns/authhelper/CHANGELOG.md @@ -4,7 +4,8 @@ All notable changes to this add-on will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - +### Changed +- Depend on Passive Scanner add-on (Issue 7959). ## [0.16.0] - 2024-11-06 ### Fixed diff --git a/addOns/authhelper/authhelper.gradle.kts b/addOns/authhelper/authhelper.gradle.kts index af0c65016c0..467a7ba9f0f 100644 --- a/addOns/authhelper/authhelper.gradle.kts +++ b/addOns/authhelper/authhelper.gradle.kts @@ -31,6 +31,9 @@ zapAddOn { register("network") { version.set(">=0.6.0") } + register("pscan") { + version.set(">= 0.1.0 & < 1.0.0") + } register("selenium") { version.set("15.*") } @@ -50,6 +53,7 @@ crowdin { dependencies { zapAddOn("commonlib") zapAddOn("network") + zapAddOn("pscan") zapAddOn("selenium") zapAddOn("spiderAjax") diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthTestDialog.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthTestDialog.java index 0bdb7331795..ea694a73b3f 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthTestDialog.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthTestDialog.java @@ -43,12 +43,12 @@ import org.parosproxy.paros.view.View; import org.zaproxy.addon.authhelper.AutoDetectSessionManagementMethodType.AutoDetectSessionManagementMethod; import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.ZAP; import org.zaproxy.zap.authentication.AuthenticationMethod; import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; import org.zaproxy.zap.authentication.AuthenticationMethodType; import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.selenium.BrowserUI; import org.zaproxy.zap.extension.selenium.BrowsersComboBoxModel; import org.zaproxy.zap.extension.selenium.ExtensionSelenium; @@ -344,10 +344,10 @@ public void counterInc(String site, String key) { } } context = session.getContext(contextName); - ExtensionPassiveScan extPscan = + ExtensionPassiveScan2 extPscan = Control.getSingleton() .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class); + .getExtension(ExtensionPassiveScan2.class); int count = 0; int score = 0; diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java index 96d55d188fd..cfa49e4a411 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java @@ -43,12 +43,12 @@ import org.parosproxy.paros.model.Session; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.view.View; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.authentication.FormBasedAuthenticationMethodType; import org.zaproxy.zap.authentication.JsonBasedAuthenticationMethodType; import org.zaproxy.zap.authentication.PostBasedAuthenticationMethodType; import org.zaproxy.zap.authentication.PostBasedAuthenticationMethodType.PostBasedAuthenticationMethod; import org.zaproxy.zap.extension.authentication.ExtensionAuthentication; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.selenium.ExtensionSelenium; import org.zaproxy.zap.extension.sessions.ExtensionSessionManagement; import org.zaproxy.zap.extension.users.ExtensionUserManagement; @@ -66,7 +66,7 @@ public class ExtensionAuthhelper extends ExtensionAdaptor implements SessionChan private static final List> EXTENSION_DEPENDENCIES = List.of( - ExtensionPassiveScan.class, + ExtensionPassiveScan2.class, ExtensionSelenium.class, ExtensionUserManagement.class); diff --git a/addOns/pscan/CHANGELOG.md b/addOns/pscan/CHANGELOG.md index 918527ec3b2..f7daab46482 100644 --- a/addOns/pscan/CHANGELOG.md +++ b/addOns/pscan/CHANGELOG.md @@ -7,13 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added - Manage the passive scan related options and the scan rules (Issue 7959). +- Add passive scanner (Issue 7959). ### Changed - Fields with default or missing values are omitted for the following automation jobs in saved Automation Framework plans: - `passiveScan-config` - `passiveScan-wait` + ### Fixed -- Fixed passiveScan-wait alert tests. +- Fixed `passiveScan-wait` alert tests. ## [0.0.1] - 2024-09-02 ### Added @@ -21,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Provide the Stats Passive Scan Rule (Issue 7959). - Provide the scan status label (Issue 7959). - Provide the `pscan` API on newer ZAP versions (Issue 7959). +- Provide the Automation Framework passive scan jobs: + - `passiveScan-config` + - `passiveScan-wait` - Dynamically un/load add-on passive scan rules (Issue 7959). [0.0.1]: https://github.com/zaproxy/zap-extensions/releases/pscan-v0.0.1 diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/ExtensionPassiveScan2.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/ExtensionPassiveScan2.java index b6323c4b70e..0d97813f2e3 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/ExtensionPassiveScan2.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/ExtensionPassiveScan2.java @@ -28,17 +28,29 @@ import org.apache.logging.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; +import org.parosproxy.paros.control.Control.Mode; +import org.parosproxy.paros.core.proxy.ProxyListener; import org.parosproxy.paros.extension.Extension; import org.parosproxy.paros.extension.ExtensionAdaptor; import org.parosproxy.paros.extension.ExtensionHook; +import org.parosproxy.paros.extension.ExtensionLoader; +import org.parosproxy.paros.extension.SessionChangedListener; +import org.parosproxy.paros.extension.history.ExtensionHistory; +import org.parosproxy.paros.extension.history.ProxyListenerLog; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.model.Session; +import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.addon.pscan.internal.AddOnScanRulesLoader; import org.zaproxy.addon.pscan.internal.DefaultStatsListener; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; import org.zaproxy.addon.pscan.internal.ScanRuleManager; import org.zaproxy.addon.pscan.internal.StatsPassiveScanner; +import org.zaproxy.addon.pscan.internal.scanner.PassiveScanController; +import org.zaproxy.addon.pscan.internal.scanner.PassiveScanTask; import org.zaproxy.addon.pscan.internal.ui.OptionsPassiveScan; import org.zaproxy.addon.pscan.internal.ui.PassiveScannerOptionsPanel; import org.zaproxy.addon.pscan.internal.ui.PolicyPassiveScanPanel; -import org.zaproxy.zap.extension.pscan.PassiveScanParam; +import org.zaproxy.zap.extension.alert.ExtensionAlert; import org.zaproxy.zap.extension.pscan.PassiveScanner; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; import org.zaproxy.zap.extension.script.ExtensionScript; @@ -52,12 +64,18 @@ public class ExtensionPassiveScan2 extends ExtensionAdaptor { public static final String NAME = "ExtensionPassiveScan2"; + // Should be after the last one that saves the HttpMessage, as this ProxyListener doesn't change + // the HttpMessage. + public static final int PROXY_LISTENER_ORDER = ProxyListenerLog.PROXY_LISTENER_ORDER + 1; + public static final String SCRIPT_TYPE_PASSIVE = "passive"; private static final Logger LOGGER = LogManager.getLogger(ExtensionPassiveScan2.class); private static final List> DEPENDENCIES = - List.of(org.zaproxy.zap.extension.pscan.ExtensionPassiveScan.class); + List.of( + ExtensionAlert.class, + org.zaproxy.zap.extension.pscan.ExtensionPassiveScan.class); private final boolean loadScanRules; private AddOnScanRulesLoader scanRulesLoader; @@ -73,13 +91,17 @@ public class ExtensionPassiveScan2 extends ExtensionAdaptor { private OptionsPassiveScan optionsPassiveScan; private PolicyPassiveScanPanel policyPanel; - private PassiveScanParam passiveScanParam; + private PassiveScannerOptions options; private PassiveScannerOptionsPanel passiveScannerOptionsPanel; private Method setPassiveScanRuleManager; - private ScanRuleManager scanRuleManager; + private PassiveScannersManagerImpl scannersManager; private Object scanRuleManagerProxy; + private final boolean addScanner; + private PassiveScanController psc; + private boolean passiveScanEnabled; + public ExtensionPassiveScan2() { super(NAME); @@ -100,68 +122,23 @@ public ExtensionPassiveScan2() { if (addOptions) { try { - scanRuleManager = new ScanRuleManager(); + scannersManager = new PassiveScannersManagerImpl(); InvocationHandler invocationHandler = (o, method, args) -> { switch (method.getName()) { case "add": - { - PassiveScanner scanner = (PassiveScanner) args[0]; - try { - boolean added = scanRuleManager.add(scanner); - if (added && scanner instanceof PluginPassiveScanner) { - PluginPassiveScanner pps = - (PluginPassiveScanner) scanner; - pps.setConfig( - getModel().getOptionsParam().getConfig()); - if (hasView()) { - getPolicyPanel() - .getPassiveScanTableModel() - .addScanner(pps); - } - } - return added; - - } catch (Exception e) { - LOGGER.error( - "Failed to load passive scan rule {}", - scanner.getName(), - e); - return false; - } - } + return scannersManager.add((PassiveScanner) args[0]); case "getScanRule": - return scanRuleManager.getScanRule((int) args[0]); + return scannersManager.getScanRule((int) args[0]); case "getScanRules": - return scanRuleManager.getScanRules(); + return scannersManager.getScanners(); case "getPluginScanRules": - return scanRuleManager.getPluginScanRules(); + return scannersManager.getScanRules(); case "remove": - { - boolean removed; - PassiveScanner scanner; - if (args[0] instanceof PassiveScanner) { - scanner = (PassiveScanner) args[0]; - removed = scanRuleManager.remove(scanner); - } else { - String name = (String) args[0]; - scanner = scanRuleManager.getScanRule(name); - removed = scanRuleManager.remove(name); - } - - if (scanner != null - && hasView() - && scanner instanceof PluginPassiveScanner) { - getPolicyPanel() - .getPassiveScanTableModel() - .removeScanner((PluginPassiveScanner) scanner); - } - - return removed; - } + return scannersManager.removeImpl(args[0]); default: return null; @@ -184,6 +161,9 @@ && hasView() LOGGER.error("Failed to create ScanRuleManager:", e); } } + + addScanner = + hasField(org.zaproxy.zap.extension.pscan.ExtensionPassiveScan.class, "controller"); } private void setScanRuleManager(Object object) { @@ -230,12 +210,12 @@ public String getDescription() { @Override public void init() { - if (scanRuleManager != null) { + if (scannersManager != null) { setScanRuleManager(scanRuleManagerProxy); } if (loadScanRules) { - scanRulesLoader = new AddOnScanRulesLoader(getExtPscan()); + scanRulesLoader = new AddOnScanRulesLoader(this); } addScanStatus = @@ -255,15 +235,81 @@ public void postInit() { @Override public void optionsLoaded() { - if (scanRuleManager != null) { - scanRuleManager.setAutoTagScanners(getPassiveScanParam().getAutoTagScanners()); + if (scannersManager != null) { + scannersManager.getManager().setAutoTagScanners(options.getAutoTagScanners()); } + + if (addScanner) { + passiveScanEnabled = true; + getPassiveScanController(); + } + } + + /** + * Gets the manager of passive scanners. + * + * @return the manager, never {@code null}. + * @since 0.1.0 + */ + public PassiveScannersManager getPassiveScannersManager() { + return scannersManager; + } + + /** + * Gets the number of records to scan (queued). + * + * @return the number of records to scan. + * @since 0.1.0 + */ + public int getRecordsToScan() { + if (!addScanner) { + return getExtPscan().getRecordsToScan(); + } + if (passiveScanEnabled && psc != null) { + return psc.getRecordsToScan(); + } + return 0; + } + + /** + * Empties the passive scanner queue without passively scanning the messages. + * + *

Currently running scanners will run to completion but new scanners will only be run when + * new messages are added to the queue. + * + * @since 0.1.0 + */ + public void clearQueue() { + if (!addScanner) { + getExtPscan().clearQueue(); + return; + } + if (psc != null) { + psc.clearQueue(); + } + } + + private PassiveScanController getPassiveScanController() { + if (passiveScanEnabled && psc == null) { + final ExtensionLoader extensionLoader = Control.getSingleton().getExtensionLoader(); + psc = + new PassiveScanController( + this, + extensionLoader.getExtension(ExtensionHistory.class), + extensionLoader.getExtension(ExtensionAlert.class)); + psc.setSession(Model.getSingleton().getSession()); + psc.start(); + } + return psc; } @Override public void hook(ExtensionHook extensionHook) { - if (scanRuleManager != null) { - extensionHook.addOptionsParamSet(getPassiveScanParam()); + if (scannersManager != null) { + if (addScanner) { + options = new PassiveScannerOptions(); + extensionHook.addOptionsParamSet(options); + } if (hasView()) { extensionHook.getHookView().addOptionPanel(getPassiveScannerOptionsPanel()); @@ -274,7 +320,7 @@ public void hook(ExtensionHook extensionHook) { if (org.zaproxy.zap.extension.pscan.PassiveScanAPI.class.getAnnotation(Deprecated.class) != null) { - extensionHook.addApiImplementor(new PassiveScanApi(getExtPscan(), scanRuleManager)); + extensionHook.addApiImplementor(new PassiveScanApi(this, scannersManager)); } if (loadScanRules) { @@ -316,6 +362,11 @@ public void highwaterMarkSet(String key, long value) { extScript.registerScriptType(scriptType); } } + + if (addScanner) { + extensionHook.addProxyListener(new ProxyListenerImpl()); + extensionHook.addSessionListener(new SessionListenerImpl()); + } } private PolicyPassiveScanPanel getPolicyPanel() { @@ -325,24 +376,17 @@ private PolicyPassiveScanPanel getPolicyPanel() { return policyPanel; } - private PassiveScanParam getPassiveScanParam() { - if (passiveScanParam == null) { - passiveScanParam = new PassiveScanParam(); - } - return passiveScanParam; - } - private PassiveScannerOptionsPanel getPassiveScannerOptionsPanel() { if (passiveScannerOptionsPanel == null) { passiveScannerOptionsPanel = - new PassiveScannerOptionsPanel(getExtPscan(), Constant.messages); + new PassiveScannerOptionsPanel(this::clearQueue, Constant.messages); } return passiveScannerOptionsPanel; } private OptionsPassiveScan getOptionsPassiveScan() { if (optionsPassiveScan == null) { - optionsPassiveScan = new OptionsPassiveScan(scanRuleManager); + optionsPassiveScan = new OptionsPassiveScan(scannersManager.getManager()); } return optionsPassiveScan; } @@ -391,4 +435,172 @@ public void unload() { setScanRuleManager(null); } } + + @Override + public void destroy() { + super.destroy(); + + stopPassiveScanController(); + } + + private void stopPassiveScanController() { + if (psc != null) { + psc.shutdown(); + psc = null; + } + } + + void setPassiveScanEnabled(boolean enabled) { + if (!addScanner) { + return; + } + + if (passiveScanEnabled != enabled) { + passiveScanEnabled = enabled; + if (enabled) { + getPassiveScanController(); + } else { + stopPassiveScanController(); + } + } + } + + PassiveScanTask getOldestRunningTask() { + if (!addScanner) { + return null; + } + if (passiveScanEnabled) { + return getPassiveScanController().getOldestRunningTask(); + } + return null; + } + + List getRunningTasks() { + if (!addScanner) { + return List.of(); + } + if (passiveScanEnabled) { + return getPassiveScanController().getRunningTasks(); + } + return List.of(); + } + + private class ProxyListenerImpl implements ProxyListener { + + @Override + public int getArrangeableListenerOrder() { + return PROXY_LISTENER_ORDER; + } + + @Override + public boolean onHttpRequestSend(HttpMessage msg) { + return true; + } + + @Override + public boolean onHttpResponseReceive(HttpMessage msg) { + if (psc != null) { + psc.responseReceived(); + } + return true; + } + } + + private class SessionListenerImpl implements SessionChangedListener { + + @Override + public void sessionAboutToChange(Session session) { + stopPassiveScanController(); + } + + @Override + public void sessionChanged(Session session) { + if (passiveScanEnabled) { + getPassiveScanController().setSession(session); + } + } + + @Override + public void sessionScopeChanged(Session session) { + // Nothing to do. + } + + @Override + public void sessionModeChanged(Mode mode) { + // Nothing to do. + } + } + + private class PassiveScannersManagerImpl implements PassiveScannersManager { + + private final ScanRuleManager scanRuleManager; + + PassiveScannersManagerImpl() { + this.scanRuleManager = new ScanRuleManager(); + } + + ScanRuleManager getManager() { + return scanRuleManager; + } + + @Override + public boolean add(PassiveScanner scanner) { + try { + boolean added = scanRuleManager.add(scanner); + if (added && scanner instanceof PluginPassiveScanner) { + PluginPassiveScanner pps = (PluginPassiveScanner) scanner; + pps.setConfig(getModel().getOptionsParam().getConfig()); + if (hasView()) { + getPolicyPanel().getPassiveScanTableModel().addScanner(pps); + } + } + return added; + + } catch (Exception e) { + LOGGER.error("Failed to load passive scan rule {}", scanner.getName(), e); + return false; + } + } + + @Override + public boolean remove(PassiveScanner scanner) { + return removeImpl(scanner); + } + + public boolean removeImpl(Object value) { + boolean removed; + PassiveScanner scanner; + if (value instanceof PassiveScanner) { + scanner = (PassiveScanner) value; + removed = scanRuleManager.remove(scanner); + } else { + String name = (String) value; + scanner = scanRuleManager.getScanRule(name); + removed = scanRuleManager.remove(name); + } + + if (scanner != null && hasView() && scanner instanceof PluginPassiveScanner) { + getPolicyPanel() + .getPassiveScanTableModel() + .removeScanner((PluginPassiveScanner) scanner); + } + + return removed; + } + + @Override + public List getScanners() { + return scanRuleManager.getScanners(); + } + + @Override + public PluginPassiveScanner getScanRule(int id) { + return scanRuleManager.getScanRule(id); + } + + @Override + public List getScanRules() { + return scanRuleManager.getScanRules(); + } + } } diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScanApi.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScanApi.java index aff9ed60a20..c54ca8c2db2 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScanApi.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScanApi.java @@ -19,7 +19,6 @@ */ package org.zaproxy.addon.pscan; -import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,7 +27,8 @@ import org.apache.logging.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.core.scanner.Plugin; -import org.zaproxy.addon.pscan.internal.ScanRuleManager; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; +import org.zaproxy.addon.pscan.internal.scanner.PassiveScanTask; import org.zaproxy.zap.extension.api.ApiAction; import org.zaproxy.zap.extension.api.ApiException; import org.zaproxy.zap.extension.api.ApiImplementor; @@ -37,9 +37,6 @@ import org.zaproxy.zap.extension.api.ApiResponseList; import org.zaproxy.zap.extension.api.ApiResponseSet; import org.zaproxy.zap.extension.api.ApiView; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; -import org.zaproxy.zap.extension.pscan.PassiveScanParam; -import org.zaproxy.zap.extension.pscan.PassiveScanTask; import org.zaproxy.zap.extension.pscan.PassiveScanner; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; import org.zaproxy.zap.utils.ApiUtils; @@ -76,17 +73,16 @@ public class PassiveScanApi extends ApiImplementor { private static final String PARAM_ALERT_THRESHOLD = "alertThreshold"; private static final String PARAM_MAX_ALERTS = "maxAlerts"; - private ExtensionPassiveScan extension; - private final ScanRuleManager scanRuleManager; - private Method setPassiveScanEnabledMethod; + private final ExtensionPassiveScan2 extension; + private final PassiveScannersManager scannersManager; public PassiveScanApi() { this(null, null); } - public PassiveScanApi(ExtensionPassiveScan extension, ScanRuleManager scanRuleManager) { + public PassiveScanApi(ExtensionPassiveScan2 extension, PassiveScannersManager scannersManager) { this.extension = extension; - this.scanRuleManager = scanRuleManager; + this.scannersManager = scannersManager; this.addApiAction(new ApiAction(ACTION_SET_ENABLED, new String[] {PARAM_ENABLED})); this.addApiAction( @@ -110,23 +106,11 @@ public PassiveScanApi(ExtensionPassiveScan extension, ScanRuleManager scanRuleMa this.addApiView(new ApiView(VIEW_SCANNERS)); ApiView currentRule = new ApiView(VIEW_CURRENT_RULE); currentRule.setDeprecated(true); - // When generating the API clients messages is not initialised. - if (Constant.messages != null) { - currentRule.setDeprecatedDescription( - Constant.messages.getString("pscan.api.view.currentRule.deprecated")); - } + currentRule.setDeprecatedDescription( + Constant.messages.getString("pscan.api.view.currentRule.deprecated")); this.addApiView(currentRule); this.addApiView(new ApiView(VIEW_CURRENT_TASKS)); this.addApiView(new ApiView(VIEW_MAX_ALERTS_PER_RULE)); - - try { - setPassiveScanEnabledMethod = - ExtensionPassiveScan.class.getDeclaredMethod( - "setPassiveScanEnabled", boolean.class); - setPassiveScanEnabledMethod.setAccessible(true); - } catch (NoSuchMethodException | SecurityException e) { - LOGGER.debug("An error occurred while getting the method:", e); - } } @Override @@ -140,14 +124,10 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc case ACTION_SET_ENABLED: boolean enabled = getParam(params, PARAM_ENABLED, false); - try { - setPassiveScanEnabledMethod.invoke(extension, enabled); - } catch (Exception e) { - throw new ApiException(ApiException.Type.INTERNAL_ERROR, e); - } + extension.setPassiveScanEnabled(enabled); break; case ACTION_SET_SCAN_ONLY_IN_SCOPE: - getPassiveScanParam().setScanOnlyInScope(params.getBoolean(PARAM_ONLY_IN_SCOPE)); + getOptions().setScanOnlyInScope(params.getBoolean(PARAM_ONLY_IN_SCOPE)); break; case ACTION_ENABLE_ALL_SCANNERS: setAllPluginPassiveScannersEnabled(true); @@ -178,16 +158,15 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc setPluginPassiveScannerAlertThreshold(pluginId, alertThreshold); break; case ACTION_SET_MAX_ALERTS_PER_RULE: - getPassiveScanParam() - .setMaxAlertsPerRule(ApiUtils.getIntParam(params, PARAM_MAX_ALERTS)); + getOptions().setMaxAlertsPerRule(ApiUtils.getIntParam(params, PARAM_MAX_ALERTS)); break; case ACTION_DISABLE_ALL_TAGS: - getPassiveScanParam() + getOptions() .getAutoTagScanners() .forEach(tagScanner -> tagScanner.setEnabled(false)); break; case ACTION_ENABLE_ALL_TAGS: - getPassiveScanParam() + getOptions() .getAutoTagScanners() .forEach(tagScanner -> tagScanner.setEnabled(true)); break; @@ -201,8 +180,8 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc return ApiResponseElement.OK; } - private PassiveScanParam getPassiveScanParam() { - return extension.getModel().getOptionsParam().getParamSet(PassiveScanParam.class); + private PassiveScannerOptions getOptions() { + return extension.getModel().getOptionsParam().getParamSet(PassiveScannerOptions.class); } private void setPluginPassiveScannersEnabled(JSONObject params, boolean enabled) @@ -233,10 +212,7 @@ private boolean hasPluginPassiveScanner(int pluginId) { } private PluginPassiveScanner getScanRule(int pluginId) { - if (scanRuleManager != null) { - return (PluginPassiveScanner) scanRuleManager.getScanRule(pluginId); - } - return extension.getPluginPassiveScanner(pluginId); + return scannersManager.getScanRule(pluginId); } /** @@ -252,10 +228,7 @@ private void setAllPluginPassiveScannersEnabled(boolean enabled) { } private List getPluginScanRules() { - if (scanRuleManager != null) { - return scanRuleManager.getPluginScanRules(); - } - return extension.getPluginPassiveScanners(); + return scannersManager.getScanRules(); } /** @@ -313,7 +286,7 @@ public ApiResponse handleApiView(String name, JSONObject params) throws ApiExcep case VIEW_SCAN_ONLY_IN_SCOPE: result = new ApiResponseElement( - name, Boolean.toString(getPassiveScanParam().isScanOnlyInScope())); + name, Boolean.toString(getOptions().isScanOnlyInScope())); break; case VIEW_RECORDS_TO_SCAN: result = new ApiResponseElement(name, String.valueOf(extension.getRecordsToScan())); @@ -348,7 +321,7 @@ public ApiResponse handleApiView(String name, JSONObject params) throws ApiExcep result = new ApiResponseElement( VIEW_MAX_ALERTS_PER_RULE, - Integer.toString(getPassiveScanParam().getMaxAlertsPerRule())); + Integer.toString(getOptions().getMaxAlertsPerRule())); break; default: throw new ApiException(ApiException.Type.BAD_VIEW); @@ -358,14 +331,16 @@ public ApiResponse handleApiView(String name, JSONObject params) throws ApiExcep private ApiResponseSet getResponseForTask(PassiveScanTask task, String name) { Map map = new HashMap<>(); - PassiveScanner scanner = task.getCurrentScanner(); - map.put("name", scanner == null ? "" : scanner.getName()); - map.put("url", task.getURI().toString()); - long time = task.getStartTime(); - if (time > 0) { - time = System.currentTimeMillis() - time; + if (task != null) { + PassiveScanner scanner = task.getCurrentScanner(); + map.put("name", scanner == null ? "" : scanner.getName()); + map.put("url", task.getURI().toString()); + long time = task.getStartTime(); + if (time > 0) { + time = System.currentTimeMillis() - time; + } + map.put("time", String.valueOf(time)); } - map.put("time", String.valueOf(time)); return new ApiResponseSet<>(name, map); } } diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScannersManager.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScannersManager.java new file mode 100644 index 00000000000..13b294b985b --- /dev/null +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/PassiveScannersManager.java @@ -0,0 +1,72 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan; + +import java.util.List; +import org.zaproxy.zap.extension.pscan.PassiveScanner; +import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; + +/** + * The manager of passive scanners. + * + * @since 0.1.0 + */ +public interface PassiveScannersManager { + + /** + * Adds the given scanner. + * + *

Scanners with duplicated name are not added. + * + * @param scanner the scanner to add. + * @return {@code true} if the scanner was added, {@code false} otherwise. + */ + boolean add(PassiveScanner scanner); + + /** + * Removes the given scanner. + * + * @param scanner the scanner to remove. + * @return {@code true} if the scanner was removed, {@code false} otherwise. + */ + boolean remove(PassiveScanner scanner); + + /** + * Gets all the scanners. + * + * @return the scanners, never {@code null}. + */ + List getScanners(); + + /** + * Gets the scan rule with the given ID. + * + * @param id the ID of the scan rule. + * @return the scan rule, or {@code null} if not present. + */ + PluginPassiveScanner getScanRule(int id); + + /** + * Gets all the scan rules. + * + * @return the scan rules, never {@code null}. + */ + List getScanRules(); +} diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/internal/PscanRulesTableModel.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/internal/PscanRulesTableModel.java index 4b62b365e7d..ab53a3a5696 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/internal/PscanRulesTableModel.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/internal/PscanRulesTableModel.java @@ -25,8 +25,8 @@ import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.zaproxy.addon.automation.jobs.JobUtils; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.addon.pscan.automation.jobs.PassiveScanConfigJob; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; @SuppressWarnings("serial") @@ -42,7 +42,7 @@ public class PscanRulesTableModel extends AbstractTableModel { private List rules = new ArrayList<>(); - private ExtensionPassiveScan extPscan; + private ExtensionPassiveScan2 extPscan; public PscanRulesTableModel() { super(); @@ -124,21 +124,21 @@ public void remove(int index) { } } - private ExtensionPassiveScan getExtPscan() { + private ExtensionPassiveScan2 getExtPscan() { if (this.extPscan == null) { this.extPscan = Control.getSingleton() .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class); + .getExtension(ExtensionPassiveScan2.class); } return this.extPscan; } public List getAllScanRules() { - return this.getExtPscan().getPluginPassiveScanners(); + return getExtPscan().getPassiveScannersManager().getScanRules(); } public PluginPassiveScanner getScanRule(int id) { - return this.getExtPscan().getPluginPassiveScanner(id); + return getExtPscan().getPassiveScannersManager().getScanRule(id); } } diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJob.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJob.java index bdfdd9f56b3..2e2f8291cfb 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJob.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJob.java @@ -34,22 +34,21 @@ import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.core.scanner.Plugin.AlertThreshold; -import org.parosproxy.paros.model.Model; import org.zaproxy.addon.automation.AutomationData; import org.zaproxy.addon.automation.AutomationEnvironment; import org.zaproxy.addon.automation.AutomationJob; import org.zaproxy.addon.automation.AutomationProgress; import org.zaproxy.addon.automation.jobs.JobData; import org.zaproxy.addon.automation.jobs.JobUtils; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.addon.pscan.automation.internal.PassiveScanConfigJobDialog; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; -import org.zaproxy.zap.extension.pscan.PassiveScanParam; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; public class PassiveScanConfigJob extends AutomationJob { public static final String JOB_NAME = "passiveScan-config"; - private static final String OPTIONS_METHOD_NAME = "getPassiveScanParam"; + private static final String OPTIONS_METHOD_NAME = "getPassiveScannerOptions"; private static final String PARAM_ID = "id"; private static final String PARAM_ENABLE_TAGS = "enableTags"; @@ -58,26 +57,20 @@ public class PassiveScanConfigJob extends AutomationJob { private static final String[] IGNORE_PARAMS = new String[] {PARAM_ENABLE_TAGS, PARAM_DISABLE_ALL_RULES}; - private ExtensionPassiveScan extPScan; + private final ExtensionPassiveScan2 pscan; private Parameters parameters = new Parameters(); private Parameters originalParameters = new Parameters(); private Data data; public PassiveScanConfigJob() { + this.pscan = + Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionPassiveScan2.class); data = new Data(this, this.parameters); } - private ExtensionPassiveScan getExtPScan() { - if (extPScan == null) { - extPScan = - Control.getSingleton() - .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class); - } - return extPScan; - } - @Override public void planStarted() { // Save the current state @@ -134,7 +127,8 @@ public void verifyParameters(AutomationProgress progress) { continue; } int id = Integer.parseInt(idObj.toString()); - PluginPassiveScanner plugin = getExtPScan().getPluginPassiveScanner(id); + PluginPassiveScanner plugin = + pscan.getPassiveScannersManager().getScanRule(id); if (plugin == null) { progress.warn( Constant.messages.getString( @@ -191,12 +185,14 @@ public void applyParameters(AutomationProgress progress) { public void runJob(AutomationEnvironment env, AutomationProgress progress) { // Configure any rules if (Boolean.TRUE.equals(this.getData().getParameters().getDisableAllRules())) { - getExtPScan().getPluginPassiveScanners().stream() + pscan.getPassiveScannersManager() + .getScanRules() .forEach(pscan -> pscan.setEnabled(false)); } for (Rule rule : this.getData().getRules()) { - PluginPassiveScanner plugin = getExtPScan().getPluginPassiveScanner(rule.getId()); + PluginPassiveScanner plugin = + pscan.getPassiveScannersManager().getScanRule(rule.getId()); AlertThreshold pluginTh = JobUtils.parseAlertThreshold(rule.getThreshold(), this.getName(), progress); if (pluginTh != null && plugin != null) { @@ -211,15 +207,9 @@ public void runJob(AutomationEnvironment env, AutomationProgress progress) { } } // enable / disable pscan tags - PassiveScanParam pscanParam = - Model.getSingleton().getOptionsParam().getParamSet(PassiveScanParam.class); - if (pscanParam != null) { - pscanParam - .getAutoTagScanners() - .forEach( - tagScanner -> - tagScanner.setEnabled(this.getParameters().getEnableTags())); - } + getPassiveScannerOptions() + .getAutoTagScanners() + .forEach(tagScanner -> tagScanner.setEnabled(this.getParameters().getEnableTags())); } @Override @@ -254,7 +244,7 @@ public Order getOrder() { @Override public Object getParamMethodObject() { - return getExtPScan(); + return this; } @Override @@ -262,6 +252,10 @@ public String getParamMethodName() { return OPTIONS_METHOD_NAME; } + public PassiveScannerOptions getPassiveScannerOptions() { + return pscan.getModel().getOptionsParam().getParamSet(PassiveScannerOptions.class); + } + @Override public void showDialog() { new PassiveScanConfigJobDialog(this).setVisible(true); diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJob.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJob.java index 670447490c3..138f5f2da6e 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJob.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJob.java @@ -22,7 +22,6 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -40,8 +39,9 @@ import org.zaproxy.addon.automation.JobResultData; import org.zaproxy.addon.automation.jobs.JobData; import org.zaproxy.addon.automation.jobs.JobUtils; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.addon.pscan.automation.internal.PassiveScanWaitJobDialog; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; public class PassiveScanWaitJob extends AutomationJob { @@ -49,10 +49,16 @@ public class PassiveScanWaitJob extends AutomationJob { private static final String PARAM_MAX_DURATION = "maxDuration"; + private final ExtensionPassiveScan2 pscan; + private Data data; private Parameters parameters = new Parameters(); public PassiveScanWaitJob() { + this.pscan = + Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionPassiveScan2.class); this.data = new Data(this, parameters); } @@ -68,15 +74,13 @@ public String getKeyAlertTestsResultData() { @Override public void runJob(AutomationEnvironment env, AutomationProgress progress) { - ExtensionPassiveScan extPScan = getExtPassiveScan(); - long endTime = Long.MAX_VALUE; Integer maxDuration = this.parameters.getMaxDuration(); if (maxDuration != null && maxDuration > 0) { endTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(maxDuration); } - while (extPScan.getRecordsToScan() > 0) { + while (pscan.getRecordsToScan() > 0) { if (System.currentTimeMillis() > endTime) { break; } @@ -93,20 +97,15 @@ public void runJob(AutomationEnvironment env, AutomationProgress progress) { @SuppressWarnings("removal") public List getJobResultData() { List list = new ArrayList<>(); - list.add( - new PassiveScanJobResultData( - this.getName(), getExtPassiveScan().getPluginPassiveScanners())); + List scanRules = pscan.getPassiveScannersManager().getScanRules(); + list.add(new PassiveScanJobResultData(this.getName(), scanRules)); // XXX Provided for compatibility with older add-ons. list.add( new org.zaproxy.addon.automation.jobs.PassiveScanJobResultData( - getName(), getExtPassiveScan().getPluginPassiveScanners())); + getName(), scanRules)); return list; } - private ExtensionPassiveScan getExtPassiveScan() { - return Control.getSingleton().getExtensionLoader().getExtension(ExtensionPassiveScan.class); - } - @Override public void verifyParameters(AutomationProgress progress) { Map jobData = this.getJobData(); @@ -114,7 +113,7 @@ public void verifyParameters(AutomationProgress progress) { return; } JobUtils.applyParamsToObject( - (LinkedHashMap) jobData.get("parameters"), + (Map) jobData.get("parameters"), this.parameters, this.getName(), null, diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/AddOnScanRulesLoader.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/AddOnScanRulesLoader.java index 53be3ac0c08..6e1615cf79b 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/AddOnScanRulesLoader.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/AddOnScanRulesLoader.java @@ -31,12 +31,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.parosproxy.paros.Constant; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.control.AddOn; import org.zaproxy.zap.control.AddOn.InstallationStatus; import org.zaproxy.zap.control.ExtensionFactory; import org.zaproxy.zap.extension.AddOnInstallationStatusListener; -import org.zaproxy.zap.extension.AddOnInstallationStatusListener.StatusUpdate; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; public class AddOnScanRulesLoader implements AddOnInstallationStatusListener { @@ -44,9 +43,9 @@ public class AddOnScanRulesLoader implements AddOnInstallationStatusListener { private static final Logger LOGGER = LogManager.getLogger(AddOnScanRulesLoader.class); private final Map> addOnScanRules; - private final ExtensionPassiveScan extension; + private final ExtensionPassiveScan2 extension; - public AddOnScanRulesLoader(ExtensionPassiveScan extension) { + public AddOnScanRulesLoader(ExtensionPassiveScan2 extension) { addOnScanRules = new HashMap<>(); this.extension = extension; } @@ -88,7 +87,7 @@ private void removeScanRules(AddOn addOn) { for (PluginPassiveScanner pscanrule : loadedPscanrules) { String name = pscanrule.getClass().getCanonicalName(); LOGGER.debug("Uninstall pscanrule: {}", name); - if (!extension.removePassiveScanner(pscanrule)) { + if (!extension.getPassiveScannersManager().remove(pscanrule)) { LOGGER.error("Failed to uninstall pscanrule: {}", name); } } @@ -118,7 +117,7 @@ private void loadScanRules(AddOn addOn) { pscanrule.setStatus(addOn.getStatus()); String name = pscanrule.getClass().getCanonicalName(); LOGGER.debug("Install pscanrule: {}", name); - if (!extension.addPassiveScanner(pscanrule)) { + if (!extension.getPassiveScannersManager().add(pscanrule)) { LOGGER.error("Failed to install pscanrule: {}", name); } } diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptions.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptions.java new file mode 100644 index 00000000000..608aac858f9 --- /dev/null +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptions.java @@ -0,0 +1,325 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal; + +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.configuration.ConversionException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.model.HistoryReference; +import org.zaproxy.addon.pscan.internal.scanner.PassiveScanTaskHelper; +import org.zaproxy.zap.common.VersionedAbstractParam; +import org.zaproxy.zap.extension.api.ZapApiIgnore; + +public class PassiveScannerOptions extends VersionedAbstractParam { + + private static final Logger LOGGER = LogManager.getLogger(PassiveScannerOptions.class); + + /** + * The current version of the configurations. Used to keep track of configuration changes + * between releases, in case changes/updates are needed. + * + *

It only needs to be incremented for configuration changes (not releases of the add-on). + * + * @see #CONFIG_VERSION_KEY + * @see #updateConfigsImpl(int) + */ + protected static final int CURRENT_CONFIG_VERSION = 5; + + static final String BASE_KEY = "pscans"; + + /** + * The configuration key for the version of the configurations. + * + * @see #CURRENT_CONFIG_VERSION + */ + private static final String CONFIG_VERSION_KEY = BASE_KEY + VERSION_ATTRIBUTE; + + private static final String ALL_AUTO_TAG_SCANNERS_KEY = BASE_KEY + ".autoTagScanners.scanner"; + + private static final String AUTO_TAG_SCANNER_NAME_KEY = "name"; + private static final String AUTO_TAG_SCANNER_TYPE_KEY = "type"; + private static final String AUTO_TAG_SCANNER_CONFIG_KEY = "config"; + private static final String AUTO_TAG_SCANNER_REQ_URL_REGEX_KEY = "reqUrlRegex"; + private static final String AUTO_TAG_SCANNER_REQ_HEAD_REGEX_KEY = "reqHeadRegex"; + private static final String AUTO_TAG_SCANNER_RES_HEAD_REGEX_KEY = "resHeadRegex"; + private static final String AUTO_TAG_SCANNER_RES_BODY_REGEX_KEY = "resBodyRegex"; + private static final String AUTO_TAG_SCANNER_ENABLED_KEY = "enabled"; + + private static final String CONFIRM_REMOVE_AUTO_TAG_SCANNER_KEY = + BASE_KEY + ".confirmRemoveAutoTagScanner"; + + private static final String SCAN_ONLY_IN_SCOPE_KEY = BASE_KEY + ".scanOnlyInScope"; + private static final String SCAN_FUZZER_MESSAGES_KEY = BASE_KEY + ".scanFuzzerMessages"; + private static final String PASSIVE_SCAN_THREADS = BASE_KEY + ".threads"; + private static final String MAX_ALERTS_PER_RULE = BASE_KEY + ".maxAlertsPerRule"; + private static final String MAX_BODY_SIZE_IN_BYTES = BASE_KEY + ".maxBodySizeInBytes"; + + private List autoTagScanners = new ArrayList<>(0); + + private boolean confirmRemoveAutoTagScanner = true; + + /** + * Flag that indicates whether or not the passive scan should be performed only on messages that + * are in scope. + * + *

Default is {@code false}, all messages are scanned. + */ + private boolean scanOnlyInScope; + + /** + * Flag that indicates whether or not the passive scan should be performed on traffic generated + * by the fuzzer. + * + *

Default is {@code false}, fuzzer traffic is not scanned. + */ + private boolean scanFuzzerMessages; + + /** + * The maximum number of alerts any passive scan rule should raise. Rules will be disabled once + * they exceed this threshold. Default 0, which means there is no limit. This is typically only + * useful for automated scanning. + */ + private int maxAlertsPerRule; + + private int maxBodySizeInBytesToScan; + + private int passiveScanThreads; + + public PassiveScannerOptions() {} + + @Override + protected int getCurrentVersion() { + return CURRENT_CONFIG_VERSION; + } + + @Override + protected String getConfigVersionKey() { + return CONFIG_VERSION_KEY; + } + + @Override + protected void updateConfigsImpl(int fileVersion) {} + + @Override + protected void parseImpl() { + try { + List fields = + ((HierarchicalConfiguration) getConfig()) + .configurationsAt(ALL_AUTO_TAG_SCANNERS_KEY); + this.autoTagScanners = new ArrayList<>(fields.size()); + List tempListNames = new ArrayList<>(fields.size()); + for (HierarchicalConfiguration sub : fields) { + String name = sub.getString(AUTO_TAG_SCANNER_NAME_KEY, ""); + if (!"".equals(name) && !tempListNames.contains(name)) { + tempListNames.add(name); + + RegexAutoTagScanner app = + new RegexAutoTagScanner( + sub.getString(AUTO_TAG_SCANNER_NAME_KEY), + getEnum( + AUTO_TAG_SCANNER_TYPE_KEY, + RegexAutoTagScanner.TYPE.TAG), + sub.getString(AUTO_TAG_SCANNER_CONFIG_KEY), + sub.getString(AUTO_TAG_SCANNER_REQ_URL_REGEX_KEY), + sub.getString(AUTO_TAG_SCANNER_REQ_HEAD_REGEX_KEY), + sub.getString(AUTO_TAG_SCANNER_RES_HEAD_REGEX_KEY), + sub.getString(AUTO_TAG_SCANNER_RES_BODY_REGEX_KEY), + sub.getBoolean(AUTO_TAG_SCANNER_ENABLED_KEY, true)); + + autoTagScanners.add(app); + } + } + } catch (ConversionException e) { + LOGGER.error("Error while loading the auto tag scanners: {}", e.getMessage(), e); + } + + this.confirmRemoveAutoTagScanner = getBoolean(CONFIRM_REMOVE_AUTO_TAG_SCANNER_KEY, true); + this.scanOnlyInScope = getBoolean(SCAN_ONLY_IN_SCOPE_KEY, false); + this.scanFuzzerMessages = getBoolean(SCAN_FUZZER_MESSAGES_KEY, false); + applyHistoryTypes(); + // Default threads to number of processors as passive scanning is not blocked on I/O + this.passiveScanThreads = + this.getInt(PASSIVE_SCAN_THREADS, Constant.getDefaultThreadCount() / 2); + if (this.passiveScanThreads <= 0) { + // Must be greater that zero + this.passiveScanThreads = Constant.getDefaultThreadCount() / 2; + } + this.maxAlertsPerRule = this.getInt(MAX_ALERTS_PER_RULE, 0); + this.maxBodySizeInBytesToScan = this.getInt(MAX_BODY_SIZE_IN_BYTES, 0); + } + + public void setAutoTagScanners(List scanners) { + this.autoTagScanners = scanners; + + ((HierarchicalConfiguration) getConfig()).clearTree(ALL_AUTO_TAG_SCANNERS_KEY); + + for (int i = 0, size = scanners.size(); i < size; ++i) { + String elementBaseKey = ALL_AUTO_TAG_SCANNERS_KEY + "(" + i + ")."; + RegexAutoTagScanner scanner = scanners.get(i); + + getConfig().setProperty(elementBaseKey + AUTO_TAG_SCANNER_NAME_KEY, scanner.getName()); + getConfig() + .setProperty( + elementBaseKey + AUTO_TAG_SCANNER_TYPE_KEY, + scanner.getType().toString()); + getConfig() + .setProperty(elementBaseKey + AUTO_TAG_SCANNER_CONFIG_KEY, scanner.getConf()); + getConfig() + .setProperty( + elementBaseKey + AUTO_TAG_SCANNER_REQ_URL_REGEX_KEY, + scanner.getRequestUrlRegex()); + getConfig() + .setProperty( + elementBaseKey + AUTO_TAG_SCANNER_REQ_HEAD_REGEX_KEY, + scanner.getRequestHeaderRegex()); + getConfig() + .setProperty( + elementBaseKey + AUTO_TAG_SCANNER_RES_HEAD_REGEX_KEY, + scanner.getResponseHeaderRegex()); + getConfig() + .setProperty( + elementBaseKey + AUTO_TAG_SCANNER_RES_BODY_REGEX_KEY, + scanner.getResponseBodyRegex()); + getConfig() + .setProperty( + elementBaseKey + AUTO_TAG_SCANNER_ENABLED_KEY, scanner.isEnabled()); + } + } + + public List getAutoTagScanners() { + return autoTagScanners; + } + + @ZapApiIgnore + public boolean isConfirmRemoveAutoTagScanner() { + return this.confirmRemoveAutoTagScanner; + } + + @ZapApiIgnore + public void setConfirmRemoveAutoTagScanner(boolean confirmRemove) { + this.confirmRemoveAutoTagScanner = confirmRemove; + getConfig().setProperty(CONFIRM_REMOVE_AUTO_TAG_SCANNER_KEY, confirmRemoveAutoTagScanner); + } + + /** + * Sets whether or not the passive scan should be performed only on messages that are in scope. + * + * @param scanOnlyInScope {@code true} if the scan should be performed only on messages that are + * in scope, {@code false} otherwise. + * @see #isScanOnlyInScope() + * @see org.parosproxy.paros.model.Session#isInScope(String) Session.isInScope(String) + */ + public void setScanOnlyInScope(boolean scanOnlyInScope) { + this.scanOnlyInScope = scanOnlyInScope; + getConfig().setProperty(SCAN_ONLY_IN_SCOPE_KEY, scanOnlyInScope); + } + + /** + * Tells whether or not the passive scan should be performed only on messages that are in scope. + * + * @return {@code true} if the scan should be performed only on messages that are in scope, + * {@code false} otherwise. + * @see #setScanOnlyInScope(boolean) + */ + public boolean isScanOnlyInScope() { + return scanOnlyInScope; + } + + /** + * Sets whether or not the passive scan should be performed on traffic from the fuzzer. + * + * @param scanFuzzerMessages {@code true} if the scan should be performed on traffic generated + * by the fuzzer, {@code false} otherwise. + * @see #isScanFuzzerMessages() + */ + public void setScanFuzzerMessages(boolean scanFuzzerMessages) { + this.scanFuzzerMessages = scanFuzzerMessages; + getConfig().setProperty(SCAN_FUZZER_MESSAGES_KEY, scanFuzzerMessages); + applyHistoryTypes(); + } + + /** + * Adds or removes the {@code HistoryReference} types that should be included when passive + * scanning traffic from the fuzzer. + * + * @see #isScanFuzzerMessages() + * @see #setScanFuzzerMessages(boolean) + */ + private void applyHistoryTypes() { + if (scanFuzzerMessages) { + PassiveScanTaskHelper.addApplicableHistoryType(HistoryReference.TYPE_FUZZER); + PassiveScanTaskHelper.addApplicableHistoryType(HistoryReference.TYPE_FUZZER_TEMPORARY); + } else { + PassiveScanTaskHelper.removeApplicableHistoryType(HistoryReference.TYPE_FUZZER); + PassiveScanTaskHelper.removeApplicableHistoryType( + HistoryReference.TYPE_FUZZER_TEMPORARY); + } + } + + /** + * Tells whether or not the passive scan should be performed on traffic from the fuzzer. + * + * @return {@code true} if the scan should be performed on traffic from the fuzzer, {@code + * false} otherwise. + * @see #setScanFuzzerMessages(boolean) + */ + public boolean isScanFuzzerMessages() { + return scanFuzzerMessages; + } + + public int getMaxAlertsPerRule() { + return maxAlertsPerRule; + } + + public void setMaxAlertsPerRule(int maxAlertsPerRule) { + this.maxAlertsPerRule = maxAlertsPerRule; + getConfig().setProperty(MAX_ALERTS_PER_RULE, maxAlertsPerRule); + } + + public int getMaxBodySizeInBytesToScan() { + return maxBodySizeInBytesToScan; + } + + public void setMaxBodySizeInBytesToScan(int maxBodySizeInBytesToScan) { + this.maxBodySizeInBytesToScan = maxBodySizeInBytesToScan; + getConfig().setProperty(MAX_BODY_SIZE_IN_BYTES, maxBodySizeInBytesToScan); + } + + /** Gets the number of passive scan threads. */ + public int getPassiveScanThreads() { + return passiveScanThreads; + } + + /** + * Sets the number of passive scan threads. + * + * @param passiveScanThreads the number of passive scan threads, must be > 0 + */ + public void setPassiveScanThreads(int passiveScanThreads) { + if (passiveScanThreads > 0) { + this.passiveScanThreads = passiveScanThreads; + getConfig().setProperty(PASSIVE_SCAN_THREADS, passiveScanThreads); + } + } +} diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScanner.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScanner.java new file mode 100644 index 00000000000..821894ae310 --- /dev/null +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScanner.java @@ -0,0 +1,341 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2010 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.htmlparser.jericho.Source; +import org.apache.commons.httpclient.URIException; +import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; +import org.zaproxy.zap.model.SessionStructure; +import org.zaproxy.zap.utils.Stats; + +public class RegexAutoTagScanner extends PluginPassiveScanner { + + public static final String TAG_STATS_PREFIX = "stats.tag."; + + protected static final int PATTERN_SCAN = Pattern.CASE_INSENSITIVE; + + public enum TYPE { + ALERT, + TAG, + TECH + } + + private String name = null; + private String requestUrlRegex = null; + private String requestHeaderRegex = null; + private String responseHeaderRegex = null; + private String responseBodyRegex = null; + + private Pattern requestUrlPattern = null; + private Pattern requestHeaderPattern = null; + private Pattern responseHeaderPattern = null; + private Pattern responseBodyPattern = null; + + private TYPE type = null; + private String config = null; + + protected RegexAutoTagScanner() { + // Reduced accessibility to prevent it from being loaded as scan rule. + } + + public RegexAutoTagScanner(String name, TYPE type, String config) { + super(); + this.name = name; + this.type = type; + this.config = config; + } + + public RegexAutoTagScanner( + String name, + TYPE type, + String config, + String requestUrlregex, + String requestHeaderRegex, + String responseHeaderRegex, + String responseBodyRegex, + boolean enabled) { + super(); + this.name = name; + this.setRequestUrlRegex(requestUrlregex); + this.setRequestHeaderRegex(requestHeaderRegex); + this.setResponseHeaderRegex(responseHeaderRegex); + this.setResponseBodyRegex(responseBodyRegex); + this.type = type; + this.config = config; + setEnabled(enabled); + } + + public RegexAutoTagScanner(RegexAutoTagScanner scanner) { + this( + scanner.name, + scanner.type, + scanner.config, + scanner.requestUrlRegex, + scanner.requestHeaderRegex, + scanner.responseHeaderRegex, + scanner.responseBodyRegex, + scanner.isEnabled()); + } + + @Override + public RegexAutoTagScanner copy() { + return new RegexAutoTagScanner(this); + } + + public Pattern getRequestUrlPattern() { + return requestUrlPattern; + } + + public Pattern getRequestHeaderPattern() { + return requestHeaderPattern; + } + + public Pattern getResponseHeaderPattern() { + return responseHeaderPattern; + } + + public Pattern getResponseBodyPattern() { + return responseBodyPattern; + } + + public TYPE getType() { + return type; + } + + public void setType(TYPE type) { + this.type = type; + } + + public String getConf() { + return config; + } + + public void setConf(String config) { + this.config = config; + } + + public String getRequestUrlRegex() { + return requestUrlRegex; + } + + public void setRequestUrlRegex(String requestUrlregex) { + this.requestUrlRegex = requestUrlregex; + requestUrlPattern = compileRegex(requestUrlregex); + } + + private static Pattern compileRegex(String regex) { + if (regex == null || regex.isEmpty()) { + return null; + } + return Pattern.compile(regex, PATTERN_SCAN); + } + + public String getRequestHeaderRegex() { + return requestHeaderRegex; + } + + public void setRequestHeaderRegex(String requestHeaderRegex) { + this.requestHeaderRegex = requestHeaderRegex; + requestHeaderPattern = compileRegex(requestHeaderRegex); + } + + public String getResponseHeaderRegex() { + return responseHeaderRegex; + } + + public void setResponseHeaderRegex(String responseHeaderRegex) { + this.responseHeaderRegex = responseHeaderRegex; + responseHeaderPattern = compileRegex(responseHeaderRegex); + } + + public String getResponseBodyRegex() { + return responseBodyRegex; + } + + public void setResponseBodyRegex(String responseBodyRegex) { + this.responseBodyRegex = responseBodyRegex; + responseBodyPattern = compileRegex(responseBodyRegex); + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public void scanHttpRequestSend(HttpMessage msg, int id) { + if (!this.isEnabled()) { + return; + } + if (getRequestHeaderPattern() != null) { + Matcher m = getRequestHeaderPattern().matcher(msg.getRequestHeader().toString()); + if (m.find()) { + // Scanner matches, so do what it wants... + matched(m, msg, id); + return; + } + } + if (getRequestUrlPattern() != null) { + Matcher m = getRequestUrlPattern().matcher(msg.getRequestHeader().getURI().toString()); + if (m.find()) { + // Scanner matches, so do what it wants... + matched(m, msg, id); + return; + } + } + } + + public Alert getAlert(HttpMessage msg) { + return null; + } + + @Override + public void scanHttpResponseReceive(HttpMessage msg, int id, Source source) { + if (!this.isEnabled()) { + return; + } + if (getResponseHeaderPattern() != null) { + Matcher m = getResponseHeaderPattern().matcher(msg.getResponseHeader().toString()); + if (m.find()) { + // Scanner matches, so do what it wants... + matched(m, msg, id); + return; + } + } + if (getResponseBodyPattern() != null) { + Matcher m = getResponseBodyPattern().matcher(msg.getResponseBody().toString()); + if (m.find()) { + // Scanner matches, so do what it wants... + matched(m, msg, id); + return; + } + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((config == null) ? 0 : config.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = + prime * result + ((requestHeaderRegex == null) ? 0 : requestHeaderRegex.hashCode()); + result = prime * result + ((requestUrlRegex == null) ? 0 : requestUrlRegex.hashCode()); + result = prime * result + ((responseBodyRegex == null) ? 0 : responseBodyRegex.hashCode()); + result = + prime * result + + ((responseHeaderRegex == null) ? 0 : responseHeaderRegex.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!super.equals(object)) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + RegexAutoTagScanner other = (RegexAutoTagScanner) object; + if (config == null) { + if (other.config != null) { + return false; + } + } else if (!config.equals(other.config)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (requestHeaderRegex == null) { + if (other.requestHeaderRegex != null) { + return false; + } + } else if (!requestHeaderRegex.equals(other.requestHeaderRegex)) { + return false; + } + if (requestUrlRegex == null) { + if (other.requestUrlRegex != null) { + return false; + } + } else if (!requestUrlRegex.equals(other.requestUrlRegex)) { + return false; + } + if (responseBodyRegex == null) { + if (other.responseBodyRegex != null) { + return false; + } + } else if (!responseBodyRegex.equals(other.responseBodyRegex)) { + return false; + } + if (responseHeaderRegex == null) { + if (other.responseHeaderRegex != null) { + return false; + } + } else if (!responseHeaderRegex.equals(other.responseHeaderRegex)) { + return false; + } + if (type != other.type) { + return false; + } + return true; + } + + private void matched(Matcher matcher, HttpMessage msg, int id) { + String tag = getConf(); + if (tagHistoryType(msg.getHistoryRef().getHistoryType())) { + if (matcher.groupCount() > 0) { + tag = matcher.pattern().matcher(matcher.group()).replaceFirst(tag); + } + addHistoryTag(tag); + } + + try { + Stats.incCounter(SessionStructure.getHostName(msg), TAG_STATS_PREFIX + this.getConf()); + } catch (URIException e) { + // Ignore + } + } + + private boolean tagHistoryType(int historyType) { + return PluginPassiveScanner.getDefaultHistoryTypes().contains(historyType); + } + + @Override + public boolean appliesToHistoryType(int historyType) { + return true; + } +} diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ScanRuleManager.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ScanRuleManager.java index cd5018697ea..f3ec23cf715 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ScanRuleManager.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ScanRuleManager.java @@ -26,11 +26,11 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.zaproxy.addon.pscan.PassiveScannersManager; import org.zaproxy.zap.extension.pscan.PassiveScanner; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; -import org.zaproxy.zap.extension.pscan.scanner.RegexAutoTagScanner; -public class ScanRuleManager { +public class ScanRuleManager implements PassiveScannersManager { private static final Logger LOGGER = LogManager.getLogger(ScanRuleManager.class); @@ -39,6 +39,7 @@ public class ScanRuleManager { public ScanRuleManager() {} + @Override public boolean add(PassiveScanner scanRule) { if (scanRule == null) { throw new IllegalArgumentException("Parameter must not be null."); @@ -71,25 +72,34 @@ private boolean addPluginPassiveScannerImpl(PluginPassiveScanner scanner) { } private boolean addPassiveScannerImpl(PassiveScanner passiveScanner) { + String name = passiveScanner.getName(); + if (scannerNames.contains(name)) { + LOGGER.error("Duplicate passive scan rule name: {}", passiveScanner.getName()); + return false; + } + scannerNames.add(name); return scanRules.add(passiveScanner); } - public PassiveScanner getScanRule(int id) { + @Override + public PluginPassiveScanner getScanRule(int id) { for (PassiveScanner scanner : scanRules) { if (scanner instanceof PluginPassiveScanner) { if (((PluginPassiveScanner) scanner).getPluginId() == id) { - return scanner; + return (PluginPassiveScanner) scanner; } } } return null; } - public List getScanRules() { + @Override + public List getScanners() { return scanRules; } - public List getPluginScanRules() { + @Override + public List getScanRules() { List pluginPassiveScanners = new ArrayList<>(); for (PassiveScanner scanner : scanRules) { if ((scanner instanceof PluginPassiveScanner) @@ -100,6 +110,7 @@ public List getPluginScanRules() { return pluginPassiveScanners; } + @Override public boolean remove(PassiveScanner scanRule) { if (scanRule == null) { throw new IllegalArgumentException("Parameter must not be null."); diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanController.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanController.java new file mode 100644 index 00000000000..8c70213723f --- /dev/null +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanController.java @@ -0,0 +1,302 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2022 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal.scanner; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.db.DatabaseException; +import org.parosproxy.paros.extension.history.ExtensionHistory; +import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.model.Session; +import org.parosproxy.paros.network.HttpMalformedHeaderException; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; +import org.zaproxy.zap.extension.alert.ExtensionAlert; +import org.zaproxy.zap.extension.pscan.PassiveScanData; +import org.zaproxy.zap.extension.pscan.PassiveScanner; +import org.zaproxy.zap.utils.Stats; + +public class PassiveScanController extends Thread { + + private static final Logger LOGGER = LogManager.getLogger(PassiveScanController.class); + + private Constructor pscanDataConstructor; + private Consumer setPscanActions; + + private ExtensionHistory extHist; + private PassiveScanTaskHelper helper; + private Session session; + + private ThreadPoolExecutor executor; + + private int currentId = 1; + private int lastId = -1; + private int mainSleep = 2000; + private int postSleep = 200; + private volatile boolean shutDown = false; + + public PassiveScanController( + ExtensionPassiveScan2 extPscan, ExtensionHistory extHistory, ExtensionAlert extAlert) { + setName("ZAP-PassiveScanController"); + this.extHist = extHistory; + + helper = new PassiveScanTaskHelper(extPscan, extAlert); + + // Get the last id - in case we've just opened an existing session + currentId = getLastHistoryId(); + lastId = currentId; + + try { + pscanDataConstructor = PassiveScanData.class.getConstructor(HttpMessage.class); + } catch (Exception e) { + // Ignore, the constructor exists but was previously not visible. + } + + try { + InvocationHandler invocationHandler = + (o, method, args) -> { + switch (method.getName()) { + case "addHistoryTag": + helper.addHistoryTag((HistoryReference) args[0], (String) args[1]); + return null; + + case "raiseAlert": + helper.raiseAlert((HistoryReference) args[0], (Alert) args[1]); + return null; + + default: + return null; + } + }; + + Class clazz = + org.zaproxy.zap.extension.pscan.ExtensionPassiveScan.class + .getClassLoader() + .loadClass("org.zaproxy.zap.extension.pscan.PassiveScanActions"); + Method setPassiveScanActions = + org.zaproxy.zap.extension.pscan.PassiveScanner.class.getDeclaredMethod( + "setPassiveScanActions", clazz); + Object passiveScanActions = + Proxy.newProxyInstance( + clazz.getClassLoader(), new Class[] {clazz}, invocationHandler); + + setPscanActions = + scanRule -> { + try { + setPassiveScanActions.invoke(scanRule, passiveScanActions); + } catch (Exception e) { + // New core method exists. + } + }; + + } catch (Exception e) { + LOGGER.error("Failed to create PassiveScanActions:", e); + } + } + + public void setSession(Session session) { + this.session = session; + } + + @Override + public void run() { + LOGGER.debug("Starting passive scan monitoring"); + try { + scan(); + } finally { + LOGGER.debug("Stopping passive scan monitoring"); + } + } + + private void scan() { + // Get the last id - in case we've just opened an existing session + currentId = this.getLastHistoryId(); + lastId = currentId; + + // Prevent re-scanning of existing message. + if (currentId != 0) { + currentId++; + } + HistoryReference href = null; + + while (!shutDown) { + try { + if (href != null || lastId > currentId) { + currentId++; + } else { + // Either just started or there are no new records + try { + Thread.sleep(mainSleep); + if (shutDown) { + return; + } + } catch (InterruptedException e) { + // New URL, but give it a chance to be processed first + try { + Thread.sleep(postSleep); + } catch (InterruptedException e2) { + // Ignore + } + } + lastId = this.getLastHistoryId(); + } + href = getHistoryReference(currentId); + + if (shutDown) { + return; + } + + if (href != null + && (!getOptions().isScanOnlyInScope() || session.isInScope(href))) { + LOGGER.debug( + "Submitting request to executor: {} id {} type {}", + href.getURI(), + currentId, + href.getHistoryType()); + getExecutor() + .submit( + new PassiveScanTask( + href, helper, pscanDataConstructor, setPscanActions)); + } + int recordsToScan = this.getRecordsToScan(); + Stats.setHighwaterMark("stats.pscan.recordsToScan", recordsToScan); + + } catch (Exception e) { + if (shutDown) { + return; + } + if (href != null + && HistoryReference.getTemporaryTypes().contains(href.getHistoryType())) { + LOGGER.debug("Temporary record {} no longer available:", currentId, e); + } else { + LOGGER.error("Failed on record {} from History table", currentId, e); + } + } + } + } + + private PassiveScannerOptions getOptions() { + return extHist.getModel().getOptionsParam().getParamSet(PassiveScannerOptions.class); + } + + private ThreadPoolExecutor getExecutor() { + if (this.executor == null || this.executor.isShutdown()) { + int threads = getOptions().getPassiveScanThreads(); + LOGGER.debug("Creating new executor with {} threads", threads); + + this.executor = + (ThreadPoolExecutor) + Executors.newFixedThreadPool( + threads, new PassiveScanThreadFactory("ZAP-PassiveScan-")); + } + return this.executor; + } + + private HistoryReference getHistoryReference(final int historyReferenceId) { + if (extHist != null) { + return extHist.getHistoryReference(historyReferenceId); + } + + try { + return new HistoryReference(historyReferenceId); + } catch (HttpMalformedHeaderException | DatabaseException e) { + return null; + } + } + + private int getLastHistoryId() { + return this.extHist.getLastHistoryId(); + } + + public int getRecordsToScan() { + return this.getLastHistoryId() - getLastScannedId() + helper.getRunningTasks().size(); + } + + private int getLastScannedId() { + if (currentId > lastId) { + return currentId - 1; + } + return currentId; + } + + public void shutdown() { + LOGGER.debug("Shutdown"); + this.shutDown = true; + if (this.executor != null) { + this.executor.shutdown(); + } + this.helper.shutdownTasks(); + } + + public List getRunningTasks() { + return this.helper.getRunningTasks(); + } + + public PassiveScanTask getOldestRunningTask() { + return this.helper.getOldestRunningTask(); + } + + public void clearQueue() { + currentId = this.getLastHistoryId(); + lastId = currentId; + this.helper.shutdownTasks(); + } + + public void responseReceived() { + this.interrupt(); + } + + private static class PassiveScanThreadFactory implements ThreadFactory { + + private final AtomicInteger threadNumber; + private final String namePrefix; + private final ThreadGroup group; + + public PassiveScanThreadFactory(String namePrefix) { + threadNumber = new AtomicInteger(1); + this.namePrefix = namePrefix; + group = Thread.currentThread().getThreadGroup(); + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + if (t.isDaemon()) { + t.setDaemon(false); + } + if (t.getPriority() != Thread.NORM_PRIORITY - 1) { + t.setPriority(Thread.NORM_PRIORITY - 1); + } + return t; + } + } +} diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTask.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTask.java new file mode 100644 index 00000000000..cf0effd0ba3 --- /dev/null +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTask.java @@ -0,0 +1,231 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal.scanner; + +import java.lang.reflect.Constructor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import net.htmlparser.jericho.Source; +import org.apache.commons.httpclient.URI; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.db.DatabaseException; +import org.parosproxy.paros.db.RecordHistory; +import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMalformedHeaderException; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.extension.pscan.PassiveScanData; +import org.zaproxy.zap.extension.pscan.PassiveScanner; +import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; +import org.zaproxy.zap.utils.Stats; + +/** A class which runs all of the enabled passive scanners against a specified HistoryReference */ +public class PassiveScanTask implements Runnable { + + private final Constructor pscanDataConstructor; + private final Consumer pscanActionsSetter; + + private HistoryReference href; + + private PassiveScanTaskHelper helper; + + private int maxBodySize; + private Boolean completed = null; + private boolean shutdown = false; + private PassiveScanner currentScanner; + private long startTime; + private long stopTime; + + private static final Logger LOGGER = LogManager.getLogger(PassiveScanTask.class); + + public PassiveScanTask( + HistoryReference hr, + PassiveScanTaskHelper helper, + Constructor pscanDataConstructor, + Consumer pscanActionsSetter) { + this.href = hr; + this.helper = helper; + this.pscanDataConstructor = pscanDataConstructor; + this.pscanActionsSetter = pscanActionsSetter; + this.maxBodySize = helper.getMaxBodySizeInBytesToScan(); + helper.addTaskToList(this); + } + + public Boolean hasCompleted() { + return completed; + } + + public void shutdown() { + this.shutdown = true; + } + + public PassiveScanner getCurrentScanner() { + return this.currentScanner; + } + + public URI getURI() { + return this.href.getURI(); + } + + public HistoryReference getHistoryReference() { + return this.href; + } + + public long getStartTime() { + return startTime; + } + + public long getStopTime() { + return stopTime; + } + + @Override + public void run() { + boolean scanned = false; + startTime = System.currentTimeMillis(); + + completed = false; + + try { + // Parse the record + HttpMessage msg = href.getHttpMessage(); + Source src = new Source(msg.getResponseBody().toString()); + PassiveScanData passiveScanData = pscanDataConstructor.newInstance(msg); + + for (PassiveScanner scanner : helper.getPassiveScanRuleManager().getScanners()) { + currentScanner = scanner; + try { + if (shutdown) { + return; + } + int hrefHistoryType = href.getHistoryType(); + if (scanner.isEnabled() + && (scanner.appliesToHistoryType(hrefHistoryType) + || PassiveScanTaskHelper.getOptedInHistoryTypes() + .contains(hrefHistoryType))) { + + if (scanner instanceof PluginPassiveScanner) { + PluginPassiveScanner pps = ((PluginPassiveScanner) scanner).copy(); + pps.setHelper(passiveScanData); + scanner = pps; + } + pscanActionsSetter.accept(scanner); + + LOGGER.debug( + "Running scan rule, URL {} plugin {}", + msg.getRequestHeader().getURI(), + scanner.getName()); + long scanRuleStartTime = System.currentTimeMillis(); + + if (maxBodySize <= 0 || msg.getRequestBody().length() < maxBodySize) { + scanner.scanHttpRequestSend(msg, href.getHistoryId()); + scanned = true; + } else { + Stats.incCounter("stats.pscan.reqBodyTooBig"); + LOGGER.debug( + "Request to {} body size {} larger than max configured {}", + msg.getRequestHeader().getURI(), + msg.getRequestBody().length(), + maxBodySize); + } + if (msg.isResponseFromTargetHost()) { + if (maxBodySize <= 0 || msg.getResponseBody().length() < maxBodySize) { + scanner.scanHttpResponseReceive(msg, href.getHistoryId(), src); + scanned = true; + } else { + Stats.incCounter("stats.pscan.respBodyTooBig"); + LOGGER.debug( + "Response from {} body size {} larger than max configured {}", + msg.getRequestHeader().getURI(), + msg.getResponseBody().length(), + maxBodySize); + } + } + if (scanned) { + long timeTaken = System.currentTimeMillis() - scanRuleStartTime; + if (scanner instanceof PluginPassiveScanner) { + PluginPassiveScanner pps = (PluginPassiveScanner) scanner; + Stats.incCounter( + "stats.pscan." + pps.getPluginId() + ".time", timeTaken); + } + // TODO remove at some point + Stats.incCounter("stats.pscan." + scanner.getName(), timeTaken); + if (timeTaken > 5000) { + // Took over 5 seconds, thats not ideal + String responseInfo = ""; + if (msg.isResponseFromTargetHost()) { + responseInfo = + msg.getResponseHeader() + .getHeader(HttpHeader.CONTENT_TYPE) + + " " + + msg.getResponseBody().length(); + } + LOGGER.warn( + "Passive Scan rule {} took {} seconds to scan {} {}", + scanner.getName(), + TimeUnit.MILLISECONDS.toSeconds(timeTaken), + msg.getRequestHeader().getURI(), + responseInfo); + } + } + } + } catch (Exception e) { + LOGGER.error( + "Scan rule '{}' failed on record {} from History table: {} {}", + scanner.getName(), + href.getHistoryId(), + href.getMethod(), + href.getURI(), + e); + } + } + + } catch (Exception e) { + if (HistoryReference.getTemporaryTypes().contains(href.getHistoryType())) { + LOGGER.debug("Temporary record {} no longer available:", href.getHistoryId(), e); + } else { + RecordHistory rec = null; + try { + rec = Model.getSingleton().getDb().getTableHistory().read(href.getHistoryId()); + } catch (HttpMalformedHeaderException | DatabaseException e2) { + // Ignore + } + if (rec == null) { + return; + } + LOGGER.error( + "Parser failed on record {} from History table", href.getHistoryId(), e); + HttpMessage msg; + try { + msg = href.getHttpMessage(); + LOGGER.error("Req Header {}", msg.getRequestHeader(), e); + } catch (Exception e1) { + // Ignore + } + } + } finally { + completed = true; + stopTime = System.currentTimeMillis(); + helper.removeTaskFromList(this); + } + } +} diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTaskHelper.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTaskHelper.java new file mode 100644 index 00000000000..1378286d14c --- /dev/null +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanTaskHelper.java @@ -0,0 +1,233 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal.scanner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import net.htmlparser.jericho.MasonTagTypes; +import net.htmlparser.jericho.MicrosoftConditionalCommentTagTypes; +import net.htmlparser.jericho.PHPTagTypes; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.model.HistoryReference; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.PassiveScannersManager; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; +import org.zaproxy.zap.extension.alert.ExtensionAlert; +import org.zaproxy.zap.extension.pscan.PassiveScanner; +import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; + +public class PassiveScanTaskHelper { + + private static final Logger LOGGER = LogManager.getLogger(PassiveScanTaskHelper.class); + + private static Set optedInHistoryTypes = new HashSet<>(); + + private volatile boolean shutDown = false; + + private final ExtensionPassiveScan2 extPscan; + private final ExtensionAlert extAlert; + private Map alertCounts = new HashMap<>(); + + private List activeList = Collections.synchronizedList(new ArrayList<>()); + private List taskList = Collections.synchronizedList(new ArrayList<>()); + + public PassiveScanTaskHelper(ExtensionPassiveScan2 extPscan, ExtensionAlert extensionAlert) { + + if (extensionAlert == null) { + throw new IllegalArgumentException("Parameter extensionAlert must not be null."); + } + + this.extPscan = extPscan; + this.extAlert = extensionAlert; + + MicrosoftConditionalCommentTagTypes.register(); + PHPTagTypes.register(); + // remove PHP short tags otherwise they override processing + PHPTagTypes.PHP_SHORT.deregister(); + MasonTagTypes.register(); + } + + public void addActivePassiveScanner(PassiveScanner scanner) { + this.activeList.add(scanner); + } + + public void removeActivePassiveScanner(PassiveScanner scanner) { + this.activeList.remove(scanner); + } + + public synchronized void addTaskToList(PassiveScanTask task) { + this.taskList.add(task); + } + + public synchronized void removeTaskFromList(PassiveScanTask task) { + this.taskList.remove(task); + } + + public int getTaskListSize() { + return this.taskList.size(); + } + + public synchronized void shutdownTasks() { + this.taskList.stream().forEach(PassiveScanTask::shutdown); + } + + public synchronized PassiveScanTask getOldestRunningTask() { + for (PassiveScanTask task : this.taskList) { + if (Boolean.FALSE.equals(task.hasCompleted())) { + return task; + } + } + return null; + } + + public synchronized List getRunningTasks() { + return this.taskList.stream() + .filter(task -> Boolean.FALSE.equals(task.hasCompleted())) + .collect(Collectors.toList()); + } + + public synchronized PassiveScanner getOldestRunningScanner() { + PassiveScanner scanner; + for (PassiveScanTask task : this.taskList) { + if (Boolean.FALSE.equals(task.hasCompleted())) { + scanner = task.getCurrentScanner(); + if (scanner != null) { + return scanner; + } + } + } + return null; + } + + PassiveScannersManager getPassiveScanRuleManager() { + return extPscan.getPassiveScannersManager(); + } + + public int getMaxBodySizeInBytesToScan() { + return getOptions().getMaxBodySizeInBytesToScan(); + } + + private PassiveScannerOptions getOptions() { + return extPscan.getModel().getOptionsParam().getParamSet(PassiveScannerOptions.class); + } + + public void raiseAlert(HistoryReference href, Alert alert) { + if (shutDown) { + return; + } + + alert.setSource(Alert.Source.PASSIVE); + // Raise the alert + extAlert.alertFound(alert, href); + + int maxAlertsPerRule = getOptions().getMaxAlertsPerRule(); + if (maxAlertsPerRule > 0) { + // Theres a limit on how many each rule can raise + Integer count = alertCounts.get(alert.getPluginId()); + if (count == null) { + count = Integer.valueOf(0); + } + alertCounts.put(alert.getPluginId(), count + 1); + if (count > maxAlertsPerRule) { + // Disable the plugin + PassiveScanner scanner = + getPassiveScanRuleManager().getScanRule(alert.getPluginId()); + if (scanner != null) { + LOGGER.info( + "Disabling passive scan rule {} as it has raised more than {} alerts.", + scanner.getName(), + maxAlertsPerRule); + scanner.setEnabled(false); + } + } + } + } + + /** + * Adds the given tag to the specified message. + * + * @param tag the name of the tag. + */ + public void addHistoryTag(HistoryReference href, String tag) { + if (shutDown) { + return; + } + + try { + if (!href.getTags().contains(tag)) { + href.addTag(tag); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + } + + /** + * Add the History Type ({@code int}) to the set of applicable history types. + * + * @param type the type to be added to the set of applicable history types + */ + public static void addApplicableHistoryType(int type) { + optedInHistoryTypes.add(type); + } + + /** + * Remove the History Type ({@code int}) from the set of applicable history types. + * + * @param type the type to be removed from the set of applicable history types + */ + public static void removeApplicableHistoryType(int type) { + optedInHistoryTypes.remove(type); + } + + /** + * Returns the set of History Types which have "opted-in" to be applicable for passive scanning. + * + * @return a set of {@code Integer} representing all of the History Types which have "opted-in" + * for passive scanning. + */ + public static Set getOptedInHistoryTypes() { + return Collections.unmodifiableSet(optedInHistoryTypes); + } + + /** + * Returns the full set (both default and "opted-in") which are to be applicable for passive + * scanning. + * + * @return a set of {@code Integer} representing all of the History Types which are applicable + * for passive scanning. + */ + public static Set getApplicableHistoryTypes() { + Set allApplicableTypes = new HashSet<>(); + allApplicableTypes.addAll(PluginPassiveScanner.getDefaultHistoryTypes()); + if (!optedInHistoryTypes.isEmpty()) { + allApplicableTypes.addAll(optedInHistoryTypes); + } + return allApplicableTypes; + } +} diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogAddAutoTagScanner.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogAddAutoTagScanner.java index 79e5770c9bd..097119248f0 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogAddAutoTagScanner.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogAddAutoTagScanner.java @@ -30,7 +30,7 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.parosproxy.paros.Constant; -import org.zaproxy.zap.extension.pscan.scanner.RegexAutoTagScanner; +import org.zaproxy.addon.pscan.internal.RegexAutoTagScanner; import org.zaproxy.zap.utils.ZapTextField; import org.zaproxy.zap.view.AbstractFormDialog; diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogModifyAutoTagScanner.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogModifyAutoTagScanner.java index 911b58db5a5..d38ebb408eb 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogModifyAutoTagScanner.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/DialogModifyAutoTagScanner.java @@ -21,7 +21,7 @@ import java.awt.Dialog; import org.parosproxy.paros.Constant; -import org.zaproxy.zap.extension.pscan.scanner.RegexAutoTagScanner; +import org.zaproxy.addon.pscan.internal.RegexAutoTagScanner; class DialogModifyAutoTagScanner extends DialogAddAutoTagScanner { diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScan.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScan.java index 17348d6a55a..b3eb0baf9ce 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScan.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScan.java @@ -28,9 +28,9 @@ import org.parosproxy.paros.model.OptionsParam; import org.parosproxy.paros.view.AbstractParamPanel; import org.parosproxy.paros.view.View; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; +import org.zaproxy.addon.pscan.internal.RegexAutoTagScanner; import org.zaproxy.addon.pscan.internal.ScanRuleManager; -import org.zaproxy.zap.extension.pscan.PassiveScanParam; -import org.zaproxy.zap.extension.pscan.scanner.RegexAutoTagScanner; import org.zaproxy.zap.utils.ZapHtmlLabel; import org.zaproxy.zap.view.AbstractMultipleOptionsTablePanel; @@ -69,20 +69,18 @@ public OptionsPassiveScan(ScanRuleManager scanRuleManager) { @Override public void initParam(Object obj) { OptionsParam optionsParam = (OptionsParam) obj; - PassiveScanParam passiveScanParam = optionsParam.getParamSet(PassiveScanParam.class); - tableModel.setScanDefns(passiveScanParam.getAutoTagScanners()); - scannersOptionsPanel.setRemoveWithoutConfirmation( - !passiveScanParam.isConfirmRemoveAutoTagScanner()); + PassiveScannerOptions options = optionsParam.getParamSet(PassiveScannerOptions.class); + tableModel.setScanDefns(options.getAutoTagScanners()); + scannersOptionsPanel.setRemoveWithoutConfirmation(!options.isConfirmRemoveAutoTagScanner()); } @Override public void saveParam(Object obj) throws Exception { OptionsParam optionsParam = (OptionsParam) obj; - PassiveScanParam passiveScanParam = optionsParam.getParamSet(PassiveScanParam.class); - passiveScanParam.setAutoTagScanners(tableModel.getElements()); - passiveScanParam.setConfirmRemoveAutoTagScanner( - !scannersOptionsPanel.isRemoveWithoutConfirmation()); - scanRuleManager.setAutoTagScanners(passiveScanParam.getAutoTagScanners()); + PassiveScannerOptions options = optionsParam.getParamSet(PassiveScannerOptions.class); + options.setAutoTagScanners(tableModel.getElements()); + options.setConfirmRemoveAutoTagScanner(!scannersOptionsPanel.isRemoveWithoutConfirmation()); + scanRuleManager.setAutoTagScanners(options.getAutoTagScanners()); } @Override diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScanTableModel.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScanTableModel.java index a0e262ad1b2..378faa8fcb9 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScanTableModel.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/OptionsPassiveScanTableModel.java @@ -22,7 +22,7 @@ import java.util.ArrayList; import java.util.List; import org.parosproxy.paros.Constant; -import org.zaproxy.zap.extension.pscan.scanner.RegexAutoTagScanner; +import org.zaproxy.addon.pscan.internal.RegexAutoTagScanner; import org.zaproxy.zap.view.AbstractMultipleOptionsTableModel; @SuppressWarnings("serial") diff --git a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/PassiveScannerOptionsPanel.java b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/PassiveScannerOptionsPanel.java index 9bfa3c5c165..9f9193b0b07 100644 --- a/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/PassiveScannerOptionsPanel.java +++ b/addOns/pscan/src/main/java/org/zaproxy/addon/pscan/internal/ui/PassiveScannerOptionsPanel.java @@ -26,8 +26,7 @@ import org.parosproxy.paros.Constant; import org.parosproxy.paros.model.OptionsParam; import org.parosproxy.paros.view.AbstractParamPanel; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; -import org.zaproxy.zap.extension.pscan.PassiveScanParam; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; import org.zaproxy.zap.utils.I18N; import org.zaproxy.zap.utils.ZapHtmlLabel; import org.zaproxy.zap.utils.ZapNumberSpinner; @@ -44,7 +43,7 @@ public class PassiveScannerOptionsPanel extends AbstractParamPanel { private final ZapNumberSpinner maxBodySizeInBytes; private final JButton clearQueue; - public PassiveScannerOptionsPanel(ExtensionPassiveScan extPassiveScan, I18N messages) { + public PassiveScannerOptionsPanel(Runnable queueClearer, I18N messages) { setName(messages.getString("pscan.options.main.name")); scanOnlyInScopeCheckBox = @@ -55,7 +54,7 @@ public PassiveScannerOptionsPanel(ExtensionPassiveScan extPassiveScan, I18N mess maxAlertsPerRule = new ZapNumberSpinner(); maxBodySizeInBytes = new ZapNumberSpinner(); clearQueue = new JButton(messages.getString("pscan.options.main.label.clearQueue")); - clearQueue.addActionListener(al -> extPassiveScan.clearQueue()); + clearQueue.addActionListener(al -> queueClearer.run()); setLayout(new GridBagLayout()); @@ -91,7 +90,7 @@ public PassiveScannerOptionsPanel(ExtensionPassiveScan extPassiveScan, I18N mess @Override public void initParam(Object obj) { OptionsParam optionsParam = (OptionsParam) obj; - PassiveScanParam pscanOptions = optionsParam.getParamSet(PassiveScanParam.class); + PassiveScannerOptions pscanOptions = optionsParam.getParamSet(PassiveScannerOptions.class); scanOnlyInScopeCheckBox.setSelected(pscanOptions.isScanOnlyInScope()); scanFuzzerMessagesCheckBox.setSelected(pscanOptions.isScanFuzzerMessages()); @@ -103,7 +102,7 @@ public void initParam(Object obj) { @Override public void saveParam(Object obj) throws Exception { OptionsParam optionsParam = (OptionsParam) obj; - PassiveScanParam pscanOptions = optionsParam.getParamSet(PassiveScanParam.class); + PassiveScannerOptions pscanOptions = optionsParam.getParamSet(PassiveScannerOptions.class); pscanOptions.setScanOnlyInScope(scanOnlyInScopeCheckBox.isSelected()); pscanOptions.setScanFuzzerMessages(scanFuzzerMessagesCheckBox.isSelected()); diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/PassiveScanApiUnitTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/PassiveScanApiUnitTest.java index 287b88b3b10..e9aa8b31fe9 100644 --- a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/PassiveScanApiUnitTest.java +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/PassiveScanApiUnitTest.java @@ -40,27 +40,25 @@ import org.junit.jupiter.params.provider.ValueSource; import org.parosproxy.paros.Constant; import org.parosproxy.paros.network.HttpMessage; -import org.zaproxy.addon.pscan.internal.ScanRuleManager; import org.zaproxy.zap.extension.api.API; import org.zaproxy.zap.extension.api.API.RequestType; import org.zaproxy.zap.extension.api.ApiElement; import org.zaproxy.zap.extension.api.ApiException; import org.zaproxy.zap.extension.api.ApiImplementor; import org.zaproxy.zap.extension.api.ApiParameter; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.testutils.TestUtils; /** Unit test for {@link PassiveScanApi}. */ class PassiveScanApiUnitTest extends TestUtils { private PassiveScanApi pscanApi; - private ScanRuleManager scanRuleManager; - private ExtensionPassiveScan extension; + private PassiveScannersManager scannersManager; + private ExtensionPassiveScan2 extension; @BeforeEach void setUp() { mockMessages(new ExtensionPassiveScan2()); - pscanApi = new PassiveScanApi(extension, scanRuleManager); + pscanApi = new PassiveScanApi(extension, scannersManager); } @AfterAll @@ -79,7 +77,7 @@ void shouldHavePrefix() throws Exception { @Test void shouldAddApiElements() { // Given / When - pscanApi = new PassiveScanApi(extension, scanRuleManager); + pscanApi = new PassiveScanApi(extension, scannersManager); // Then assertThat(pscanApi.getApiActions(), hasSize(11)); assertThat(pscanApi.getApiViews(), hasSize(6)); @@ -142,7 +140,7 @@ void shouldThrowApiExceptionForUnknownView(String name) throws Exception { @Test void shouldHaveDescriptionsForAllApiElements() { - pscanApi = new PassiveScanApi(extension, scanRuleManager); + pscanApi = new PassiveScanApi(extension, scannersManager); List issues = new ArrayList<>(); checkKey(pscanApi.getDescriptionKey(), issues); checkApiElements(pscanApi, pscanApi.getApiActions(), API.RequestType.action, issues); diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJobUnitTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJobUnitTest.java index c7e0b641216..949468d5b07 100644 --- a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJobUnitTest.java +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanConfigJobUnitTest.java @@ -21,26 +21,21 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.withSettings; +import static org.mockito.Mockito.verify; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.quality.Strictness; -import org.parosproxy.paros.CommandLine; +import org.mockito.InOrder; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.core.scanner.Plugin.AlertThreshold; @@ -52,49 +47,40 @@ import org.zaproxy.addon.automation.AutomationPlan; import org.zaproxy.addon.automation.AutomationProgress; import org.zaproxy.addon.automation.jobs.JobUtils; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; -import org.zaproxy.zap.extension.pscan.PassiveScanParam; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.PassiveScannersManager; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; +import org.zaproxy.addon.pscan.internal.RegexAutoTagScanner; import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; -import org.zaproxy.zap.extension.pscan.scanner.RegexAutoTagScanner; import org.zaproxy.zap.utils.I18N; -import org.zaproxy.zap.utils.ZapXmlConfiguration; class PassiveScanConfigJobUnitTest { - private static MockedStatic mockedCmdLine; - - private ExtensionLoader extensionLoader; - private ExtensionPassiveScan extPscan; - private PassiveScanParam pscanParam; - - @BeforeAll - static void init() { - mockedCmdLine = Mockito.mockStatic(CommandLine.class); - } - - @AfterAll - static void close() { - mockedCmdLine.close(); - } + private PassiveScannersManager scannersManager; + private ExtensionPassiveScan2 pscan; + private PassiveScannerOptions options; @BeforeEach void setUp() throws Exception { Constant.messages = new I18N(Locale.ENGLISH); - Model model = mock(Model.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); + Model model = mock(Model.class); Model.setSingletonForTesting(model); OptionsParam optionsParam = mock(OptionsParam.class); given(model.getOptionsParam()).willReturn(optionsParam); - pscanParam = mock(PassiveScanParam.class); - given(optionsParam.getParamSet(PassiveScanParam.class)).willReturn(pscanParam); + options = mock(PassiveScannerOptions.class); + given(optionsParam.getParamSet(PassiveScannerOptions.class)).willReturn(options); - extensionLoader = - mock(ExtensionLoader.class, withSettings().strictness(Strictness.LENIENT)); - extPscan = new ExtensionPassiveScan(); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extPscan); + pscan = mock(ExtensionPassiveScan2.class); + scannersManager = mock(PassiveScannersManager.class); + given(pscan.getPassiveScannersManager()).willReturn(scannersManager); - Control.initSingletonForTesting(Model.getSingleton(), extensionLoader); - Model.getSingleton().getOptionsParam().load(new ZapXmlConfiguration()); + given(pscan.getModel()).willReturn(model); + + ExtensionLoader extensionLoader = mock(ExtensionLoader.class); + given(extensionLoader.getExtension(ExtensionPassiveScan2.class)).willReturn(pscan); + + Control.initSingletonForTesting(model, extensionLoader); } @Test @@ -106,8 +92,8 @@ void shouldReturnDefaultFields() { assertThat(job.getType(), is(equalTo("passiveScan-config"))); assertThat(job.getName(), is(equalTo("passiveScan-config"))); assertThat(job.getOrder(), is(equalTo(Order.CONFIGS))); - assertThat(job.getParamMethodObject(), is(extPscan)); - assertThat(job.getParamMethodName(), is("getPassiveScanParam")); + assertThat(job.getParamMethodObject(), is(job)); + assertThat(job.getParamMethodName(), is("getPassiveScannerOptions")); assertThat(job.getParameters().getEnableTags(), is(equalTo(false))); } @@ -135,11 +121,7 @@ void shouldGetOptions() { // Then assertThat(progress.hasErrors(), is(equalTo(false))); assertThat(progress.hasWarnings(), is(equalTo(false))); - assertThat(o, is(notNullValue())); - assertThat(o.getClass(), is(equalTo(PassiveScanParam.class))); - assertThat(((PassiveScanParam) o).getMaxAlertsPerRule(), is(equalTo(0))); - assertThat(((PassiveScanParam) o).isScanOnlyInScope(), is(equalTo(false))); - assertThat(((PassiveScanParam) o).getMaxBodySizeInBytesToScan(), is(equalTo(0))); + assertThat(o, is(instanceOf(PassiveScannerOptions.class))); } @Test @@ -156,8 +138,6 @@ void shouldApplyParameters() { Object data = yaml.load(yamlStr); PassiveScanConfigJob job = new PassiveScanConfigJob(); - PassiveScanParam psp = (PassiveScanParam) JobUtils.getJobOptions(job, progress); - psp.load(new ZapXmlConfiguration()); // When job.setJobData(((LinkedHashMap) data)); @@ -167,9 +147,9 @@ void shouldApplyParameters() { // Then assertThat(progress.hasErrors(), is(equalTo(false))); assertThat(progress.hasWarnings(), is(equalTo(false))); - assertThat(psp.getMaxAlertsPerRule(), is(equalTo(2))); - assertThat(psp.isScanOnlyInScope(), is(equalTo(true))); - assertThat(psp.getMaxBodySizeInBytesToScan(), is(equalTo(1000))); + verify(options).setMaxAlertsPerRule(2); + verify(options).setScanOnlyInScope(true); + verify(options).setMaxBodySizeInBytesToScan(1000); assertThat(job.getParameters().getEnableTags(), is(equalTo(true))); } @@ -187,11 +167,11 @@ void shouldResetParameters() { PassiveScanConfigJob job = new PassiveScanConfigJob(); job.setPlan(mock(AutomationPlan.class)); - PassiveScanParam psp = (PassiveScanParam) JobUtils.getJobOptions(job, progress); - psp.load(new ZapXmlConfiguration()); - psp.setMaxBodySizeInBytesToScan(200); - psp.setScanOnlyInScope(false); - psp.setMaxAlertsPerRule(8); + given(options.getMaxBodySizeInBytesToScan()).willReturn(200); + given(options.isScanOnlyInScope()).willReturn(false); + given(options.getMaxAlertsPerRule()).willReturn(8); + + InOrder inOrder = inOrder(options); // When job.planStarted(); @@ -203,9 +183,12 @@ void shouldResetParameters() { // Then assertThat(progress.hasErrors(), is(equalTo(false))); assertThat(progress.hasWarnings(), is(equalTo(false))); - assertThat(psp.getMaxAlertsPerRule(), is(equalTo(8))); - assertThat(psp.isScanOnlyInScope(), is(equalTo(false))); - assertThat(psp.getMaxBodySizeInBytesToScan(), is(equalTo(200))); + inOrder.verify(options).setMaxAlertsPerRule(2); + inOrder.verify(options).setMaxBodySizeInBytesToScan(1000); + inOrder.verify(options).setScanOnlyInScope(true); + inOrder.verify(options).setMaxAlertsPerRule(8); + inOrder.verify(options).setMaxBodySizeInBytesToScan(200); + inOrder.verify(options).setScanOnlyInScope(false); } @Test @@ -221,8 +204,6 @@ void shouldWarnOnBadParameter() { Object data = yaml.load(yamlStr); PassiveScanConfigJob job = new PassiveScanConfigJob(); - PassiveScanParam psp = (PassiveScanParam) JobUtils.getJobOptions(job, progress); - psp.load(new ZapXmlConfiguration()); // When job.setJobData(((LinkedHashMap) data)); @@ -235,9 +216,9 @@ void shouldWarnOnBadParameter() { assertThat(progress.getWarnings().size(), is(equalTo(1))); assertThat( progress.getWarnings().get(0), is(equalTo("!automation.error.options.unknown!"))); - assertThat(psp.getMaxAlertsPerRule(), is(equalTo(2))); - assertThat(psp.isScanOnlyInScope(), is(equalTo(true))); - assertThat(psp.getMaxBodySizeInBytesToScan(), is(equalTo(1000))); + verify(options).setMaxAlertsPerRule(2); + verify(options).setScanOnlyInScope(true); + verify(options).setMaxBodySizeInBytesToScan(1000); } @Test @@ -246,15 +227,12 @@ void shouldSetRules() { String yamlStr = "rules:\n" + "- id: 1\n" + " threshold: Low\n" + "- id: 3\n" + " threshold: High"; - // Need to mock the extension for this test - extPscan = mock(ExtensionPassiveScan.class); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extPscan); TestPluginScanner rule1 = new TestPluginScanner(1); TestPluginScanner rule2 = new TestPluginScanner(2); TestPluginScanner rule3 = new TestPluginScanner(3); - given(extPscan.getPluginPassiveScanner(1)).willReturn(rule1); - given(extPscan.getPluginPassiveScanner(2)).willReturn(rule2); - given(extPscan.getPluginPassiveScanner(3)).willReturn(rule3); + given(scannersManager.getScanRule(1)).willReturn(rule1); + given(scannersManager.getScanRule(2)).willReturn(rule2); + given(scannersManager.getScanRule(3)).willReturn(rule3); AutomationProgress progress = new AutomationProgress(); Yaml yaml = new Yaml(); @@ -282,15 +260,12 @@ void shouldWarnOnUnknownRule() { String yamlStr = "rules:\n" + "- id: 4\n" + " threshold: Low\n" + "- id: 3\n" + " threshold: High"; - // Need to mock the extension for this test - extPscan = mock(ExtensionPassiveScan.class); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extPscan); TestPluginScanner rule1 = new TestPluginScanner(1); TestPluginScanner rule2 = new TestPluginScanner(2); TestPluginScanner rule3 = new TestPluginScanner(3); - given(extPscan.getPluginPassiveScanner(1)).willReturn(rule1); - given(extPscan.getPluginPassiveScanner(2)).willReturn(rule2); - given(extPscan.getPluginPassiveScanner(3)).willReturn(rule3); + given(scannersManager.getScanRule(1)).willReturn(rule1); + given(scannersManager.getScanRule(2)).willReturn(rule2); + given(scannersManager.getScanRule(3)).willReturn(rule3); AutomationProgress progress = new AutomationProgress(); Yaml yaml = new Yaml(); @@ -318,11 +293,8 @@ void shouldIgnoreRuleWithNoId() { String yamlStr = "rules:\n" + "- id:\n" + " threshold: Low\n" + "- id: 3\n" + " threshold: High"; - // Need to mock the extension for this test - extPscan = mock(ExtensionPassiveScan.class); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extPscan); TestPluginScanner rule3 = new TestPluginScanner(3); - given(extPscan.getPluginPassiveScanner(3)).willReturn(rule3); + given(scannersManager.getScanRule(3)).willReturn(rule3); AutomationProgress progress = new AutomationProgress(); Yaml yaml = new Yaml(); @@ -348,17 +320,14 @@ void shouldDisableAllRules() { // Given String yamlStr = "parameters:\n" + " disableAllRules: true"; - // Need to mock the extension for this test - extPscan = mock(ExtensionPassiveScan.class); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extPscan); TestPluginScanner rule1 = new TestPluginScanner(1); TestPluginScanner rule2 = new TestPluginScanner(2); TestPluginScanner rule3 = new TestPluginScanner(3); List allRules = Arrays.asList(rule1, rule2, rule3); - given(extPscan.getPluginPassiveScanner(1)).willReturn(rule1); - given(extPscan.getPluginPassiveScanner(2)).willReturn(rule2); - given(extPscan.getPluginPassiveScanner(3)).willReturn(rule3); - given(extPscan.getPluginPassiveScanners()).willReturn(allRules); + given(scannersManager.getScanRule(1)).willReturn(rule1); + given(scannersManager.getScanRule(2)).willReturn(rule2); + given(scannersManager.getScanRule(3)).willReturn(rule3); + given(scannersManager.getScanRules()).willReturn(allRules); AutomationProgress progress = new AutomationProgress(); Yaml yaml = new Yaml(); @@ -392,17 +361,14 @@ void shouldDisableAllRulesExceptSpecifiedOnes() { + "- id: 3\n" + " threshold: High"; - // Need to mock the extension for this test - extPscan = mock(ExtensionPassiveScan.class); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extPscan); TestPluginScanner rule1 = new TestPluginScanner(1); TestPluginScanner rule2 = new TestPluginScanner(2); TestPluginScanner rule3 = new TestPluginScanner(3); List allRules = Arrays.asList(rule1, rule2, rule3); - given(extPscan.getPluginPassiveScanner(1)).willReturn(rule1); - given(extPscan.getPluginPassiveScanner(2)).willReturn(rule2); - given(extPscan.getPluginPassiveScanner(3)).willReturn(rule3); - given(extPscan.getPluginPassiveScanners()).willReturn(allRules); + given(scannersManager.getScanRule(1)).willReturn(rule1); + given(scannersManager.getScanRule(2)).willReturn(rule2); + given(scannersManager.getScanRule(3)).willReturn(rule3); + given(scannersManager.getScanRules()).willReturn(allRules); AutomationProgress progress = new AutomationProgress(); Yaml yaml = new Yaml(); @@ -436,8 +402,6 @@ void shouldRejectBadEnableTagsParam() { Object data = yaml.load(yamlStr); PassiveScanConfigJob job = new PassiveScanConfigJob(); - PassiveScanParam psp = (PassiveScanParam) JobUtils.getJobOptions(job, progress); - psp.load(new ZapXmlConfiguration()); // When job.setJobData(((LinkedHashMap) data)); @@ -459,14 +423,12 @@ void shouldEnableTags() { Object data = yaml.load(yamlStr); PassiveScanConfigJob job = new PassiveScanConfigJob(); - PassiveScanParam psp = (PassiveScanParam) JobUtils.getJobOptions(job, progress); - psp.load(new ZapXmlConfiguration()); - RegexAutoTagScanner tag1 = new RegexAutoTagScanner(); + RegexAutoTagScanner tag1 = new TestRegexAutoTagScanner(); tag1.setEnabled(false); - RegexAutoTagScanner tag2 = new RegexAutoTagScanner(); + RegexAutoTagScanner tag2 = new TestRegexAutoTagScanner(); tag2.setEnabled(false); - given(pscanParam.getAutoTagScanners()).willReturn(Arrays.asList(tag1, tag2)); + given(options.getAutoTagScanners()).willReturn(Arrays.asList(tag1, tag2)); // When job.setJobData(((LinkedHashMap) data)); @@ -490,14 +452,12 @@ void shouldDisableTags() { Object data = yaml.load(yamlStr); PassiveScanConfigJob job = new PassiveScanConfigJob(); - PassiveScanParam psp = (PassiveScanParam) JobUtils.getJobOptions(job, progress); - psp.load(new ZapXmlConfiguration()); - RegexAutoTagScanner tag1 = new RegexAutoTagScanner(); + RegexAutoTagScanner tag1 = new TestRegexAutoTagScanner(); tag1.setEnabled(true); - RegexAutoTagScanner tag2 = new RegexAutoTagScanner(); + RegexAutoTagScanner tag2 = new TestRegexAutoTagScanner(); tag2.setEnabled(true); - given(pscanParam.getAutoTagScanners()).willReturn(Arrays.asList(tag1, tag2)); + given(options.getAutoTagScanners()).willReturn(Arrays.asList(tag1, tag2)); // When job.setJobData(((LinkedHashMap) data)); @@ -521,14 +481,12 @@ void shouldDisableTagsByDefault() { Object data = yaml.load(yamlStr); PassiveScanConfigJob job = new PassiveScanConfigJob(); - PassiveScanParam psp = (PassiveScanParam) JobUtils.getJobOptions(job, progress); - psp.load(new ZapXmlConfiguration()); - RegexAutoTagScanner tag1 = new RegexAutoTagScanner(); + RegexAutoTagScanner tag1 = new RegexAutoTagScanner() {}; tag1.setEnabled(true); - RegexAutoTagScanner tag2 = new RegexAutoTagScanner(); + RegexAutoTagScanner tag2 = new RegexAutoTagScanner() {}; tag2.setEnabled(true); - given(pscanParam.getAutoTagScanners()).willReturn(Arrays.asList(tag1, tag2)); + given(options.getAutoTagScanners()).willReturn(Arrays.asList(tag1, tag2)); // When job.setJobData(((LinkedHashMap) data)); @@ -561,4 +519,8 @@ public String getName() { return null; } } + + private class TestRegexAutoTagScanner extends RegexAutoTagScanner { + // Nothing to do. + } } diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJobUnitTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJobUnitTest.java index 9d8e56d8234..65adbc1915a 100644 --- a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJobUnitTest.java +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/automation/jobs/PassiveScanWaitJobUnitTest.java @@ -25,25 +25,16 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; -import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; -import org.parosproxy.paros.CommandLine; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.extension.ExtensionLoader; @@ -51,44 +42,31 @@ import org.zaproxy.addon.automation.AutomationEnvironment; import org.zaproxy.addon.automation.AutomationJob.Order; import org.zaproxy.addon.automation.AutomationProgress; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.PassiveScannersManager; import org.zaproxy.zap.utils.I18N; -import org.zaproxy.zap.utils.ZapXmlConfiguration; class PassiveScanWaitJobUnitTest { - private static MockedStatic mockedCmdLine; - - @BeforeAll - static void init() { - mockedCmdLine = Mockito.mockStatic(CommandLine.class); - } - - @AfterAll - static void close() { - mockedCmdLine.close(); - } + private ExtensionPassiveScan2 pscan; @BeforeEach void setUp() throws Exception { Constant.messages = new I18N(Locale.ENGLISH); + + pscan = mock(ExtensionPassiveScan2.class); + PassiveScannersManager scannersManager = mock(PassiveScannersManager.class); + given(pscan.getPassiveScannersManager()).willReturn(scannersManager); + + ExtensionLoader extensionLoader = mock(ExtensionLoader.class); + given(extensionLoader.getExtension(ExtensionPassiveScan2.class)).willReturn(pscan); + + Control.initSingletonForTesting(mock(Model.class), extensionLoader); } @Test void shouldReturnDefaultFields() { - // Given - Model model = mock(Model.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); - Model.setSingletonForTesting(model); - ExtensionLoader extensionLoader = - mock(ExtensionLoader.class, withSettings().strictness(Strictness.LENIENT)); - ExtensionPassiveScan extPscan = - mock(ExtensionPassiveScan.class, withSettings().strictness(Strictness.LENIENT)); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extPscan); - - Control.initSingletonForTesting(Model.getSingleton(), extensionLoader); - Model.getSingleton().getOptionsParam().load(new ZapXmlConfiguration()); - - // When + // Given / When PassiveScanWaitJob job = new PassiveScanWaitJob(); // Then @@ -114,18 +92,14 @@ void shouldReturnCustomConfigParams() { assertThat(params.containsValue("0"), is(equalTo(true))); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Test void shouldApplyParams() { // Given PassiveScanWaitJob job = new PassiveScanWaitJob(); AutomationProgress progress = new AutomationProgress(); int duration = 10; - Map map = new HashMap(); - map.put("maxDuration", Integer.toString(duration)); - LinkedHashMap params = new LinkedHashMap(map); LinkedHashMap jobData = new LinkedHashMap<>(); - jobData.put("parameters", params); + jobData.put("parameters", Map.of("maxDuration", duration)); // When job.setJobData(jobData); @@ -138,17 +112,13 @@ void shouldApplyParams() { assertThat(progress.hasErrors(), is(equalTo(false))); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Test void shouldWarnOnUnknownParams() { // Given PassiveScanWaitJob job = new PassiveScanWaitJob(); AutomationProgress progress = new AutomationProgress(); - Map map = new HashMap(); - map.put("test", "test"); - LinkedHashMap params = new LinkedHashMap(map); LinkedHashMap jobData = new LinkedHashMap<>(); - jobData.put("parameters", params); + jobData.put("parameters", Map.of("test", "test")); // When job.setJobData(jobData); @@ -166,15 +136,7 @@ void shouldWarnOnUnknownParams() { @Test void shouldWaitForPassiveScan() { // Given - Model model = mock(Model.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); - Model.setSingletonForTesting(model); - ExtensionLoader extensionLoader = - mock(ExtensionLoader.class, withSettings().strictness(Strictness.LENIENT)); - ExtensionPassiveScan extAuto = - mock(ExtensionPassiveScan.class, withSettings().strictness(Strictness.LENIENT)); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extAuto); - - when(extAuto.getRecordsToScan()) + when(pscan.getRecordsToScan()) .thenAnswer( new Answer() { private int records = 5; @@ -186,9 +148,6 @@ public Integer answer(InvocationOnMock invocation) { } }); - Control.initSingletonForTesting(Model.getSingleton(), extensionLoader); - Model.getSingleton().getOptionsParam().load(new ZapXmlConfiguration()); - AutomationProgress progress = new AutomationProgress(); AutomationEnvironment env = mock(AutomationEnvironment.class); @@ -203,21 +162,10 @@ public Integer answer(InvocationOnMock invocation) { assertThat(progress.getJobResultData("passiveScanData2"), is(notNullValue())); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Test void shouldExitIfPassiveScanTakesLongerThanConfig() { // Given - Model model = mock(Model.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); - Model.setSingletonForTesting(model); - ExtensionLoader extensionLoader = - mock(ExtensionLoader.class, withSettings().strictness(Strictness.LENIENT)); - ExtensionPassiveScan extAuto = - mock(ExtensionPassiveScan.class, withSettings().strictness(Strictness.LENIENT)); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)).willReturn(extAuto); - given(extAuto.getRecordsToScan()).willReturn(1); - - Control.initSingletonForTesting(Model.getSingleton(), extensionLoader); - Model.getSingleton().getOptionsParam().load(new ZapXmlConfiguration()); + given(pscan.getRecordsToScan()).willReturn(1); AutomationProgress progress = new AutomationProgress(); AutomationEnvironment env = mock(AutomationEnvironment.class); @@ -225,11 +173,8 @@ void shouldExitIfPassiveScanTakesLongerThanConfig() { PassiveScanWaitJob job = new PassiveScanWaitJob(); int duration = 1; - Map map = new HashMap(); - map.put("maxDuration", Integer.toString(duration)); - LinkedHashMap params = new LinkedHashMap(map); LinkedHashMap jobData = new LinkedHashMap<>(); - jobData.put("parameters", params); + jobData.put("parameters", Map.of("maxDuration", duration)); // When job.setJobData(jobData); diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/DefaultRegexAutoTagScannerTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/DefaultRegexAutoTagScannerTest.java new file mode 100644 index 00000000000..82c0a667f04 --- /dev/null +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/DefaultRegexAutoTagScannerTest.java @@ -0,0 +1,191 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2022 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.io.InputStream; +import java.util.stream.Stream; +import net.htmlparser.jericho.Source; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.URIException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMalformedHeaderException; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.utils.Stats; +import org.zaproxy.zap.utils.StatsListener; +import org.zaproxy.zap.utils.ZapXmlConfiguration; + +@Timeout(6) +class DefaultRegexAutoTagScannerTest { + + private static final String BASE_STRING = "lorem ipsum < type= href= "; + private static final String DEFAULT_EXPECTED_SITE = "http://example.com"; + + private static Source source; + private static HttpMessage message; + private static PassiveScannerOptions options; + private StatsListener listener; + + @BeforeAll + static void beforeAll() throws Exception { + options = new PassiveScannerOptions(); + try (InputStream is = + DefaultRegexAutoTagScannerTest.class.getResourceAsStream( + "/org/zaproxy/zap/resources/config.xml")) { + options.load(new ZapXmlConfiguration(is)); + } + + StringBuilder strBuilder = new StringBuilder(16_000_000); + int count = strBuilder.capacity() / BASE_STRING.length(); + for (int i = 0; i < count; i++) { + strBuilder.append(BASE_STRING); + } + String body = strBuilder.toString(); + source = new Source(body); + message = new HttpMessage(); + message.setResponseBody(body); + } + + @BeforeEach + private void beforeEach() { + listener = spy(StatsListener.class); + Stats.addListener(listener); + } + + static Stream defaultRegexes() { + return options.getAutoTagScanners().stream().map(e -> Arguments.of(e.getName(), e)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("defaultRegexes") + void shouldNotBeSlowWhenScanningBigBody(String name, RegexAutoTagScanner scanner) { + assertDoesNotThrow(() -> scanner.scanHttpResponseReceive(message, -1, source)); + } + + private RegexAutoTagScanner getRegexRuleByName(String taggerName) { + for (RegexAutoTagScanner tagRule : options.getAutoTagScanners()) { + if (tagRule.getName().equals(taggerName)) { + return tagRule; + } + } + return null; + } + + @ParameterizedTest(name = "{0}") + @ValueSource( + strings = { + "application/JSON", + "application/jSon", + "application/json", + "aPPlication/json", + "application/json; charset=utf-8" + }) + void shouldCountWhenHeaderMatchesJsonTag(String contentType) + throws URIException, HttpMalformedHeaderException, NullPointerException { + shouldCountWhenHeaderMatchesExpectedTag(contentType, "response_json", "JSON"); + } + + @ParameterizedTest(name = "{0}") + @ValueSource( + strings = { + // Response overlap + "application/json", + "application/json; charset=utf-8", + "application/hal+json", + "application/health+json", + "application/problem+json", + "application/vnd.api+json", + "application/x-ndjson", + "text/x-json", + "text/json", + "text/json; charset=utf-8" + }) + void shouldCountWhenHeaderMatchesJsonExtendedTag(String contentType) + throws URIException, HttpMalformedHeaderException, NullPointerException { + shouldCountWhenHeaderMatchesExpectedTag(contentType, "json_extended", "JSON"); + } + + @ParameterizedTest(name = "{0}") + @ValueSource( + strings = { + "application/yaml", + "application/yaml; charset=utf-8", + "text/yaml", + "text/yaml; charset=utf-8", + "application/x-yaml" + }) + void shouldCountWhenHeaderMatchesYamlTag(String contentType) + throws URIException, HttpMalformedHeaderException, NullPointerException { + shouldCountWhenHeaderMatchesExpectedTag(contentType, "response_yaml", "YAML"); + } + + @ParameterizedTest(name = "{0}") + @ValueSource( + strings = { + "application/xml; charset=utf-8", + "text/xml", + "application/problem+xml", + "application/soap+xml" + }) + void shouldCountWhenHeaderMatchesXmlTag(String contentType) + throws URIException, HttpMalformedHeaderException, NullPointerException { + shouldCountWhenHeaderMatchesExpectedTag(contentType, "response_xml", "XML"); + } + + private void shouldCountWhenHeaderMatchesExpectedTag( + String contentType, String regexRuleName, String expectedConf) + throws URIException, HttpMalformedHeaderException, NullPointerException { + // Given + HttpMessage tagMessage = new HttpMessage(new URI("http://example.com/", true)); + RegexAutoTagScanner rule = getRegexRuleByName(regexRuleName); + rule.setEnabled(true); + tagMessage.setHistoryRef(mock(HistoryReference.class)); + tagMessage.getResponseHeader().setHeader(HttpHeader.CONTENT_TYPE, contentType); + // When + rule.scanHttpResponseReceive( + tagMessage, -1, new Source(tagMessage.getResponseBody().toString())); + // Then + ArgumentCaptor siteCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(String.class); + + verify(listener).counterInc(siteCaptor.capture(), keyCaptor.capture()); + assertThat(siteCaptor.getValue(), is(equalTo(DEFAULT_EXPECTED_SITE))); + assertThat(rule.getConf(), is(equalTo(expectedConf))); + assertThat( + keyCaptor.getValue(), + is(equalTo(RegexAutoTagScanner.TAG_STATS_PREFIX + rule.getConf()))); + } +} diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptionsUnitTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptionsUnitTest.java new file mode 100644 index 00000000000..c49deaa0a05 --- /dev/null +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/PassiveScannerOptionsUnitTest.java @@ -0,0 +1,84 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2022 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.parosproxy.paros.Constant; +import org.zaproxy.zap.utils.I18N; +import org.zaproxy.zap.utils.ZapXmlConfiguration; + +/** Unit test for {@link PassiveScannerOptions}. */ +class PassiveScannerOptionsUnitTest { + + private PassiveScannerOptions options; + private ZapXmlConfiguration configuration; + + @BeforeAll + static void beforeAll() { + Constant.messages = mock(I18N.class); + } + + @AfterAll + static void afterAll() { + Constant.messages = null; + } + + @BeforeEach + void setUp() { + options = new PassiveScannerOptions(); + configuration = new ZapXmlConfiguration(); + options.load(configuration); + } + + @Test + void shouldHaveConfigVersionKey() { + assertThat(options.getConfigVersionKey(), is(equalTo("pscans[@version]"))); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 10}) + void shouldLoadThreadsFromConfig(int threads) { + // Given + configuration.setProperty("pscans.threads", threads); + // When + options.load(configuration); + // Then + assertThat(options.getPassiveScanThreads(), is(equalTo(threads))); + } + + @Test + void shouldDefaultThreads() { + // Given / When + options.load(configuration); + // Then + assertThat( + options.getPassiveScanThreads(), is(equalTo(Constant.getDefaultThreadCount() / 2))); + } +} diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScannerUnitTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScannerUnitTest.java new file mode 100644 index 00000000000..9f0b21eb5f1 --- /dev/null +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/RegexAutoTagScannerUnitTest.java @@ -0,0 +1,114 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2020 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import net.htmlparser.jericho.Source; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.URIException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.parosproxy.paros.db.DatabaseException; +import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.network.HttpMalformedHeaderException; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.utils.Stats; +import org.zaproxy.zap.utils.StatsListener; + +@SuppressWarnings("removal") +class RegexAutoTagScannerUnitTest { + + private static final String BODY = + "@@head@@@@body_one@@ @@body_two@@"; + private static final String TEST_PATTERN = ".*foo\\sbar"; + private static final String TEST_CONFIG = "Test"; + + private RegexAutoTagScanner rule; + private StatsListener listener; + + @BeforeEach + void setUp() { + rule = + new RegexAutoTagScanner( + TEST_CONFIG, + RegexAutoTagScanner.TYPE.TAG, + TEST_CONFIG, + null, + null, + null, + TEST_PATTERN, + true); + listener = spy(StatsListener.class); + Stats.addListener(listener); + } + + @AfterEach + void cleanup() { + Stats.removeListener(listener); + } + + @Test + void shouldNotCountTagWhenBodyDoesNotMatch() + throws URIException, HttpMalformedHeaderException, DatabaseException { + // Given + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody(BODY); + msg.setHistoryRef(mock(HistoryReference.class)); + // When + rule.scanHttpResponseReceive(msg, -1, new Source(BODY)); + // Then + Mockito.verifyNoInteractions(listener); + } + + @Test + void shouldCountTagWhenBodyHasMatch() + throws URIException, HttpMalformedHeaderException, DatabaseException { + // Given + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody(BODY.replace("@@body_two@@", "Lorem ipsum dolor sit amet, foo bar")); + msg.setHistoryRef(mock(HistoryReference.class)); + // When + rule.scanHttpResponseReceive(msg, -1, new Source(msg.getResponseBody().toString())); + // Then + verify(listener) + .counterInc( + "http://example.com", RegexAutoTagScanner.TAG_STATS_PREFIX + TEST_CONFIG); + Mockito.verifyNoMoreInteractions(listener); + } + + @Test + void shouldNotCountWhenDisabledThoughBodyContainsMatch() + throws URIException, HttpMalformedHeaderException, DatabaseException { + // Given + rule.setEnabled(false); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody(BODY.replace("@@body_two@@", "Lorem ipsum dolor sit amet, foo bar")); + msg.setHistoryRef(mock(HistoryReference.class)); + // When + rule.scanHttpResponseReceive(msg, -1, new Source(msg.getResponseBody().toString())); + // Then + Mockito.verifyNoInteractions(listener); + } +} diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/ScanRuleManagerUnitTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/ScanRuleManagerUnitTest.java new file mode 100644 index 00000000000..91670b78d13 --- /dev/null +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/ScanRuleManagerUnitTest.java @@ -0,0 +1,202 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2018 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.zaproxy.zap.extension.pscan.PassiveScanner; + +/** Unit test for {@link ScanRuleManager}. */ +class ScanRuleManagerUnitTest { + + private ScanRuleManager manager; + + @BeforeEach + void setUp() throws Exception { + manager = new ScanRuleManager(); + } + + @Test + void shouldHaveNoScannersByDefault() { + assertThat(manager.getScanners(), is(empty())); + } + + @Test + void shouldAddPassiveScanner() { + // Given + PassiveScanner scanner = mock(PassiveScanner.class); + // When + boolean scannerAdded = manager.add(scanner); + // Then + assertThat(manager.getScanners(), contains(scanner)); + assertThat(scannerAdded, is(equalTo(true))); + } + + @Test + void shouldIgnorePassiveScannerWithSameName() { + // Given + PassiveScanner scanner1 = mock(PassiveScanner.class); + when(scanner1.getName()).thenReturn("PassiveScanner 1"); + PassiveScanner otherScannerWithSameName = mock(PassiveScanner.class); + when(otherScannerWithSameName.getName()).thenReturn("PassiveScanner 1"); + // When + manager.add(scanner1); + boolean otherScannerAdded = manager.add(otherScannerWithSameName); + // Then + assertThat(manager.getScanners(), contains(scanner1)); + assertThat(otherScannerAdded, is(equalTo(false))); + } + + @Test + void shouldRemovePassiveScanner() { + // Given + PassiveScanner scanner1 = mock(PassiveScanner.class); + manager.add(scanner1); + PassiveScanner scanner2 = mock(TestPassiveScanner.class); + when(scanner2.getName()).thenReturn("TestPassiveScanner"); + manager.add(scanner2); + // When + boolean removed = manager.remove(scanner2.getClass().getName()); + // Then + assertThat(manager.getScanners(), contains(scanner1)); + assertThat(removed, is(equalTo(true))); + } + + @Test + void shouldNotRemovePassiveScannerNotAdded() { + // Given + PassiveScanner scanner = mock(PassiveScanner.class); + // When + boolean removed = manager.remove(scanner.getClass().getName()); + // Then + assertThat(removed, is(equalTo(false))); + } + + @Test + void shouldSetAutoTagScanners() { + // Given + List scanners = new ArrayList<>(); + RegexAutoTagScanner scanner1 = mock(RegexAutoTagScanner.class); + when(scanner1.getName()).thenReturn("RegexAutoTagScanner 1"); + scanners.add(scanner1); + RegexAutoTagScanner scanner2 = mock(RegexAutoTagScanner.class); + when(scanner2.getName()).thenReturn("RegexAutoTagScanner 2"); + scanners.add(scanner2); + // When + manager.setAutoTagScanners(scanners); + // Then + assertThat(manager.getScanners(), contains(scanner1, scanner2)); + } + + @Test + void shouldRemovePreviousAutoTagScannersButNotPassiveScanners() { + // Given + RegexAutoTagScanner scanner1 = mock(RegexAutoTagScanner.class); + when(scanner1.getName()).thenReturn("RegexAutoTagScanner 1"); + manager.add(scanner1); + PassiveScanner scanner2 = mock(PassiveScanner.class); + when(scanner2.getName()).thenReturn("PassiveScanner 1"); + manager.add(scanner2); + List scanners = new ArrayList<>(); + RegexAutoTagScanner scanner3 = mock(RegexAutoTagScanner.class); + when(scanner3.getName()).thenReturn("RegexAutoTagScanner 2"); + scanners.add(scanner3); + // When + manager.setAutoTagScanners(scanners); + // Then + assertThat(manager.getScanners(), contains(scanner2, scanner3)); + } + + @Test + void shouldIgnoreAutoTagScannerWithSameName() { + // Given + List scanners = new ArrayList<>(); + RegexAutoTagScanner scanner1 = mock(RegexAutoTagScanner.class); + when(scanner1.getName()).thenReturn("RegexAutoTagScanner 1"); + scanners.add(scanner1); + RegexAutoTagScanner otherScannerWithSameName = mock(RegexAutoTagScanner.class); + when(otherScannerWithSameName.getName()).thenReturn("RegexAutoTagScanner 1"); + scanners.add(otherScannerWithSameName); + // When + manager.setAutoTagScanners(scanners); + // Then + assertThat(manager.getScanners(), contains(scanner1)); + } + + @Test + void shouldAllowToChangeListWhileIterating() { + // Given + PassiveScanner scanner1 = mock(PassiveScanner.class); + manager.add(scanner1); + TestPassiveScanner scanner2 = mock(TestPassiveScanner.class); + when(scanner2.getName()).thenReturn("TestPassiveScanner"); + manager.add(scanner2); + // When / Then + assertDoesNotThrow( + () -> + manager.getScanners() + .forEach( + e -> { + manager.remove(e); + manager.add(e); + })); + assertThat(manager.getScanners(), contains(scanner1, scanner2)); + } + + @Test + void shouldAllowToChangeListWhileIteratingAfterSettingAutoTagScanners() { + // Given + PassiveScanner scanner1 = mock(PassiveScanner.class); + manager.add(scanner1); + RegexAutoTagScanner scanner2 = mock(RegexAutoTagScanner.class); + when(scanner2.getName()).thenReturn("RegexAutoTagScanner"); + List autoTagScanners = new ArrayList<>(); + autoTagScanners.add(scanner2); + manager.setAutoTagScanners(autoTagScanners); + // When / Then + assertDoesNotThrow( + () -> + manager.getScanners() + .forEach( + e -> { + if (!(e instanceof RegexAutoTagScanner)) { + manager.remove(e); + manager.add(e); + } + })); + assertThat(manager.getScanners(), contains(scanner2, scanner1)); + } + + /** An interface to mock {@code PassiveScanner}s with different class name. */ + private static interface TestPassiveScanner extends PassiveScanner { + // Nothing to do. + } +} diff --git a/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanControllerUnitTest.java b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanControllerUnitTest.java new file mode 100644 index 00000000000..ffdb091fe48 --- /dev/null +++ b/addOns/pscan/src/test/java/org/zaproxy/addon/pscan/internal/scanner/PassiveScanControllerUnitTest.java @@ -0,0 +1,397 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2022 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.pscan.internal.scanner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import net.htmlparser.jericho.Source; +import org.apache.commons.httpclient.URI; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.control.Control; +import org.parosproxy.paros.extension.history.ExtensionHistory; +import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.model.OptionsParam; +import org.parosproxy.paros.model.Session; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.PassiveScannersManager; +import org.zaproxy.addon.pscan.internal.PassiveScannerOptions; +import org.zaproxy.zap.extension.alert.ExtensionAlert; +import org.zaproxy.zap.extension.pscan.PassiveScanner; +import org.zaproxy.zap.testutils.TestUtils; +import org.zaproxy.zap.utils.I18N; + +/** Unit test for {@link PassiveScanController}. */ +@Disabled("Requires latest core changes to work properly.") +class PassiveScanControllerUnitTest extends TestUtils { + + private static final String EXAMPLE_URL = "https://www.example.com"; + private PassiveScanController psc; + private PassiveScannersManager scanRuleManager; + private ExtensionHistory extHistory; + private ExtensionPassiveScan2 extPscan; + private ExtensionAlert extAlert; + private PassiveScannerOptions options; + private Session session; + + @BeforeEach + void setUp() throws Exception { + Constant.messages = mock(I18N.class); + Control.initSingletonForTesting(); + + scanRuleManager = mock(PassiveScannersManager.class); + extHistory = mock(ExtensionHistory.class); + extPscan = mock(ExtensionPassiveScan2.class); + extAlert = mock(ExtensionAlert.class); + options = mock(PassiveScannerOptions.class); + session = mock(Session.class); + + Model model = mock(Model.class); + Model.setSingletonForTesting(model); + given(model.getSession()).willReturn(session); + + given(extPscan.getPassiveScannersManager()).willReturn(scanRuleManager); + given(extPscan.getModel()).willReturn(model); + given(extHistory.getModel()).willReturn(model); + OptionsParam optionsParam = mock(OptionsParam.class); + given(model.getOptionsParam()).willReturn(optionsParam); + given(optionsParam.getParamSet(PassiveScannerOptions.class)).willReturn(options); + given(options.getPassiveScanThreads()).willReturn(2); + + psc = new PassiveScanController(extPscan, extHistory, extAlert); + psc.setSession(session); + } + + @AfterEach + void cleanup() { + psc.shutdown(); + } + + private static void sleep(int msecs) { + try { + Thread.sleep(msecs); + } catch (InterruptedException e) { + // Ignore + } + } + + @Test + void shouldProcessInScopeHistoryRecord() throws Exception { + // Given + HttpMessage msg = new HttpMessage(new URI(EXAMPLE_URL, true)); + msg.setResponseFromTargetHost(true); + + HistoryReference href = mock(HistoryReference.class); + given(href.getHttpMessage()).willReturn(msg); + given(extHistory.getLastHistoryId()).willReturn(1); + given(extHistory.getHistoryReference(1)).willReturn(href); + + ScanState scanState = new ScanState(1); + TestPassiveScanner scanner = new TestPassiveScanner(true, scanState); + given(scanRuleManager.getScanners()).willReturn(List.of(scanner)); + + // When + psc.start(); + psc.responseReceived(); + scanState.waitScanFinished(); + sleep(500); + + // Then + assertThat(psc.getRecordsToScan(), is(equalTo(0))); + assertThat(scanState.isScannedRequest(), is(equalTo(true))); + assertThat(scanState.isScannedResponse(), is(equalTo(true))); + } + + @Test + void shouldProcessHistoryRecordEvenIfConstantlyInterrupted() throws Exception { + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + try { + // Given + HttpMessage msg = new HttpMessage(new URI(EXAMPLE_URL, true)); + msg.setResponseFromTargetHost(true); + + HistoryReference href = mock(HistoryReference.class); + given(href.getHttpMessage()).willReturn(msg); + given(extHistory.getLastHistoryId()).willReturn(0, 1); + given(extHistory.getHistoryReference(1)).willReturn(href); + + ScanState scanState = new ScanState(1); + TestPassiveScanner scanner = new TestPassiveScanner(true, scanState); + given(scanRuleManager.getScanners()).willReturn(List.of(scanner)); + + executor.scheduleAtFixedRate(() -> psc.interrupt(), 0, 100, TimeUnit.MILLISECONDS); + // When + psc.start(); + // Then + scanState.waitScanFinished(); + sleep(500); + assertThat(psc.getRecordsToScan(), is(equalTo(0))); + assertThat(scanState.isScannedRequest(), is(equalTo(true))); + assertThat(scanState.isScannedResponse(), is(equalTo(true))); + } finally { + executor.shutdownNow(); + } + } + + @Test + void shouldProcessOutOfScopeHistoryRecordByDefault() throws Exception { + // Given + HttpMessage msg = new HttpMessage(new URI(EXAMPLE_URL, true)); + msg.setResponseFromTargetHost(true); + + HistoryReference href = mock(HistoryReference.class); + given(href.getHttpMessage()).willReturn(msg); + given(extHistory.getLastHistoryId()).willReturn(1); + given(extHistory.getHistoryReference(1)).willReturn(href); + + // Key config + given(session.isInScope(href)).willReturn(false); + + ScanState scanState = new ScanState(1); + TestPassiveScanner scanner = new TestPassiveScanner(true, scanState); + given(scanRuleManager.getScanners()).willReturn(List.of(scanner)); + + // When + psc.start(); + psc.responseReceived(); + scanState.waitScanFinished(); + sleep(500); + + // Then + assertThat(psc.getRecordsToScan(), is(equalTo(0))); + assertThat(scanState.isScannedRequest(), is(equalTo(true))); + assertThat(scanState.isScannedResponse(), is(equalTo(true))); + } + + @Test + void shouldNotProcessOutOfScopeHistoryRecordIfOptionSet() throws Exception { + // Given + HttpMessage msg = new HttpMessage(new URI(EXAMPLE_URL, true)); + msg.setResponseFromTargetHost(true); + + HistoryReference href = mock(HistoryReference.class); + given(href.getHttpMessage()).willReturn(msg); + given(extHistory.getLastHistoryId()).willReturn(1); + given(extHistory.getHistoryReference(1)).willReturn(href); + + // Key config + given(session.isInScope(href)).willReturn(false); + given(options.isScanOnlyInScope()).willReturn(true); + + ScanState scanState = new ScanState(0); + TestPassiveScanner scanner = new TestPassiveScanner(true, scanState); + given(scanRuleManager.getScanners()).willReturn(List.of(scanner)); + + // When + psc.start(); + psc.responseReceived(); + sleep(500); + + // Then + assertThat(psc.getRecordsToScan(), is(equalTo(0))); + assertThat(scanState.isScannedRequest(), is(equalTo(false))); + assertThat(scanState.isScannedResponse(), is(equalTo(false))); + } + + @Test + void shouldReturnRunningTasks() throws Exception { + // Given + String exampleUrl1 = EXAMPLE_URL + "/1"; + String exampleUrl2 = EXAMPLE_URL + "/2"; + HttpMessage msg1 = new HttpMessage(new URI(exampleUrl1, true)); + msg1.setResponseFromTargetHost(true); + HttpMessage msg2 = new HttpMessage(new URI(exampleUrl2, true)); + msg2.setResponseFromTargetHost(true); + + HistoryReference href1 = mock(HistoryReference.class); + HistoryReference href2 = mock(HistoryReference.class); + given(href1.getHistoryId()).willReturn(1); + given(href2.getHistoryId()).willReturn(2); + given(href1.getHttpMessage()).willReturn(msg1); + given(href2.getHttpMessage()).willReturn(msg2); + given(href1.getURI()).willReturn(new URI(exampleUrl1, true)); + given(href2.getURI()).willReturn(new URI(exampleUrl2, true)); + when(extHistory.getLastHistoryId()).thenReturn(1, 2); + given(extHistory.getHistoryReference(1)).willReturn(href1); + given(extHistory.getHistoryReference(2)).willReturn(href2); + + ScanState scanState = new ScanState(true, 2); + TestPassiveScanner scanner = new TestPassiveScanner("TPS", true, scanState); + given(scanRuleManager.getScanners()).willReturn(List.of(scanner)); + + // When + psc.start(); + long testStartTime = System.currentTimeMillis(); + psc.responseReceived(); + scanState.waitScanStarted(); + PassiveScanTask oldestTask = psc.getOldestRunningTask(); + List tasks = psc.getRunningTasks(); + int recordsToScan = psc.getRecordsToScan(); + scanState.continueScan(); + scanState.waitScanFinished(); + long testEndTime = System.currentTimeMillis(); + sleep(500); + + // Then + assertThat(psc.getRecordsToScan(), is(equalTo(0))); + assertThat(psc.getOldestRunningTask(), is(nullValue())); + assertThat(psc.getRunningTasks().size(), is(equalTo(0))); + assertThat(oldestTask.getCurrentScanner().getName(), is(equalTo("TPS"))); + assertThat(oldestTask.getURI().toString(), is(equalTo(exampleUrl1))); + assertThat(oldestTask.getStartTime(), is(greaterThan(testStartTime))); + assertThat(testEndTime, is(greaterThanOrEqualTo(oldestTask.getStartTime()))); + assertThat(tasks.size(), is(equalTo(2))); + assertThat(recordsToScan, is(equalTo(2))); + assertThat(tasks.get(0).getCurrentScanner().getName(), is(equalTo("TPS"))); + assertThat(tasks.get(0).getHistoryReference().getHistoryId(), is(equalTo(1))); + assertThat(tasks.get(1).getHistoryReference().getHistoryId(), is(equalTo(2))); + assertThat(tasks.get(0).getURI().toString(), is(equalTo(exampleUrl1))); + assertThat(tasks.get(1).getURI().toString(), is(equalTo(exampleUrl2))); + } + + static class TestPassiveScanner implements PassiveScanner { + + private final ScanState scanState; + private boolean enabled; + private String name; + + TestPassiveScanner(boolean enabled, ScanState scanState) { + this("", enabled, scanState); + } + + TestPassiveScanner(String name, boolean enabled, ScanState scanState) { + this.name = name; + this.enabled = enabled; + this.scanState = scanState; + } + + @Override + public void scanHttpRequestSend(HttpMessage msg, int id) { + scanState.scanStarted(); + scanState.holdScan(); + } + + @Override + public void scanHttpResponseReceive(HttpMessage msg, int id, Source source) { + scanState.scanFinished(); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setEnabled(boolean enabled) {} + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public boolean appliesToHistoryType(int historyType) { + return true; + } + } + + private static class ScanState { + + private CountDownLatch holdScan; + private CountDownLatch scanStarted; + private CountDownLatch scanFinished; + + private volatile boolean scannedRequest; + private volatile boolean scannedResponse; + + ScanState(int messagesToScan) { + this(false, messagesToScan); + } + + ScanState(boolean holdScan, int messagesToScan) { + this.holdScan = new CountDownLatch(holdScan ? 1 : 0); + scanStarted = new CountDownLatch(messagesToScan); + scanFinished = new CountDownLatch(messagesToScan); + } + + void holdScan() { + await(holdScan); + } + + void continueScan() { + holdScan.countDown(); + } + + void scanStarted() { + scannedRequest = true; + scanStarted.countDown(); + } + + void scanFinished() { + scannedResponse = true; + scanFinished.countDown(); + } + + void waitScanStarted() { + await(scanStarted); + } + + void waitScanFinished() { + await(scanFinished); + } + + boolean isScannedRequest() { + return scannedRequest; + } + + boolean isScannedResponse() { + return scannedResponse; + } + + private static void await(CountDownLatch cdl) { + try { + if (!cdl.await(5, TimeUnit.SECONDS)) { + throw new RuntimeException("Await condition failed."); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + } +} diff --git a/addOns/quickstart/CHANGELOG.md b/addOns/quickstart/CHANGELOG.md index 41caf34728b..9c6ee07f847 100644 --- a/addOns/quickstart/CHANGELOG.md +++ b/addOns/quickstart/CHANGELOG.md @@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Stats counter to the main toolbar button (Issue 8375). +### Changed +- Depend on Passive Scanner add-on (Issue 7959). + ### Fixed - An exception that prevented the look and feel from changing completely. diff --git a/addOns/quickstart/quickstart.gradle.kts b/addOns/quickstart/quickstart.gradle.kts index 672ca78b1c2..befc32f3c57 100644 --- a/addOns/quickstart/quickstart.gradle.kts +++ b/addOns/quickstart/quickstart.gradle.kts @@ -74,6 +74,9 @@ zapAddOn { register("network") { version.set(">= 0.3.0") } + register("pscan") { + version.set(">= 0.1.0 & < 1.0.0") + } } } } @@ -82,6 +85,7 @@ zapAddOn { dependencies { zapAddOn("callhome") zapAddOn("network") + zapAddOn("pscan") zapAddOn("reports") zapAddOn("selenium") zapAddOn("spider") diff --git a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ExtensionQuickStart.java b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ExtensionQuickStart.java index 31f9e1964d1..b039fc29565 100644 --- a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ExtensionQuickStart.java +++ b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ExtensionQuickStart.java @@ -67,13 +67,13 @@ import org.zaproxy.addon.callhome.InvalidServiceUrlException; import org.zaproxy.addon.network.ExtensionNetwork; import org.zaproxy.addon.network.common.ZapUnknownHostException; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.addon.reports.ExtensionReports; import org.zaproxy.zap.ZAP; import org.zaproxy.zap.ZAP.ProcessType; import org.zaproxy.zap.extension.alert.ExtensionAlert; import org.zaproxy.zap.extension.ext.ExtensionExtension; import org.zaproxy.zap.extension.help.ExtensionHelp; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.quickstart.AttackThread.Progress; import org.zaproxy.zap.network.HttpRequestConfig; import org.zaproxy.zap.utils.ZapXmlConfiguration; @@ -109,7 +109,7 @@ public class ExtensionQuickStart extends ExtensionAdaptor private static final List> DEPENDENCIES = List.of( - ExtensionPassiveScan.class, + ExtensionPassiveScan2.class, ExtensionAlert.class, ExtensionReports.class, ExtensionNetwork.class); diff --git a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ZapItScan.java b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ZapItScan.java index dc0bde148f1..407645abf0d 100644 --- a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ZapItScan.java +++ b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ZapItScan.java @@ -44,8 +44,8 @@ import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpResponseHeader; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.alert.ExtensionAlert; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.network.HttpRedirectionValidator; import org.zaproxy.zap.network.HttpRequestConfig; import org.zaproxy.zap.utils.Stats; @@ -105,10 +105,10 @@ public boolean recon(String url) { } // Wait for passive scan to complete - ExtensionPassiveScan extPscan = + ExtensionPassiveScan2 extPscan = Control.getSingleton() .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class); + .getExtension(ExtensionPassiveScan2.class); while (extPscan.getRecordsToScan() > 0) { try { Thread.sleep(200); diff --git a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/AjaxSpiderExplorer.java b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/AjaxSpiderExplorer.java index 431f591c432..06cf1d75d39 100644 --- a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/AjaxSpiderExplorer.java +++ b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/AjaxSpiderExplorer.java @@ -32,11 +32,11 @@ import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.model.Model; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.ZAP; import org.zaproxy.zap.eventBus.Event; import org.zaproxy.zap.eventBus.EventConsumer; import org.zaproxy.zap.extension.alert.AlertEventPublisher; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.quickstart.PlugableSpider; import org.zaproxy.zap.extension.quickstart.QuickStartBackgroundPanel; import org.zaproxy.zap.extension.quickstart.QuickStartParam; @@ -102,8 +102,10 @@ public ExtensionAjax getExtAjax() { return Control.getSingleton().getExtensionLoader().getExtension(ExtensionAjax.class); } - public ExtensionPassiveScan getExtPscan() { - return Control.getSingleton().getExtensionLoader().getExtension(ExtensionPassiveScan.class); + public ExtensionPassiveScan2 getExtPscan() { + return Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionPassiveScan2.class); } @Override @@ -116,7 +118,7 @@ public void startScan(URI uri) { if (selInd == Select.MODERN.getIndex()) { // Only run if modern - keep monitoring for the relevant alert until the passive scan // queue empties - ExtensionPassiveScan extPscan = this.getExtPscan(); + ExtensionPassiveScan2 extPscan = getExtPscan(); while (extPscan.getRecordsToScan() > 0) { if (isModern) { break; diff --git a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/ExtensionQuickStartAjaxSpider.java b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/ExtensionQuickStartAjaxSpider.java index 2aca5af123b..8424c297c2a 100644 --- a/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/ExtensionQuickStartAjaxSpider.java +++ b/addOns/quickstart/src/main/java/org/zaproxy/zap/extension/quickstart/ajaxspider/ExtensionQuickStartAjaxSpider.java @@ -26,7 +26,7 @@ import org.parosproxy.paros.extension.ExtensionAdaptor; import org.parosproxy.paros.extension.ExtensionHook; import org.parosproxy.paros.view.View; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.quickstart.ExtensionQuickStart; import org.zaproxy.zap.extension.selenium.ExtensionSelenium; import org.zaproxy.zap.extension.spiderAjax.ExtensionAjax; @@ -40,7 +40,7 @@ public class ExtensionQuickStartAjaxSpider extends ExtensionAdaptor { public static final String NAME = "ExtensionQuickStartAjaxSpider"; private static final List> DEPENDENCIES = - List.of(ExtensionAjax.class, ExtensionSelenium.class, ExtensionPassiveScan.class); + List.of(ExtensionAjax.class, ExtensionSelenium.class, ExtensionPassiveScan2.class); private AjaxSpiderExplorer ase; diff --git a/addOns/retest/CHANGELOG.md b/addOns/retest/CHANGELOG.md index 99992b58e44..eb3f4523af5 100644 --- a/addOns/retest/CHANGELOG.md +++ b/addOns/retest/CHANGELOG.md @@ -7,6 +7,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased ### Changed - To handle automation class changes. +- Depend on newer version of Passive Scanner add-on (Issue 7959). ## [0.10.0] - 2024-09-02 ### Changed diff --git a/addOns/retest/retest.gradle.kts b/addOns/retest/retest.gradle.kts index b5bbc1f6981..adaf3587cee 100644 --- a/addOns/retest/retest.gradle.kts +++ b/addOns/retest/retest.gradle.kts @@ -14,7 +14,9 @@ zapAddOn { register("commonlib") { version.set(">= 1.17.0 & < 2.0.0") } - register("pscan") + register("pscan") { + version.set(">= 0.1.0 & < 1.0.0") + } } } } diff --git a/addOns/retest/src/test/java/org/zaproxy/addon/retest/RetestPlanGeneratorUnitTest.java b/addOns/retest/src/test/java/org/zaproxy/addon/retest/RetestPlanGeneratorUnitTest.java index 20cc59fa823..8c0988b5434 100644 --- a/addOns/retest/src/test/java/org/zaproxy/addon/retest/RetestPlanGeneratorUnitTest.java +++ b/addOns/retest/src/test/java/org/zaproxy/addon/retest/RetestPlanGeneratorUnitTest.java @@ -23,6 +23,8 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.List; @@ -30,7 +32,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.parosproxy.paros.Constant; +import org.parosproxy.paros.control.Control; import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.extension.ExtensionLoader; +import org.parosproxy.paros.model.Model; import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.addon.automation.AutomationEnvironment; @@ -41,6 +46,8 @@ import org.zaproxy.addon.automation.jobs.RequestorJob; import org.zaproxy.addon.automation.tests.AbstractAutomationTest; import org.zaproxy.addon.automation.tests.AutomationAlertTest; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.PassiveScannersManager; import org.zaproxy.addon.pscan.automation.jobs.PassiveScanConfigJob; import org.zaproxy.addon.pscan.automation.jobs.PassiveScanWaitJob; import org.zaproxy.zap.network.HttpRequestBody; @@ -55,6 +62,15 @@ class RetestPlanGeneratorUnitTest extends TestUtils { static void init() { Constant.messages = new I18N(Locale.ENGLISH); + ExtensionPassiveScan2 pscan = mock(ExtensionPassiveScan2.class); + PassiveScannersManager scannersManager = mock(PassiveScannersManager.class); + given(pscan.getPassiveScannersManager()).willReturn(scannersManager); + + ExtensionLoader extensionLoader = mock(ExtensionLoader.class); + given(extensionLoader.getExtension(ExtensionPassiveScan2.class)).willReturn(pscan); + + Control.initSingletonForTesting(mock(Model.class), extensionLoader); + List alertData = new ArrayList<>(); HttpMessage msgOne = new HttpMessage(); msgOne.getRequestHeader().setVersion(HttpHeader.HTTP11); diff --git a/addOns/scripts/CHANGELOG.md b/addOns/scripts/CHANGELOG.md index 6022a3d4069..1a47b02bc6d 100644 --- a/addOns/scripts/CHANGELOG.md +++ b/addOns/scripts/CHANGELOG.md @@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Fields with default or missing values are omitted for the `script` job in saved Automation Framework plans. - Depends on an updated version of the Common Library add-on. +- Depend on Passive Scanner add-on (Issue 7959). + +### Fixed +- Correct auto-complete suggestions for parameters of Passive Rules. ## [45.7.0] - 2024-10-07 ### Fixed diff --git a/addOns/scripts/scripts.gradle.kts b/addOns/scripts/scripts.gradle.kts index 5f458eb102d..9046d968878 100644 --- a/addOns/scripts/scripts.gradle.kts +++ b/addOns/scripts/scripts.gradle.kts @@ -32,6 +32,9 @@ zapAddOn { register("commonlib") { version.set(">=1.29.0") } + register("pscan") { + version.set(">= 0.1.0 & < 1.0.0") + } } } ascanrules { @@ -63,6 +66,7 @@ spotless { dependencies { zapAddOn("automation") zapAddOn("commonlib") + zapAddOn("pscan") implementation("net.bytebuddy:byte-buddy:1.14.13") diff --git a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/ExtensionScriptsUI.java b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/ExtensionScriptsUI.java index cdec8ccc538..56e4ab98239 100644 --- a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/ExtensionScriptsUI.java +++ b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/ExtensionScriptsUI.java @@ -45,12 +45,12 @@ import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.view.OptionsDialog; import org.parosproxy.paros.view.View; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.ZAP; import org.zaproxy.zap.extension.api.API; import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; import org.zaproxy.zap.extension.authentication.ExtensionAuthentication; import org.zaproxy.zap.extension.help.ExtensionHelp; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptEngineWrapper; import org.zaproxy.zap.extension.script.ScriptEventListener; @@ -656,7 +656,7 @@ public void scriptAdded(ScriptWrapper script, boolean display) { case ExtensionActiveScan.SCRIPT_TYPE_ACTIVE: activeScriptSynchronizer.scriptAdded(script); break; - case ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE: + case ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE: passiveScriptSynchronizer.scriptAdded(script); break; } @@ -689,7 +689,7 @@ public void scriptRemoved(ScriptWrapper script) { case ExtensionActiveScan.SCRIPT_TYPE_ACTIVE: activeScriptSynchronizer.scriptRemoved(script); break; - case ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE: + case ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE: passiveScriptSynchronizer.scriptRemoved(script); break; } @@ -744,7 +744,7 @@ public void scriptSaved(ScriptWrapper script) { case ExtensionActiveScan.SCRIPT_TYPE_ACTIVE: activeScriptSynchronizer.scriptAdded(script); break; - case ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE: + case ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE: passiveScriptSynchronizer.scriptAdded(script); break; } diff --git a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/autocomplete/ScriptAutoCompleteKeyListener.java b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/autocomplete/ScriptAutoCompleteKeyListener.java index 210a504e740..dff0dacf301 100644 --- a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/autocomplete/ScriptAutoCompleteKeyListener.java +++ b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/autocomplete/ScriptAutoCompleteKeyListener.java @@ -30,10 +30,10 @@ import javax.swing.SwingUtilities; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.authentication.ScriptBasedAuthenticationMethodType; import org.zaproxy.zap.control.ExtensionFactory; import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.scripts.ExtensionScriptsUI; @@ -102,10 +102,10 @@ public ScriptAutoCompleteKeyListener(JTextArea textInput) { // Passive Rules HashMap passiveRuleMap = new HashMap<>(); + passiveRuleMap.put("ps", "org.zaproxy.zap.extension.scripts.scanrules.PassiveScriptHelper"); passiveRuleMap.put("msg", "org.parosproxy.paros.network.HttpMessage"); - passiveRuleMap.put("ps", "org.zaproxy.zap.extension.pscan.scanner.ScriptsPassiveScanner"); passiveRuleMap.put("src", "net.htmlparser.jericho.Source"); - typeToClassMaps.put(ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE, passiveRuleMap); + typeToClassMaps.put(ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE, passiveRuleMap); // Payload Generator - none : has no parameters diff --git a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizer.java b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizer.java index e23dc8f062b..ba077d61c7c 100644 --- a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizer.java +++ b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizer.java @@ -26,7 +26,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.parosproxy.paros.control.Control; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptWrapper; @@ -35,7 +35,7 @@ public class PassiveScriptSynchronizer { private static final Logger LOGGER = LogManager.getLogger(PassiveScriptSynchronizer.class); private ExtensionScript extScript; - private ExtensionPassiveScan extPscan; + private ExtensionPassiveScan2 extPscan; private final Map scriptToScanRuleMap = new HashMap<>(); @@ -79,7 +79,7 @@ public void scriptAdded(ScriptWrapper script) { .getConstructor(ScriptWrapper.class, metadata.getClass()) .newInstance(script, metadata); - if (!getExtPscan().addPluginPassiveScanner(scanRule)) { + if (!getExtPscan().getPassiveScannersManager().add(scanRule)) { LOGGER.error("Failed to install script scan rule: {}", script.getName()); return; } @@ -108,7 +108,7 @@ public void unload() { } private boolean unloadScanRule(PassiveScriptScanRule scanRule) { - if (!getExtPscan().removePluginPassiveScanner(scanRule)) { + if (!getExtPscan().getPassiveScannersManager().remove(scanRule)) { LOGGER.error("Failed to uninstall script scan rule: {}", scanRule.getName()); return false; } @@ -123,12 +123,12 @@ private ExtensionScript getExtScript() { return extScript; } - private ExtensionPassiveScan getExtPscan() { + private ExtensionPassiveScan2 getExtPscan() { if (extPscan == null) { extPscan = Control.getSingleton() .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class); + .getExtension(ExtensionPassiveScan2.class); } return extPscan; } diff --git a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptSynchronizerUtils.java b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptSynchronizerUtils.java index 0d6b530af31..6017cad1f04 100644 --- a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptSynchronizerUtils.java +++ b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptSynchronizerUtils.java @@ -27,7 +27,7 @@ import org.parosproxy.paros.core.scanner.PluginFactory; import org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadata; import org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadataProvider; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptWrapper; @@ -76,7 +76,7 @@ static boolean hasClashingId(int id, ScriptWrapper script) { hasClashingId = true; existingRuleName = loadedPlugin.getName(); } else { - var pluginPassiveScanner = getExtPscan().getPluginPassiveScanner(id); + var pluginPassiveScanner = getExtPscan().getPassiveScannersManager().getScanRule(id); if (pluginPassiveScanner != null) { hasClashingId = true; existingRuleName = pluginPassiveScanner.getName(); @@ -100,7 +100,9 @@ private static ExtensionScript getExtScript() { return Control.getSingleton().getExtensionLoader().getExtension(ExtensionScript.class); } - private static ExtensionPassiveScan getExtPscan() { - return Control.getSingleton().getExtensionLoader().getExtension(ExtensionPassiveScan.class); + private static ExtensionPassiveScan2 getExtPscan() { + return Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionPassiveScan2.class); } } diff --git a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScanner.java b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScanner.java index f1bd6df903f..3b7da366384 100644 --- a/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScanner.java +++ b/addOns/scripts/src/main/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScanner.java @@ -26,7 +26,7 @@ import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.network.HttpMessage; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptWrapper; import org.zaproxy.zap.extension.script.ScriptsCache; @@ -52,7 +52,7 @@ private static ScriptsCache createScriptsCache() { } return extension.createScriptsCache( Configuration.builder() - .setScriptType(ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE) + .setScriptType(ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE) .setTargetInterface(PassiveScript.class) .setInterfaceProvider( (scriptWrapper, targetInterface) -> { diff --git a/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ActiveScriptSynchronizerUnitTest.java b/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ActiveScriptSynchronizerUnitTest.java index 4578b92d3b6..f68797b4228 100644 --- a/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ActiveScriptSynchronizerUnitTest.java +++ b/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ActiveScriptSynchronizerUnitTest.java @@ -41,14 +41,15 @@ import org.parosproxy.paros.model.Model; import org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadata; import org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadataProvider; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.PassiveScannersManager; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptWrapper; import org.zaproxy.zap.testutils.TestUtils; class ActiveScriptSynchronizerUnitTest extends TestUtils { - private ExtensionPassiveScan extensionPassiveScan; + private ExtensionPassiveScan2 extensionPassiveScan; private ExtensionScript extensionScript; private ExtensionLoader extensionLoader; private Model model; @@ -56,7 +57,7 @@ class ActiveScriptSynchronizerUnitTest extends TestUtils { @BeforeEach void setUp() throws Exception { setUpZap(); - extensionPassiveScan = mock(ExtensionPassiveScan.class); + extensionPassiveScan = mock(ExtensionPassiveScan2.class); extensionScript = mock(ExtensionScript.class); extensionLoader = mock(ExtensionLoader.class); model = mock(Model.class); @@ -175,8 +176,10 @@ private ScriptWrapper createScriptWrapper(T scriptInterface, Class script throws Exception { var script = mock(ScriptWrapper.class); given(extensionLoader.getExtension(ExtensionScript.class)).willReturn(extensionScript); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)) + given(extensionLoader.getExtension(ExtensionPassiveScan2.class)) .willReturn(extensionPassiveScan); + PassiveScannersManager scannersManager = mock(PassiveScannersManager.class); + given(extensionPassiveScan.getPassiveScannersManager()).willReturn(scannersManager); given(extensionScript.getInterface(script, scriptClass)).willReturn(scriptInterface); return script; } diff --git a/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizerUnitTest.java b/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizerUnitTest.java index 0af5931ad7e..40bc5329168 100644 --- a/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizerUnitTest.java +++ b/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/PassiveScriptSynchronizerUnitTest.java @@ -41,14 +41,16 @@ import org.parosproxy.paros.model.Model; import org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadata; import org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadataProvider; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; +import org.zaproxy.addon.pscan.PassiveScannersManager; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptWrapper; import org.zaproxy.zap.testutils.TestUtils; public class PassiveScriptSynchronizerUnitTest extends TestUtils { - private ExtensionPassiveScan extensionPassiveScan; + private ExtensionPassiveScan2 extensionPassiveScan; + private PassiveScannersManager scannersManager; private ExtensionScript extensionScript; private ExtensionLoader extensionLoader; private Model model; @@ -56,7 +58,8 @@ public class PassiveScriptSynchronizerUnitTest extends TestUtils { @BeforeEach void setUp() throws Exception { setUpZap(); - extensionPassiveScan = mock(ExtensionPassiveScan.class); + extensionPassiveScan = mock(ExtensionPassiveScan2.class); + scannersManager = mock(PassiveScannersManager.class); extensionScript = mock(ExtensionScript.class); extensionLoader = mock(ExtensionLoader.class); model = mock(Model.class); @@ -80,13 +83,13 @@ public ScanRuleMetadata getMetadata() { ScriptWrapper script = createScriptWrapper(metadataProvider, ScanRuleMetadataProvider.class); var scanRuleCaptor = ArgumentCaptor.forClass(PassiveScriptScanRule.class); - given(extensionPassiveScan.addPluginPassiveScanner(any())).willReturn(true); + given(scannersManager.add(any())).willReturn(true); // When try (var ignored = mockStatic(PluginFactory.class)) { synchronizer.scriptAdded(script); } // Then - verify(extensionPassiveScan, times(1)).addPluginPassiveScanner(scanRuleCaptor.capture()); + verify(scannersManager, times(1)).add(scanRuleCaptor.capture()); PassiveScriptScanRule scanRule = scanRuleCaptor.getValue(); assertThat(scanRule, is(notNullValue())); assertThat(scanRule.getPluginId(), is(equalTo(metadata.getId()))); @@ -108,7 +111,7 @@ public ScanRuleMetadata getMetadata() { }; ScriptWrapper script = createScriptWrapper(metadataProvider, ScanRuleMetadataProvider.class); - given(extensionPassiveScan.addPluginPassiveScanner(any())).willReturn(true); + given(scannersManager.add(any())).willReturn(true); // When try (var ignored = mockStatic(PluginFactory.class)) { synchronizer.scriptAdded(script); @@ -116,7 +119,7 @@ public ScanRuleMetadata getMetadata() { } // Then var scanRuleCaptor = ArgumentCaptor.forClass(PassiveScriptScanRule.class); - verify(extensionPassiveScan, times(1)).addPluginPassiveScanner(scanRuleCaptor.capture()); + verify(scannersManager, times(1)).add(scanRuleCaptor.capture()); PassiveScriptScanRule scanRule = scanRuleCaptor.getValue(); assertThat(scanRule, is(notNullValue())); assertThat(scanRule.getPluginId(), is(equalTo(metadata.getId()))); @@ -136,7 +139,7 @@ public ScanRuleMetadata getMetadata() { return metadata; } }; - given(extensionPassiveScan.addPluginPassiveScanner(any())).willReturn(true); + given(scannersManager.add(any())).willReturn(true); ScriptWrapper script = createScriptWrapper(metadataProvider, ScanRuleMetadataProvider.class); // When @@ -146,7 +149,7 @@ public ScanRuleMetadata getMetadata() { } // Then var scanRuleCaptor = ArgumentCaptor.forClass(PassiveScriptScanRule.class); - verify(extensionPassiveScan, times(1)).removePluginPassiveScanner(scanRuleCaptor.capture()); + verify(scannersManager, times(1)).remove(scanRuleCaptor.capture()); PassiveScriptScanRule scanRule = scanRuleCaptor.getValue(); assertThat(scanRule, is(notNullValue())); assertThat(scanRule.getPluginId(), is(equalTo(metadata.getId()))); @@ -178,8 +181,9 @@ private ScriptWrapper createScriptWrapper(T scriptInterface, Class script throws Exception { var script = mock(ScriptWrapper.class); given(extensionLoader.getExtension(ExtensionScript.class)).willReturn(extensionScript); - given(extensionLoader.getExtension(ExtensionPassiveScan.class)) + given(extensionLoader.getExtension(ExtensionPassiveScan2.class)) .willReturn(extensionPassiveScan); + given(extensionPassiveScan.getPassiveScannersManager()).willReturn(scannersManager); given(extensionScript.getInterface(script, scriptClass)).willReturn(scriptInterface); return script; } diff --git a/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScannerUnitTest.java b/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScannerUnitTest.java index d1308a3a3ed..d928304891d 100644 --- a/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScannerUnitTest.java +++ b/addOns/scripts/src/test/java/org/zaproxy/zap/extension/scripts/scanrules/ScriptsPassiveScannerUnitTest.java @@ -51,7 +51,7 @@ import org.parosproxy.paros.model.HistoryReference; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.network.HttpMessage; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.pscan.PassiveScanData; import org.zaproxy.zap.extension.pscan.PassiveScanTaskHelper; import org.zaproxy.zap.extension.script.ExtensionScript; @@ -65,7 +65,7 @@ /** Unit test for {@link ScriptsPassiveScanner}. */ class ScriptsPassiveScannerUnitTest extends TestUtils { - private static final String SCRIPT_TYPE = ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE; + private static final String SCRIPT_TYPE = ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE; private static final Class TARGET_INTERFACE = PassiveScript.class; private ExtensionLoader extensionLoader; diff --git a/addOns/wappalyzer/CHANGELOG.md b/addOns/wappalyzer/CHANGELOG.md index fcaa0bc6fdb..2e7d32dbfed 100644 --- a/addOns/wappalyzer/CHANGELOG.md +++ b/addOns/wappalyzer/CHANGELOG.md @@ -4,7 +4,8 @@ All notable changes to this add-on will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - +### Changed +- Depend on Passive Scanner add-on (Issue 7959). ## [21.43.0] - 2024-11-25 ### Changed diff --git a/addOns/wappalyzer/src/main/java/org/zaproxy/zap/extension/wappalyzer/ExtensionWappalyzer.java b/addOns/wappalyzer/src/main/java/org/zaproxy/zap/extension/wappalyzer/ExtensionWappalyzer.java index 7399e98d217..9b48f95346a 100644 --- a/addOns/wappalyzer/src/main/java/org/zaproxy/zap/extension/wappalyzer/ExtensionWappalyzer.java +++ b/addOns/wappalyzer/src/main/java/org/zaproxy/zap/extension/wappalyzer/ExtensionWappalyzer.java @@ -51,8 +51,8 @@ import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.view.View; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.alert.ExampleAlertProvider; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.search.ExtensionSearch; import org.zaproxy.zap.utils.ThreadUtils; import org.zaproxy.zap.view.ScanPanel; @@ -87,7 +87,7 @@ public class ExtensionWappalyzer extends ExtensionAdaptor /** The dependencies of the extension. */ private static final List> EXTENSION_DEPENDENCIES = - List.of(ExtensionPassiveScan.class); + List.of(ExtensionPassiveScan2.class); private TechPassiveScanner passiveScanner; @@ -188,12 +188,16 @@ public void hook(ExtensionHook extensionHook) { extensionHook.addApiImplementor(new TechApi(this)); extensionHook.addOptionsParamSet(techDetectParam); - ExtensionPassiveScan extPScan = + getPscanExtension().getPassiveScannersManager().add(passiveScanner); + extensionHook.addOptionsChangedListener(passiveScanner); + } + + private ExtensionPassiveScan2 getPscanExtension() { + ExtensionPassiveScan2 extPScan = Control.getSingleton() .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class); - extPScan.addPassiveScanner(passiveScanner); - extensionHook.addOptionsChangedListener(passiveScanner); + .getExtension(ExtensionPassiveScan2.class); + return extPScan; } @Override @@ -247,11 +251,7 @@ public boolean canUnload() { public void unload() { super.unload(); - ExtensionPassiveScan extPScan = - Control.getSingleton() - .getExtensionLoader() - .getExtension(ExtensionPassiveScan.class); - extPScan.removePassiveScanner(passiveScanner); + getPscanExtension().getPassiveScannersManager().remove(passiveScanner); } @Override diff --git a/addOns/wappalyzer/wappalyzer.gradle.kts b/addOns/wappalyzer/wappalyzer.gradle.kts index 576b459a25a..6cc3c5f5b32 100644 --- a/addOns/wappalyzer/wappalyzer.gradle.kts +++ b/addOns/wappalyzer/wappalyzer.gradle.kts @@ -28,6 +28,9 @@ zapAddOn { register("commonlib") { version.set(">= 1.17.0 & < 2.0.0") } + register("pscan") { + version.set(">= 0.1.0 & < 1.0.0") + } } } } @@ -41,6 +44,7 @@ zapAddOn { dependencies { zapAddOn("automation") zapAddOn("commonlib") + zapAddOn("pscan") compileOnly(libs.log4j.core) diff --git a/addOns/zest/CHANGELOG.md b/addOns/zest/CHANGELOG.md index 0104c1f8b91..4ebb6de31bf 100644 --- a/addOns/zest/CHANGELOG.md +++ b/addOns/zest/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Use Semantic Version. - Maintenance changes. +- Depend on Passive Scanner add-on (Issue 7959). ## [47] - 2024-09-24 ### Fixed diff --git a/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ExtensionZest.java b/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ExtensionZest.java index 49cd30f93fb..7742de33a55 100644 --- a/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ExtensionZest.java +++ b/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ExtensionZest.java @@ -59,11 +59,11 @@ import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.view.View; import org.zaproxy.addon.network.ExtensionNetwork; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.control.AddOn; import org.zaproxy.zap.extension.anticsrf.AntiCsrfToken; import org.zaproxy.zap.extension.anticsrf.ExtensionAntiCSRF; import org.zaproxy.zap.extension.httppanel.Message; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptEngineWrapper; import org.zaproxy.zap.extension.script.ScriptEventListener; @@ -1374,7 +1374,7 @@ public void pasteToNode( } if (ZestZapUtils.getShadowLevel(cnpNodes.get(i)) == 0 && (stmt.isPassive() - || !ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE.equals( + || !ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE.equals( script.getTypeName()))) { // Dont paste non passive statements into a passive script if (afterChild != null) { @@ -1453,7 +1453,7 @@ public boolean canPasteNodesTo(ScriptNode node) { ZestScriptWrapper script = this.getZestTreeModel().getScriptWrapper(node); - if (ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE.equals(script.getType().getName())) { + if (ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE.equals(script.getType().getName())) { isPassive = true; } diff --git a/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestScriptWrapper.java b/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestScriptWrapper.java index c75ffd6986b..13dd7dc7be1 100644 --- a/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestScriptWrapper.java +++ b/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestScriptWrapper.java @@ -23,9 +23,9 @@ import javax.script.ScriptException; import org.parosproxy.paros.control.Control; import org.zaproxy.addon.network.ExtensionNetwork; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.authentication.ScriptBasedAuthenticationMethodType; import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptWrapper; import org.zaproxy.zest.core.v1.ZestScript; @@ -59,7 +59,7 @@ public ZestScriptWrapper(ScriptWrapper script) { case "sequence": // ExtensionSequence.TYPE_SEQUENCE ztype = Type.Active; break; - case ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE: + case ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE: ztype = Type.Passive; break; case ExtensionScript.TYPE_TARGETED: diff --git a/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestTreeTransferHandler.java b/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestTreeTransferHandler.java index 73cac6343e2..8fbc46988fe 100644 --- a/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestTreeTransferHandler.java +++ b/addOns/zest/src/main/java/org/zaproxy/zap/extension/zest/ZestTreeTransferHandler.java @@ -28,7 +28,7 @@ import javax.swing.tree.TreePath; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.addon.pscan.ExtensionPassiveScan2; import org.zaproxy.zap.extension.script.ScriptNode; import org.zaproxy.zest.core.v1.ZestConditional; import org.zaproxy.zest.core.v1.ZestContainer; @@ -111,7 +111,7 @@ public boolean canImport(TransferHandler.TransferSupport support) { ZestScriptWrapper sw = extension.getZestTreeModel().getScriptWrapper(parent); if (sw == null) { return false; - } else if (ExtensionPassiveScan.SCRIPT_TYPE_PASSIVE.equals(sw.getTypeName()) + } else if (ExtensionPassiveScan2.SCRIPT_TYPE_PASSIVE.equals(sw.getTypeName()) && !isSafe(dragStmt)) { // LOGGER.debug("canImport cant paste unsafe stmts into passive script"); return false; diff --git a/addOns/zest/zest.gradle.kts b/addOns/zest/zest.gradle.kts index 8a4a2840644..b0e55f30275 100644 --- a/addOns/zest/zest.gradle.kts +++ b/addOns/zest/zest.gradle.kts @@ -28,6 +28,9 @@ zapAddOn { register("network") { version.set(">=0.2.0") } + register("pscan") { + version.set(">= 0.1.0 & < 1.0.0") + } register("scripts") { version.set(">=45.2.0") } @@ -42,6 +45,7 @@ zapAddOn { dependencies { zapAddOn("commonlib") zapAddOn("network") + zapAddOn("pscan") zapAddOn("scripts") zapAddOn("selenium")