From 461000e51241ea362f74061001b5ce01b08309f6 Mon Sep 17 00:00:00 2001 From: Simon Bennetts Date: Wed, 30 Oct 2024 15:28:19 +0000 Subject: [PATCH] Sequence: Initial seq ascan job implementation It is _not_ complete, but should be ready for review. The plan is to add to this in future PRs: - AF UI - Unit tests - More testing - Help - Desktop GUI - ... Signed-off-by: Simon Bennetts --- .../addon/automation/jobs/ActiveScanJob.java | 80 +---- .../automation/jobs/PolicyDefinition.java | 67 +++- .../jobs/ActiveScanJobUnitTest.java | 12 +- addOns/sequence/CHANGELOG.md | 2 + addOns/sequence/sequence.gradle.kts | 6 +- .../extension/sequence/ExtensionSequence.java | 5 +- .../sequence/StdActiveScanRunner.java | 167 ++++++++++ .../ExtensionSequenceAutomation.java | 10 + .../automation/SequenceActiveScanJob.java | 305 ++++++++++++++++++ .../sequence/resources/Messages.properties | 7 + addOns/zest/zest.gradle.kts | 2 +- 11 files changed, 574 insertions(+), 89 deletions(-) create mode 100644 addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/StdActiveScanRunner.java create mode 100644 addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/SequenceActiveScanJob.java diff --git a/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/ActiveScanJob.java b/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/ActiveScanJob.java index 8b0190eb6f9..e1f6a18d580 100644 --- a/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/ActiveScanJob.java +++ b/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/ActiveScanJob.java @@ -30,10 +30,6 @@ import org.apache.commons.lang3.StringUtils; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; -import org.parosproxy.paros.core.scanner.Plugin; -import org.parosproxy.paros.core.scanner.Plugin.AlertThreshold; -import org.parosproxy.paros.core.scanner.Plugin.AttackStrength; -import org.parosproxy.paros.core.scanner.PluginFactory; import org.zaproxy.addon.automation.AutomationData; import org.zaproxy.addon.automation.AutomationEnvironment; import org.zaproxy.addon.automation.AutomationJob; @@ -41,7 +37,6 @@ import org.zaproxy.addon.automation.ContextWrapper; import org.zaproxy.addon.automation.JobResultData; import org.zaproxy.addon.automation.gui.ActiveScanJobDialog; -import org.zaproxy.addon.automation.jobs.PolicyDefinition.Rule; import org.zaproxy.addon.commonlib.Constants; import org.zaproxy.zap.extension.ascan.ActiveScan; import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; @@ -182,7 +177,8 @@ public void runJob(AutomationEnvironment env, AutomationProgress progress) { // Error already raised above } } else { - scanPolicy = this.getScanPolicy(progress); + scanPolicy = + this.getData().getPolicyDefinition().getScanPolicy(this.getName(), progress); } if (scanPolicy != null) { contextSpecificObjects.add(scanPolicy); @@ -240,78 +236,6 @@ private List createJobResultData(int scanId) { return list; } - protected ScanPolicy getScanPolicy(AutomationProgress progress) { - ScanPolicy scanPolicy = new ScanPolicy(); - - // Set default strength - AttackStrength st = - JobUtils.parseAttackStrength( - this.getData().getPolicyDefinition().getDefaultStrength(), - this.getName(), - progress); - if (st != null) { - scanPolicy.setDefaultStrength(st); - progress.info( - Constant.messages.getString( - "automation.info.ascan.setdefstrength", this.getName(), st.name())); - } - - // Set default threshold - PluginFactory pluginFactory = scanPolicy.getPluginFactory(); - AlertThreshold th = - JobUtils.parseAlertThreshold( - this.getData().getPolicyDefinition().getDefaultThreshold(), - this.getName(), - progress); - if (th != null) { - scanPolicy.setDefaultThreshold(th); - if (th == AlertThreshold.OFF) { - for (Plugin plugin : pluginFactory.getAllPlugin()) { - plugin.setEnabled(false); - } - } else { - scanPolicy.setDefaultThreshold(th); - } - progress.info( - Constant.messages.getString( - "automation.info.ascan.setdefthreshold", this.getName(), th.name())); - } - - // Configure any rules - for (Rule rule : this.getData().getPolicyDefinition().getRules()) { - Plugin plugin = pluginFactory.getPlugin(rule.getId()); - if (plugin == null) { - // Will have already warned about this - continue; - } - AttackStrength pluginSt = - JobUtils.parseAttackStrength(rule.getStrength(), this.getName(), progress); - if (pluginSt != null) { - plugin.setAttackStrength(pluginSt); - plugin.setEnabled(true); - progress.info( - Constant.messages.getString( - "automation.info.ascan.rule.setstrength", - this.getName(), - rule.getId(), - pluginSt.name())); - } - AlertThreshold pluginTh = - JobUtils.parseAlertThreshold(rule.getThreshold(), this.getName(), progress); - if (pluginTh != null) { - plugin.setAlertThreshold(pluginTh); - plugin.setEnabled(!AlertThreshold.OFF.equals(pluginTh)); - progress.info( - Constant.messages.getString( - "automation.info.ascan.rule.setthreshold", - this.getName(), - rule.getId(), - pluginTh.name())); - } - } - return scanPolicy; - } - @Override public boolean isExcludeParam(String param) { switch (param) { diff --git a/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/PolicyDefinition.java b/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/PolicyDefinition.java index ed82b852fe9..5c30959c4e5 100644 --- a/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/PolicyDefinition.java +++ b/addOns/automation/src/main/java/org/zaproxy/addon/automation/jobs/PolicyDefinition.java @@ -31,6 +31,7 @@ import org.parosproxy.paros.core.scanner.PluginFactory; import org.zaproxy.addon.automation.AutomationData; import org.zaproxy.addon.automation.AutomationProgress; +import org.zaproxy.addon.automation.jobs.PolicyDefinition.Rule; import org.zaproxy.zap.extension.ascan.ScanPolicy; @Getter @@ -43,7 +44,7 @@ public class PolicyDefinition extends AutomationData { private String defaultThreshold = JobUtils.thresholdToI18n(AlertThreshold.MEDIUM.name()); private List rules = new ArrayList<>(); - protected static void parsePolicyDefinition( + public static void parsePolicyDefinition( Object policyDefnObj, PolicyDefinition policyDefinition, String jobName, @@ -116,6 +117,70 @@ protected static void parsePolicyDefinition( } } + public ScanPolicy getScanPolicy(String jobName, AutomationProgress progress) { + ScanPolicy scanPolicy = new ScanPolicy(); + + // Set default strength + AttackStrength st = JobUtils.parseAttackStrength(getDefaultStrength(), jobName, progress); + if (st != null) { + scanPolicy.setDefaultStrength(st); + progress.info( + Constant.messages.getString( + "automation.info.ascan.setdefstrength", jobName, st.name())); + } + + // Set default threshold + PluginFactory pluginFactory = scanPolicy.getPluginFactory(); + AlertThreshold th = JobUtils.parseAlertThreshold(getDefaultThreshold(), jobName, progress); + if (th != null) { + scanPolicy.setDefaultThreshold(th); + if (th == AlertThreshold.OFF) { + for (Plugin plugin : pluginFactory.getAllPlugin()) { + plugin.setEnabled(false); + } + } else { + scanPolicy.setDefaultThreshold(th); + } + progress.info( + Constant.messages.getString( + "automation.info.ascan.setdefthreshold", jobName, th.name())); + } + + // Configure any rules + for (Rule rule : getRules()) { + Plugin plugin = pluginFactory.getPlugin(rule.getId()); + if (plugin == null) { + // Will have already warned about this + continue; + } + AttackStrength pluginSt = + JobUtils.parseAttackStrength(rule.getStrength(), jobName, progress); + if (pluginSt != null) { + plugin.setAttackStrength(pluginSt); + plugin.setEnabled(true); + progress.info( + Constant.messages.getString( + "automation.info.ascan.rule.setstrength", + jobName, + rule.getId(), + pluginSt.name())); + } + AlertThreshold pluginTh = + JobUtils.parseAlertThreshold(rule.getThreshold(), jobName, progress); + if (pluginTh != null) { + plugin.setAlertThreshold(pluginTh); + plugin.setEnabled(!AlertThreshold.OFF.equals(pluginTh)); + progress.info( + Constant.messages.getString( + "automation.info.ascan.rule.setthreshold", + jobName, + rule.getId(), + pluginTh.name())); + } + } + return scanPolicy; + } + public void addRule(Rule rule) { this.rules.add(rule); } diff --git a/addOns/automation/src/test/java/org/zaproxy/addon/automation/jobs/ActiveScanJobUnitTest.java b/addOns/automation/src/test/java/org/zaproxy/addon/automation/jobs/ActiveScanJobUnitTest.java index e1962b355c2..598b5a334e5 100644 --- a/addOns/automation/src/test/java/org/zaproxy/addon/automation/jobs/ActiveScanJobUnitTest.java +++ b/addOns/automation/src/test/java/org/zaproxy/addon/automation/jobs/ActiveScanJobUnitTest.java @@ -445,7 +445,7 @@ void shouldReturnScanPolicyForDefaultData() throws MalformedURLException { // When job.setJobData(data); job.verifyParameters(progress); - ScanPolicy policy = job.getScanPolicy(progress); + ScanPolicy policy = job.getData().getPolicyDefinition().getScanPolicy(null, progress); // Then assertThat(policy, is(notNullValue())); @@ -469,7 +469,7 @@ void shouldSetScanPolicyDefaults() throws MalformedURLException { // When job.setJobData(data); job.verifyParameters(progress); - ScanPolicy policy = job.getScanPolicy(progress); + ScanPolicy policy = job.getData().getPolicyDefinition().getScanPolicy(null, progress); // Then assertThat(policy, is(notNullValue())); @@ -492,7 +492,7 @@ void shouldDisableAllRulesWithString() throws MalformedURLException { // When job.setJobData(data); job.verifyParameters(progress); - ScanPolicy policy = job.getScanPolicy(progress); + ScanPolicy policy = job.getData().getPolicyDefinition().getScanPolicy(null, progress); // Then assertThat(policy, is(notNullValue())); @@ -528,7 +528,7 @@ void shouldSetSpecifiedRuleConfigs() throws MalformedURLException { // When job.setJobData(data); job.verifyParameters(progress); - ScanPolicy policy = job.getScanPolicy(progress); + ScanPolicy policy = job.getData().getPolicyDefinition().getScanPolicy(null, progress); // Then assertThat(policy, is(notNullValue())); @@ -571,7 +571,7 @@ void shouldTurnOffSpecifiedRule() throws MalformedURLException { // When job.setJobData(data); job.verifyParameters(progress); - ScanPolicy policy = job.getScanPolicy(progress); + ScanPolicy policy = job.getData().getPolicyDefinition().getScanPolicy(null, progress); // Then assertThat(policy, is(notNullValue())); @@ -613,7 +613,7 @@ void shouldWarnOnUnknownRule() throws MalformedURLException { // When job.setJobData(data); job.verifyParameters(progress); - job.getScanPolicy(progress); + job.getData().getPolicyDefinition().getScanPolicy(null, progress); // Then assertThat(progress.hasWarnings(), is(equalTo(true))); diff --git a/addOns/sequence/CHANGELOG.md b/addOns/sequence/CHANGELOG.md index 0c22ff66d71..ff8e2c24314 100644 --- a/addOns/sequence/CHANGELOG.md +++ b/addOns/sequence/CHANGELOG.md @@ -4,6 +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 +### Added +- Initial sequence-activeScan implementation. ### Changed - Update minimum ZAP version to 2.15.0. - Maintenance changes. diff --git a/addOns/sequence/sequence.gradle.kts b/addOns/sequence/sequence.gradle.kts index e8209569ed9..ac251e1ab86 100644 --- a/addOns/sequence/sequence.gradle.kts +++ b/addOns/sequence/sequence.gradle.kts @@ -8,6 +8,7 @@ zapAddOn { url.set("https://www.zaproxy.org/docs/desktop/addons/sequence-scanner/") dependencies { addOns { + register("network") register("zest") { version.set("48.*") } @@ -20,7 +21,9 @@ zapAddOn { } dependencies { addOns { - register("automation") + register("automation") { + version.set(">= 0.44") + } register("exim") { version.set(">= 0.13") } @@ -35,6 +38,7 @@ dependencies { zapAddOn("automation") zapAddOn("commonlib") zapAddOn("exim") + zapAddOn("network") zapAddOn("zest") testImplementation(project(":testutils")) diff --git a/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/ExtensionSequence.java b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/ExtensionSequence.java index e6939173856..c3c505fb813 100644 --- a/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/ExtensionSequence.java +++ b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/ExtensionSequence.java @@ -34,6 +34,7 @@ import org.parosproxy.paros.extension.ExtensionHook; import org.parosproxy.paros.extension.ViewDelegate; import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.network.ExtensionNetwork; import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptCollection; @@ -45,7 +46,7 @@ public class ExtensionSequence extends ExtensionAdaptor implements ScannerHook { private static final List> DEPENDENCIES = - List.of(ExtensionScript.class, ExtensionZest.class); + List.of(ExtensionNetwork.class, ExtensionScript.class, ExtensionZest.class); private ExtensionScript extScript; private ExtensionActiveScan extActiveScan; @@ -225,7 +226,7 @@ private ExtensionScript getExtScript() { return extScript; } - private ExtensionActiveScan getExtActiveScan() { + protected ExtensionActiveScan getExtActiveScan() { if (extActiveScan == null) { extActiveScan = Control.getSingleton() diff --git a/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/StdActiveScanRunner.java b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/StdActiveScanRunner.java new file mode 100644 index 00000000000..9fbcf0d21cc --- /dev/null +++ b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/StdActiveScanRunner.java @@ -0,0 +1,167 @@ +/* + * 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.zap.extension.sequence; + +import java.io.IOException; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +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.SiteNode; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.network.ExtensionNetwork; +import org.zaproxy.zap.extension.ascan.ActiveScan; +import org.zaproxy.zap.extension.ascan.ScanPolicy; +import org.zaproxy.zap.extension.zest.ExtensionZest; +import org.zaproxy.zap.extension.zest.ZestScriptWrapper; +import org.zaproxy.zap.extension.zest.ZestZapRunner; +import org.zaproxy.zap.extension.zest.ZestZapUtils; +import org.zaproxy.zap.model.Target; +import org.zaproxy.zap.users.User; +import org.zaproxy.zest.core.v1.ZestActionFailException; +import org.zaproxy.zest.core.v1.ZestAssertFailException; +import org.zaproxy.zest.core.v1.ZestAssignFailException; +import org.zaproxy.zest.core.v1.ZestClientFailException; +import org.zaproxy.zest.core.v1.ZestInvalidCommonTestException; +import org.zaproxy.zest.core.v1.ZestRequest; +import org.zaproxy.zest.core.v1.ZestResponse; +import org.zaproxy.zest.core.v1.ZestScript; +import org.zaproxy.zest.core.v1.ZestStatement; + +public class StdActiveScanRunner extends ZestZapRunner { + + private static final int SEQUENCE_HISTORY_TYPE = HistoryReference.TYPE_SEQUENCE_TEMPORARY; + private static final Logger LOGGER = LogManager.getLogger(StdActiveScanRunner.class); + + private ZestScriptWrapper wrapper; + private ScanPolicy scanPolicy; + private User user; + + private final String name; + private final SiteNode fakeRoot; + private final SiteNode fakeDirectory; + private int step; + + private final ExtensionHistory extHistory; + private final ExtensionSequence extSeq; + + public StdActiveScanRunner(ZestScriptWrapper wrapper, ScanPolicy scanPolicy, User user) { + super( + Control.getSingleton().getExtensionLoader().getExtension(ExtensionZest.class), + Control.getSingleton().getExtensionLoader().getExtension(ExtensionNetwork.class), + wrapper); + + this.wrapper = wrapper; + this.scanPolicy = scanPolicy; + this.user = user; + + this.extHistory = + Control.getSingleton().getExtensionLoader().getExtension(ExtensionHistory.class); + this.extSeq = + Control.getSingleton().getExtensionLoader().getExtension(ExtensionSequence.class); + + this.name = wrapper.getName(); + fakeRoot = new SiteNode(null, HistoryReference.TYPE_SEQUENCE_TEMPORARY, name); + fakeDirectory = new SiteNode(null, HistoryReference.TYPE_SEQUENCE_TEMPORARY, name); + fakeRoot.add(fakeDirectory); + step = 0; + } + + @Override + public String run(ZestScript script, Map params) + throws ZestAssertFailException, + ZestActionFailException, + IOException, + ZestInvalidCommonTestException, + ZestAssignFailException, + ZestClientFailException { + return super.run(this.wrapper.getZestScript(), params); + } + + @Override + public ZestResponse runStatement( + ZestScript script, ZestStatement stmt, ZestResponse lastResponse) + throws ZestAssertFailException, + ZestActionFailException, + ZestInvalidCommonTestException, + IOException, + ZestAssignFailException, + ZestClientFailException { + ZestResponse resp = super.runStatement(script, stmt, lastResponse); + + if (stmt instanceof ZestRequest) { + step += 1; + HttpMessage msg = + ZestZapUtils.toHttpMessage(this.getLastRequest(), this.getLastResponse()); + SiteNode node = messageToSiteNode(msg, step); + if (node != null) { + fakeDirectory.add(node); + + int scanId = + extSeq.getExtActiveScan() + .startScan(new Target(node), user, new Object[] {scanPolicy}); + + ActiveScan ascan = extSeq.getExtActiveScan().getScan(scanId); + while (ascan.isRunning()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + + return resp; + } + + private SiteNode messageToSiteNode(HttpMessage msg, int step) { + SiteNode temp = null; + try { + temp = + new SiteNode( + null, + SEQUENCE_HISTORY_TYPE, + Constant.messages.getString("sequence.automation.step", step)); + HistoryReference ref = + new HistoryReference( + extHistory.getModel().getSession(), SEQUENCE_HISTORY_TYPE, msg); + + extHistory.addHistory(ref); + // The "ALERT-TAG" prefix means these tags will propagate to any alerts raised against + // them dropping that prefix. + // FIXME: replace with core constant after 2.16 + ref.addTag("ALERT-TAG:ZAP-SEQ-NAME=" + wrapper.getName()); + ref.addTag("ALERT-TAG:ZAP-SEQ-INDEX=" + step); + + temp.setHistoryReference(ref); + + } catch (Exception e) { + LOGGER.error( + "An exception occurred while converting a HttpMessage to SiteNode: {}", + e.getMessage(), + e); + } + return temp; + } +} diff --git a/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/ExtensionSequenceAutomation.java b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/ExtensionSequenceAutomation.java index 3c29c0bf644..b02682e24a1 100644 --- a/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/ExtensionSequenceAutomation.java +++ b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/ExtensionSequenceAutomation.java @@ -27,6 +27,8 @@ import org.parosproxy.paros.extension.ExtensionHook; import org.zaproxy.addon.automation.ExtensionAutomation; import org.zaproxy.addon.exim.ExtensionExim; +import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; +import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.sequence.ExtensionSequence; import org.zaproxy.zap.extension.zest.ExtensionZest; @@ -38,6 +40,7 @@ public class ExtensionSequenceAutomation extends ExtensionAdaptor { List.of(ExtensionAutomation.class, ExtensionExim.class, ExtensionSequence.class); private SequenceImportJob importJob; + private SequenceActiveScanJob ascanJob; public ExtensionSequenceAutomation() { super(NAME); @@ -58,6 +61,12 @@ public void hook(ExtensionHook extensionHook) { getExtension(ExtensionExim.class), getExtension(ExtensionZest.class)); extAuto.registerAutomationJob(importJob); + + ascanJob = + new SequenceActiveScanJob( + getExtension(ExtensionActiveScan.class), + getExtension(ExtensionScript.class)); + extAuto.registerAutomationJob(ascanJob); } private static T getExtension(Class clazz) { @@ -74,6 +83,7 @@ public void unload() { ExtensionAutomation extAuto = getExtension(ExtensionAutomation.class); extAuto.unregisterAutomationJob(importJob); + extAuto.unregisterAutomationJob(ascanJob); } @Override diff --git a/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/SequenceActiveScanJob.java b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/SequenceActiveScanJob.java new file mode 100644 index 00000000000..13211771974 --- /dev/null +++ b/addOns/sequence/src/main/java/org/zaproxy/zap/extension/sequence/automation/SequenceActiveScanJob.java @@ -0,0 +1,305 @@ +/* + * 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.zap.extension.sequence.automation; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.zaproxy.addon.automation.AutomationData; +import org.zaproxy.addon.automation.AutomationEnvironment; +import org.zaproxy.addon.automation.AutomationJob; +import org.zaproxy.addon.automation.AutomationJobException; +import org.zaproxy.addon.automation.AutomationProgress; +import org.zaproxy.addon.automation.ContextWrapper; +import org.zaproxy.addon.automation.JobResultData; +import org.zaproxy.addon.automation.jobs.ActiveScanJobResultData; +import org.zaproxy.addon.automation.jobs.JobData; +import org.zaproxy.addon.automation.jobs.JobUtils; +import org.zaproxy.addon.automation.jobs.PolicyDefinition; +import org.zaproxy.zap.extension.ascan.ActiveScan; +import org.zaproxy.zap.extension.ascan.ExtensionActiveScan; +import org.zaproxy.zap.extension.ascan.ScanPolicy; +import org.zaproxy.zap.extension.script.ExtensionScript; +import org.zaproxy.zap.extension.script.ScriptWrapper; +import org.zaproxy.zap.extension.sequence.ExtensionSequence; +import org.zaproxy.zap.extension.sequence.StdActiveScanRunner; +import org.zaproxy.zap.extension.zest.ZestScriptWrapper; +import org.zaproxy.zap.model.Target; +import org.zaproxy.zap.users.User; + +public class SequenceActiveScanJob extends AutomationJob { + + public static final String JOB_NAME = "sequence-activeScan"; + + private static final Logger LOGGER = LogManager.getLogger(SequenceActiveScanJob.class); + + private static final String PARAM_CONTEXT = "context"; + private static final String PARAM_POLICY = "policy"; + private static final String PARAM_SEQUENCE = "sequence"; + private static final String PARAM_USER = "user"; + + private final ExtensionActiveScan extAScan; + private final ExtensionScript extScript; + + private Parameters parameters = new Parameters(); + private PolicyDefinition policyDefinition = new PolicyDefinition(); + private Data data; + + public SequenceActiveScanJob(ExtensionActiveScan extAScan, ExtensionScript extScript) { + this.extAScan = extAScan; + this.extScript = extScript; + data = new Data(this, this.parameters, this.policyDefinition); + } + + @Override + public AutomationJob newJob() throws AutomationJobException { + return new SequenceActiveScanJob(extAScan, extScript); + } + + @Override + public boolean supportsAlertTests() { + return true; + } + + @Override + public String getKeyAlertTestsResultData() { + return ActiveScanJobResultData.KEY; + } + + @Override + public void verifyParameters(AutomationProgress progress) { + Map jobData = this.getJobData(); + if (jobData == null) { + return; + } + + for (Object key : jobData.keySet().toArray()) { + switch (key.toString()) { + case "parameters": + LinkedHashMap params = (LinkedHashMap) jobData.get(key); + JobUtils.applyParamsToObject( + params, this.parameters, this.getName(), null, progress); + break; + case "policyDefinition": + // Parse the policy defn + PolicyDefinition.parsePolicyDefinition( + jobData.get(key), policyDefinition, this.getName(), progress); + break; + case "name": + case "tests": + case "type": + // Handled before we get here + break; + default: + progress.warn( + Constant.messages.getString( + "automation.error.element.unknown", this.getName(), key)); + + break; + } + } + + this.verifyUser(this.getParameters().getUser(), progress); + } + + @Override + public void applyParameters(AutomationProgress progress) { + JobUtils.applyObjectToObject( + this.parameters, + JobUtils.getJobOptions(this, progress), + this.getName(), + new String[] {PARAM_SEQUENCE, PARAM_POLICY, PARAM_CONTEXT, PARAM_USER}, + progress, + this.getPlan().getEnv()); + } + + @Override + public Map getCustomConfigParameters() { + Map map = super.getCustomConfigParameters(); + map.put(PARAM_CONTEXT, ""); + return map; + } + + @Override + public boolean supportsMonitorTests() { + return true; + } + + @Override + public void runJob(AutomationEnvironment env, AutomationProgress progress) { + + extAScan.setPanelSwitch(false); + try { + + ContextWrapper context; + if (StringUtils.isNotEmpty(this.getParameters().getContext())) { + context = env.getContextWrapper(this.getParameters().getContext()); + if (context == null) { + progress.error( + Constant.messages.getString( + "sequence.automation.error.context.unknown", + this.getParameters().getContext())); + return; + } + } else { + context = env.getDefaultContextWrapper(); + } + + Target target = new Target(context.getContext()); + target.setRecurse(true); + List contextSpecificObjects = new ArrayList<>(); + User user = this.getUser(this.getParameters().getUser(), progress); + + ScanPolicy scanPolicy = null; + if (!StringUtils.isEmpty(this.getParameters().getPolicy())) { + try { + scanPolicy = + extAScan.getPolicyManager().getPolicy(this.getParameters().getPolicy()); + } catch (ConfigurationException e) { + // Error already raised above + } + } else { + scanPolicy = + this.getData() + .getPolicyDefinition() + .getScanPolicy(this.getName(), progress); + } + if (scanPolicy != null) { + contextSpecificObjects.add(scanPolicy); + } + + List scripts = extScript.getScripts(ExtensionSequence.TYPE_SEQUENCE); + + Optional scriptWrapper = + scripts.stream() + .filter(s -> s.getName().equals(this.parameters.getSequence())) + .findFirst(); + + if (scriptWrapper.isEmpty() || !(scriptWrapper.get() instanceof ZestScriptWrapper)) { + progress.error( + Constant.messages.getString( + "sequence.automation.error.sequence.unknown", + this.getParameters().getSequence())); + return; + } + + StdActiveScanRunner zzr = + new StdActiveScanRunner( + (ZestScriptWrapper) scriptWrapper.get(), scanPolicy, user); + + try { + zzr.run(null, null); + } catch (Exception e) { + progress.error( + Constant.messages.getString( + "automation.error.unexpected.internal", e.getMessage())); + LOGGER.error(e.getMessage(), e); + } + } finally { + extAScan.setPanelSwitch(true); + } + } + + @Override + public List getJobResultData() { + ActiveScan lastScan = this.extAScan.getLastScan(); + if (lastScan != null) { + return createJobResultData(lastScan.getId()); + } + return new ArrayList<>(); + } + + private List createJobResultData(int scanId) { + List list = new ArrayList<>(); + list.add(new ActiveScanJobResultData(this.getName(), this.extAScan.getScan(scanId))); + return list; + } + + @Override + public String getSummary() { + return Constant.messages.getString( + "sequence.automation.ascan.summary", this.getParameters().getSequence()); + } + + @Override + public Data getData() { + return data; + } + + @Override + public Parameters getParameters() { + return parameters; + } + + @Override + public String getType() { + return JOB_NAME; + } + + @Override + public Order getOrder() { + return Order.ATTACK; + } + + @Override + public Object getParamMethodObject() { + return null; + } + + @Override + public String getParamMethodName() { + return null; + } + + @Override + public void showDialog() { + // TODO Implement in a future PR + } + + @Getter + public static class Data extends JobData { + private final Parameters parameters; + private final PolicyDefinition policyDefinition; + + public Data(AutomationJob job, Parameters parameters, PolicyDefinition policyDefinition) { + super(job); + this.parameters = parameters; + this.policyDefinition = policyDefinition; + } + } + + @Getter + @Setter + public static class Parameters extends AutomationData { + private String sequence = ""; + private String context = ""; + private String user = ""; + private String policy = ""; + } +} diff --git a/addOns/sequence/src/main/resources/org/zaproxy/zap/extension/sequence/resources/Messages.properties b/addOns/sequence/src/main/resources/org/zaproxy/zap/extension/sequence/resources/Messages.properties index e6e67111057..784952aa13d 100644 --- a/addOns/sequence/src/main/resources/org/zaproxy/zap/extension/sequence/resources/Messages.properties +++ b/addOns/sequence/src/main/resources/org/zaproxy/zap/extension/sequence/resources/Messages.properties @@ -1,6 +1,12 @@ +sequence.automation.ascan.summary = Sequence: {0} + sequence.automation.desc = Sequence Automation Framework Integration sequence.automation.dialog.jobName = Job Name: + +sequence.automation.error.context.unknown = Unrecognised context: {0} sequence.automation.error.noresourcefile = Cannot access file: {0} +sequence.automation.error.sequence.unknown = - Unrecognised sequence: {0} + sequence.automation.import.dialog.assertCode = Assert Status Code: sequence.automation.import.dialog.assertLength = Assert Length: sequence.automation.import.dialog.name = Sequence Name: @@ -13,6 +19,7 @@ sequence.automation.import.script.error = Job {0}: Error creating sequence: {1} sequence.automation.import.sequencecreated = Job {0}: Created sequence with {1} message(s). sequence.automation.import.summary = Name: {0}, Path: {1} sequence.automation.name = Sequence Automation +sequence.automation.step = Step {0} sequence.custom.tab.description = Sequences are defined as scripts. sequence.custom.tab.deselectall.label = Deselect All Sequence Scripts diff --git a/addOns/zest/zest.gradle.kts b/addOns/zest/zest.gradle.kts index 331d91bb321..8a4a2840644 100644 --- a/addOns/zest/zest.gradle.kts +++ b/addOns/zest/zest.gradle.kts @@ -45,7 +45,7 @@ dependencies { zapAddOn("scripts") zapAddOn("selenium") - implementation("org.zaproxy:zest:0.22.0") { + api("org.zaproxy:zest:0.22.0") { // Provided by commonlib add-on. exclude(group = "com.fasterxml.jackson") // Provided by Selenium add-on.