/* * * Paros and its related class files. * * Paros is an HTTP/HTTPS proxy for assessing web application security. * Copyright (C) 2003-2004 Chinotec Technologies Company * * This program is free software; you can redistribute it and/or * modify it under the terms of the Clarified Artistic License * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Clarified Artistic License for more details. * * You should have received a copy of the Clarified Artistic License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // ZAP: 2011/05/15 Support for exclusions // ZAP: 2011/08/30 Support for scanner levels // ZAP: 2012/02/18 Dont log errors for temporary hrefs // ZAP: 2012/03/15 Changed the method getPathRegex to use the class StringBuilder // instead of StringBuffer and replaced some string concatenations with calls // to the method append of the class StringBuilder. Removed unnecessary castings // in the methods scanSingleNode, notifyHostComplete and pluginCompleted. Changed // the methods processPlugin and pluginCompleted to use Long.valueOf instead of // creating a new Long. // ZAP: 2012/04/25 Added @Override annotation to the appropriate method. // ZAP: 2012/07/30 Issue 43: Added support for Scope // ZAP: 2012/08/07 Issue 342 Support the HttpSenderListener // ZAP: 2012/08/07 Renamed Level to AlertThreshold and added support for AttackStrength // ZAP: 2012/08/31 Enabled control of AttackStrength // ZAP: 2012/11/22 Issue 421: Cleanly shut down any active scan threads on shutdown // ZAP: 2013/01/19 Issue 460 Add support for a scan progress dialog // ZAP: 2013/03/08 Added some debug logging // ZAP: 2014/01/16 Add support to plugin skipping // ZAP: 2014/02/21 Issue 1043: Custom active scan dialog // ZAP: 2014/03/23 Issue 1084: NullPointerException while selecting a node in the "Sites" tab // ZAP: 2014/04/01 Changed to set a name to created threads. // ZAP: 2014/06/23 Issue 1241: Active scanner might not report finished state when using host scanners // ZAP: 2014/06/26 Added the possibility to evaluate the current plugin/process progress // ZAP: 2014/07/07 Issue 389: Enable technology scope for scanners // ZAP: 2014/08/14 Issue 1291: 407 Proxy Authentication Required while active scanning // ZAP: 2014/10/24 Issue 1378: Revamp active scan panel // ZAP: 2014/10/25 Issue 1062: Made it possible to hook into the active scanner from extensions // ZAP: 2014/11/19 Issue 1412: Manage scan policies // ZAP: 2015/02/18 Issue 1062: Tidied up extension hooks // ZAP: 2015/04/02 Issue 321: Support multiple databases and Issue 1582: Low memory option // ZAP: 2015/04/17 A problem occur when a single node should be scanned because count start from -1 // ZAP: 2015/05/04 Issue 1566: Improve active scan's reported progress // ZAP: 2015/07/26 Issue 1618: Target Technology Not Honored // ZAP: 2015/10/29 Issue 2005: Active scanning incorrectly performed on sibling nodes // ZAP: 2015/11/27 Issue 2086: Report request counts per plugin // ZAP: 2015/12/16 Prevent HostProcess (and plugins run) from becoming in undefined state // ZAP: 2016/01/27 Prevent HostProcess from reporting progress higher than 100% // ZAP: 2016/04/21 Allow scanners to notify of messages sent (and tweak the progress and request count of each plugin) // ZAP: 2016/06/29 Allow to specify and obtain the reason why a scanner was skipped // ZAP: 2016/07/12 Do not allow techSet to be null // ZAP: 2016/07/01 Issue 2647 Support a/pscan rule configuration // ZAP: 2016/09/20 - Reorder statements to prevent (potential) NullPointerException in scanSingleNode // - JavaDoc tweaks // ZAP: 2016/11/14 Restore and deprecate old constructor, to keep binary compatibility // ZAP: 2016/12/13 Issue 2951: Support active scan rule and scan max duration // ZAP: 2016/12/20 Include the name of the user when logging the scan info // ZAP: 2017/03/20 Improve node enumeration in pre-scan phase. // ZAP: 2017/03/20 Log the number of messages sent by the scanners, when finished. // ZAP: 2017/03/25 Ensure messages to be scanned have a response. package org.parosproxy.paros.core.scanner; import java.io.IOException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.common.ThreadPool; import org.parosproxy.paros.db.DatabaseException; import org.parosproxy.paros.model.HistoryReference; import org.parosproxy.paros.network.ConnectionParam; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpSender; import org.zaproxy.zap.extension.ascan.ScanPolicy; import org.zaproxy.zap.extension.ruleconfig.RuleConfig; import org.zaproxy.zap.extension.ruleconfig.RuleConfigParam; import org.zaproxy.zap.model.SessionStructure; import org.zaproxy.zap.model.StructuralNode; import org.zaproxy.zap.model.TechSet; import org.zaproxy.zap.users.User; public class HostProcess implements Runnable { private static final Logger log = Logger.getLogger(HostProcess.class); private static final DecimalFormat decimalFormat = new java.text.DecimalFormat("###0.###"); private List<StructuralNode> startNodes = null; private boolean isStop = false; private PluginFactory pluginFactory; private ScannerParam scannerParam = null; private HttpSender httpSender = null; private ThreadPool threadPool = null; private Scanner parentScanner = null; private String hostAndPort = ""; private Analyser analyser = null; private Kb kb = null; private User user = null; private TechSet techSet; private RuleConfigParam ruleConfigParam; private String stopReason = null; /** * A {@code Map} from plugin IDs to corresponding {@link PluginStats}. * * @see #processPlugin(Plugin) */ private final Map<Integer, PluginStats> mapPluginStats = new HashMap<>(); private long hostProcessStartTime = 0; // ZAP: progress related private int nodeInScopeCount = 0; private int percentage = 0; /** * The count of requests sent by the {@code HostProcess} itself. */ private int requestCount; /** * Constructs a {@code HostProcess}, with no rules' configurations. * * @param hostAndPort the host:port value of the site that need to be processed * @param parentScanner the scanner instance which instantiated this process * @param scannerParam the session scanner parameters * @param connectionParam the connection parameters * @param scanPolicy the scan policy * @deprecated Use {@link #HostProcess(String, Scanner, ScannerParam, ConnectionParam, ScanPolicy, RuleConfigParam)} * instead. It will be removed in a future version. */ @Deprecated public HostProcess(String hostAndPort, Scanner parentScanner, ScannerParam scannerParam, ConnectionParam connectionParam, ScanPolicy scanPolicy) { this(hostAndPort, parentScanner, scannerParam, connectionParam, scanPolicy, null); } /** * Constructs a {@code HostProcess}. * * @param hostAndPort the host:port value of the site that need to be processed * @param parentScanner the scanner instance which instantiated this process * @param scannerParam the session scanner parameters * @param connectionParam the connection parameters * @param scanPolicy the scan policy * @param ruleConfigParam the rules' configurations, might be {@code null}. * @since 2.6.0 */ public HostProcess(String hostAndPort, Scanner parentScanner, ScannerParam scannerParam, ConnectionParam connectionParam, ScanPolicy scanPolicy, RuleConfigParam ruleConfigParam) { super(); this.hostAndPort = hostAndPort; this.parentScanner = parentScanner; this.scannerParam = scannerParam; this.pluginFactory = scanPolicy.getPluginFactory().clone(); this.ruleConfigParam = ruleConfigParam; httpSender = new HttpSender(connectionParam, true, HttpSender.ACTIVE_SCANNER_INITIATOR); httpSender.setUser(this.user); httpSender.setRemoveUserDefinedAuthHeaders(true); int maxNumberOfThreads; if (scannerParam.getHandleAntiCSRFTokens()) { // Single thread if handling anti CSRF tokens, otherwise token requests might get out of step maxNumberOfThreads = 1; } else { maxNumberOfThreads = scannerParam.getThreadPerHost(); } threadPool = new ThreadPool(maxNumberOfThreads, "ZAP-ActiveScanner-"); this.techSet = TechSet.AllTech; } /** * Set the initial starting node. * Should be set after the HostProcess initialization * @param startNode the start node we should start from */ public void setStartNode(StructuralNode startNode) { this.startNodes = new ArrayList<StructuralNode>(); this.startNodes.add(startNode); } public void addStartNode(StructuralNode startNode) { if (this.startNodes == null) { this.startNodes = new ArrayList<StructuralNode>(); } this.startNodes.add(startNode); } /** * Stop the current scanning process */ public void stop() { isStop = true; getAnalyser().stop(); } /** * Main execution method */ @Override public void run() { log.debug("HostProcess.run"); try { TraverseCounter counter = new TraverseCounter(); hostProcessStartTime = System.currentTimeMillis(); for (StructuralNode node : startNodes) { // ZAP: before all get back the size of this scan traverse(node, true, counter); // ZAP: begin to analyze the scope getAnalyser().start(node); } nodeInScopeCount = counter.getCount(); logScanInfo(); Plugin plugin; while (!isStop() && pluginFactory.existPluginToRun()) { plugin = pluginFactory.nextPlugin(); if (plugin != null) { plugin.setDelayInMs(this.scannerParam.getDelayInMs()); plugin.setTechSet(this.techSet); processPlugin(plugin); } else { // waiting for dependency - no test ready yet Util.sleep(1000); } } threadPool.waitAllThreadComplete(300000); } catch (Exception e) { log.error("An error occurred while active scanning:", e); stop(); } finally { notifyHostProgress(null); notifyHostComplete(); getHttpSender().shutdown(); } } /** * Logs information about the scan. * <p> * It logs the {@link #nodeInScopeCount number of nodes} that will be scanned and the name of the {@link #user}, if any. */ private void logScanInfo() { StringBuilder strBuilder = new StringBuilder(150); strBuilder.append("Scanning "); strBuilder.append(nodeInScopeCount); strBuilder.append(" node(s) "); if (parentScanner.getJustScanInScope()) { strBuilder.append("[just in scope] "); } strBuilder.append("from ").append(hostAndPort); if (user != null) { strBuilder.append(" as "); strBuilder.append(user.getName()); } log.info(strBuilder.toString()); } private void processPlugin(final Plugin plugin) { synchronized (mapPluginStats) { mapPluginStats.put(plugin.getId(), new PluginStats()); } if (!plugin.targets(techSet)) { pluginSkipped(plugin, Constant.messages.getString("ascan.progress.label.skipped.reason.techs")); pluginCompleted(plugin); return; } log.info("start host " + hostAndPort + " | " + plugin.getCodeName() + " strength " + plugin.getAttackStrength() + " threshold " + plugin.getAlertThreshold()); for (StructuralNode startNode : startNodes) { if (plugin instanceof AbstractHostPlugin) { if (!scanSingleNode(plugin, startNode)) { // Mark the plugin as as completed if it was not run so the scan process can continue as expected. // The plugin might not be run if the startNode: is not in scope, is explicitly excluded, ... pluginCompleted(plugin); } } else if (plugin instanceof AbstractAppPlugin) { try { traverse(startNode, true, new TraverseAction() { @Override public void apply(StructuralNode node) { log.debug("traverse: plugin=" + plugin.getName() + " url=" + node.getName()); scanSingleNode(plugin, node); } @Override public boolean isStopTraversing() { return isSkipped(plugin); } }); threadPool.waitAllThreadComplete(600000); } finally { pluginCompleted(plugin); } } } } private void traverse(StructuralNode node, TraverseAction action) { this.traverse(node, false, action); } private void traverse(StructuralNode node, boolean incRelatedSiblings, TraverseAction action) { if (node == null || isStop()) { return; } Set<StructuralNode> parentNodes = new HashSet<>(); parentNodes.add(node); action.apply(node); if (!action.isStopTraversing() && parentScanner.scanChildren()) { if (incRelatedSiblings) { // Also match siblings with the same hierarchic name // If we dont do this http://localhost/start might match the GET variant // in the Sites tree and miss the hierarchic node. // Note that this is only done for the top level try { Iterator<StructuralNode> iter = node.getParent().getChildIterator(); String nodeName = SessionStructure.getCleanRelativeName(node, false); while (iter.hasNext()) { StructuralNode sibling = iter.next(); if (! node.isSameAs(sibling) && nodeName.equals( SessionStructure.getCleanRelativeName(sibling, false))) { log.debug("traverse: including related sibling " + sibling.getName()); parentNodes.add(sibling); } } } catch (DatabaseException e) { // Ignore - if we cant connect to the db there will be plenty of other errors logged ;) } } for (StructuralNode pNode : parentNodes) { Iterator<StructuralNode> iter = pNode.getChildIterator(); while (iter.hasNext() && !isStop() && !action.isStopTraversing()) { StructuralNode child = iter.next(); // ZAP: Implement pause and resume while (parentScanner.isPaused() && !isStop()) { Util.sleep(500); } try { traverse(child, action); } catch (Exception e) { log.error(e.getMessage(), e); } } } } } protected boolean nodeInScope(String nodeName) { return parentScanner.isInScope(nodeName); } /** * Create new plugin instance and run against a node * * @param plugin the scanner * @param node the node to scan, ignored if {@code null}. * @return {@code true} if the {@code plugin} was run, {@code false} otherwise. */ private boolean scanSingleNode(Plugin plugin, StructuralNode node) { Thread thread; Plugin test; HttpMessage msg; // do not poll for isStop here to allow every plugin to run but terminate immediately. //if (isStop()) return; if (!canScanNode(node)) { return false; } try { HistoryReference hRef = node.getHistoryReference(); msg = hRef.getHttpMessage(); if (msg == null) { // Likely to be a temporary node log.debug("scanSingleNode msg null"); return false; } // Ensure the temporary nodes, added automatically to Sites tree, have a response. // The scanners might base the logic/attacks on the state of the response (e.g. status code). if (msg.getResponseHeader().isEmpty()) { msg = msg.cloneRequest(); if (!obtainResponse(hRef, msg)) { return false; } } log.debug("scanSingleNode node plugin=" + plugin.getName() + " node=" + node.getName()); test = plugin.getClass().newInstance(); test.setConfig(plugin.getConfig()); if (this.ruleConfigParam != null) { // Set the configuration rules for (RuleConfig rc : this.ruleConfigParam.getAllRuleConfigs()) { test.getConfig().setProperty(rc.getKey(), rc.getValue()); } } test.setDelayInMs(plugin.getDelayInMs()); test.setDefaultAlertThreshold(plugin.getAlertThreshold()); test.setDefaultAttackStrength(plugin.getAttackStrength()); test.setTechSet(getTechSet()); test.init(msg, this); notifyHostProgress(plugin.getName() + ": " + msg.getRequestHeader().getURI().toString()); } catch (Exception e) { log.error(e.getMessage() + " " + node.getName(), e); return false; } do { if (this.isStop()) { return false; } thread = threadPool.getFreeThreadAndRun(test); if (thread == null) { Util.sleep(200); } } while (thread == null); mapPluginStats.get(plugin.getId()).incProgress(); return true; } private boolean obtainResponse(HistoryReference hRef, HttpMessage message) { try { getHttpSender().sendAndReceive(message); notifyNewMessage(message); requestCount++; return true; } catch (IOException e) { log.warn( "Failed to obtain the HTTP response for href [id=" + hRef.getHistoryId() + ", type=" + hRef.getHistoryType() + ", URL=" + hRef.getURI() + "]: " + e.getMessage()); return false; } } /** * Tells whether or not the scanner can scan the given node. * <p> * A node must not be null, must contain a valid HistoryReference and be in scope. * * @param node the node to be checked * @return {@code true} if the node can be scanned, {@code false} otherwise. */ private boolean canScanNode(StructuralNode node) { if (node == null) { if (log.isDebugEnabled()) { log.debug("Ignoring null node"); } return false; } HistoryReference hRef = node.getHistoryReference(); if (hRef == null) { if (log.isDebugEnabled()) { log.debug("Ignoring null history reference for node: " + node.getName()); } return false; } if (HistoryReference.TYPE_SCANNER == hRef.getHistoryType()) { if (log.isDebugEnabled()) { log.debug("Ignoring \"scanner\" type href [id=" + hRef.getHistoryId() + ", URL=" + hRef.getURI() + "]"); } return false; } if (!nodeInScope(node.getName())) { if (log.isDebugEnabled()) { log.debug("Ignoring node not in scope: " + node.getName()); } return false; } return true; } /** * ZAP: method to get back the number of tests that need to be performed * @return the number of tests that need to be executed for this Scanner */ public int getTestTotalCount() { return nodeInScopeCount; } /** * ZAP: method to get back the current progress status of a specific plugin * @param plugin the plugin we're asking the progress * @return the current managed test count */ public int getTestCurrentCount(Plugin plugin) { PluginStats pluginStats = mapPluginStats.get(plugin.getId()); if (pluginStats == null) { return 0; } return pluginStats.getProgress(); } /** * @deprecated (2.5.0) No longer used/needed, Plugin's progress is automatically updated/maintained by * {@code HostProcess}. * @param plugin unused * @param value unused */ @Deprecated public void setTestCurrentCount(Plugin plugin, int value) { // No longer used. } /** * @return Returns the httpSender. */ public HttpSender getHttpSender() { return httpSender; } /** * Check if the current host scan has been stopped * @return true if the process has been stopped */ public boolean isStop() { if (this.scannerParam.getMaxScanDurationInMins() > 0) { if (System.currentTimeMillis() - this.hostProcessStartTime > TimeUnit.MINUTES.toMillis(this.scannerParam.getMaxScanDurationInMins())) { this.stopReason = Constant.messages.getString("ascan.progress.label.skipped.reason.maxScan"); this.stop(); } } return (isStop || parentScanner.isStop()); } /** * Check if the current host scan has been paused * @return true if the process has been paused */ public boolean isPaused() { return parentScanner.isPaused(); } public int getPercentageComplete () { return this.percentage; } private void notifyHostProgress(String msg) { if (pluginFactory.totalPluginToRun() == 0) { percentage = 100; } else { int numberRunning = 0; double progressRunning = 0; for (Plugin plugin : pluginFactory.getRunning()) { int scannedNodes = getTestCurrentCount(plugin); double pluginPercentage = (scannedNodes * 100.0) / getTestTotalCount(); if (pluginPercentage >= 100) { // More nodes are being scanned that the ones enumerated at the beginning... // Update global count and... nodeInScopeCount = scannedNodes; // make sure not return 100 (or more). pluginPercentage = 99; } progressRunning += pluginPercentage; numberRunning++; } int avgRunning = (int) (progressRunning / numberRunning); percentage = ((100 * pluginFactory.totalPluginCompleted()) + avgRunning) / pluginFactory.totalPluginToRun(); } parentScanner.notifyHostProgress(hostAndPort, msg, percentage); } private void notifyHostComplete() { long diffTimeMillis = System.currentTimeMillis() - hostProcessStartTime; String diffTimeString = decimalFormat.format(diffTimeMillis / 1000.0) + "s"; log.info("completed host " + hostAndPort + " in " + diffTimeString); parentScanner.notifyHostComplete(hostAndPort); } /** * Notifies interested parties that a new message was sent (and received). * <p> * {@link Plugin Plugins} should call {@link #notifyNewMessage(Plugin)} or {@link #notifyNewMessage(Plugin, HttpMessage)}, * instead. * * @param msg the new HTTP message * @since 1.2.0 */ public void notifyNewMessage(HttpMessage msg) { parentScanner.notifyNewMessage(msg); } /** * Notifies that the given {@code plugin} sent (and received) the given HTTP message. * * @param plugin the plugin that sent the message * @param message the message sent * @throws IllegalArgumentException if the given {@code plugin} is {@code null}. * @since 2.5.0 * @see #notifyNewMessage(Plugin) */ public void notifyNewMessage(Plugin plugin, HttpMessage message) { parentScanner.notifyNewMessage(message); notifyNewMessage(plugin); } /** * Notifies that the given {@code plugin} sent (and received) a non-HTTP message. * <p> * The call to this method has no effect if there's no {@code Plugin} with the given ID (or, it was not yet started). * * @param plugin the plugin that sent a non-HTTP message * @throws IllegalArgumentException if the given parameter is {@code null}. * @since 2.5.0 * @see #notifyNewMessage(Plugin, HttpMessage) */ public void notifyNewMessage(Plugin plugin) { if (plugin == null) { throw new IllegalArgumentException("Parameter plugin must not be null."); } PluginStats pluginStats = mapPluginStats.get(plugin.getId()); if (pluginStats != null) { pluginStats.incMessageCount(); } } public void alertFound(Alert alert) { parentScanner.notifyAlertFound(alert); } /** * Give back the current process's Analyzer * @return the HTTP analyzer */ public Analyser getAnalyser() { if (analyser == null) { analyser = new Analyser(getHttpSender(), this); } return analyser; } public boolean handleAntiCsrfTokens() { return this.scannerParam.getHandleAntiCSRFTokens(); } /** * Skips the given plugin. * <p> * <strong>Note:</strong> Whenever possible callers should use {@link #pluginSkipped(Plugin, String)} instead. * * @param plugin the plugin that will be skipped, must not be {@code null} * @since 2.4.0 */ public void pluginSkipped(Plugin plugin) { pluginSkipped(plugin, null); } /** * Skips the given {@code plugin} with the given {@code reason}. * <p> * Ideally the {@code reason} should be internationalised as it is shown in the GUI. * * @param plugin the plugin that will be skipped, must not be {@code null} * @param reason the reason why the plugin was skipped, might be {@code null} * @since 2.6.0 */ public void pluginSkipped(Plugin plugin, String reason) { PluginStats pluginStats = mapPluginStats.get(plugin.getId()); if (pluginStats == null) { return; } pluginStats.skipped(); pluginStats.setSkippedReason(reason); } /** * Tells whether or not the given {@code plugin} was skipped (either programmatically or by the user). * * @param plugin the plugin that will be checked * @return {@code true} if plugin was skipped, {@code false} otherwise * @since 2.4.0 * @see #getSkippedReason(Plugin) */ public boolean isSkipped(Plugin plugin) { PluginStats pluginStats = mapPluginStats.get(plugin.getId()); if (pluginStats != null && pluginStats.isSkipped()) { return true; } if (plugin.getTimeFinished() == null && stopReason != null) { this.pluginSkipped(plugin, stopReason); return true; } else if (this.scannerParam.getMaxRuleDurationInMins() > 0 && plugin.getTimeStarted() != null) { long endtime = System.currentTimeMillis(); if (plugin.getTimeFinished() != null) { endtime = plugin.getTimeFinished().getTime(); } if (endtime - plugin.getTimeStarted().getTime() > TimeUnit.MINUTES.toMillis(this.scannerParam.getMaxRuleDurationInMins())) { this.pluginSkipped(plugin, Constant.messages.getString("ascan.progress.label.skipped.reason.maxRule")); return true; } } return false; } /** * Gets the reason why the given plugin was skipped. * * @param plugin the plugin that will be checked * @return the reason why the given plugin was skipped, might be {@code null} if not skipped or there's no reason * @since 2.6.0 * @see #isSkipped(Plugin) */ public String getSkippedReason(Plugin plugin) { PluginStats pluginStats = mapPluginStats.get(plugin.getId()); if (pluginStats == null) { return stopReason; } return pluginStats.getSkippedReason(); } /** * Complete the current plugin and update statistics * @param plugin the plugin that need to be marked as completed */ void pluginCompleted(Plugin plugin) { PluginStats pluginStats = mapPluginStats.get(plugin.getId()); if (pluginStats == null) { // Plugin was not processed return; } StringBuilder sb = new StringBuilder(); if (isStop()) { sb.append("stopped host/plugin "); // ZAP: added skipping notifications } else if (pluginStats.isSkipped()) { sb.append("skipped plugin "); String reason = pluginStats.getSkippedReason(); if (reason != null) { sb.append('[').append(reason).append("] "); } } else { sb.append("completed host/plugin "); } sb.append(hostAndPort).append(" | ").append(plugin.getCodeName()); long startTimeMillis = pluginStats.getStartTime(); long diffTimeMillis = System.currentTimeMillis() - startTimeMillis; String diffTimeString = decimalFormat.format(diffTimeMillis / 1000.0); sb.append(" in ").append(diffTimeString).append('s'); sb.append(" with ").append(pluginStats.getMessageCount()).append(" message(s) sent"); // Probably too verbose evaluate 4 the future log.info(sb.toString()); pluginFactory.setRunningPluginCompleted(plugin); notifyHostProgress(null); // ZAP: update progress as finished pluginStats.setProgress(nodeInScopeCount); } /** * Gets the knowledge base of the current scan. * * @return the knowledge base of the current scan, never {@code null}. */ Kb getKb() { if (kb == null) { kb = new Kb(); } return kb; } protected ScannerParam getScannerParam() { return scannerParam; } public List<Plugin> getPending() { return this.pluginFactory.getPending(); } public List<Plugin> getRunning() { return this.pluginFactory.getRunning(); } public List<Plugin> getCompleted() { return this.pluginFactory.getCompleted(); } /** * Set the user to scan as. If null then the current session will be used. * @param user the user to scan as */ public void setUser(User user) { this.user = user; if (httpSender != null) { httpSender.setUser(user); } } /** * Gets the technologies to be used in the scan. * * @return the technologies, never {@code null} (since 2.6.0) * @since 2.4.0 */ public TechSet getTechSet() { return techSet; } /** * Sets the technologies to be used in the scan. * * @param techSet the technologies to be used during the scan * @since 2.4.0 * @throws IllegalArgumentException (since 2.6.0) if the given parameter is {@code null}. */ public void setTechSet(TechSet techSet) { if (techSet == null) { throw new IllegalArgumentException("Parameter techSet must not be null."); } this.techSet = techSet; } /** * ZAP: abstract plugin will call this method in order to invoke any extensions that have hooked into the active scanner * @param msg the message being scanned * @param plugin the plugin being run */ protected synchronized void performScannerHookBeforeScan(HttpMessage msg, AbstractPlugin plugin) { Iterator<ScannerHook> iter = this.parentScanner.getScannerHooks().iterator(); while(iter.hasNext()){ ScannerHook hook = iter.next(); if(hook != null) { try { hook.beforeScan(msg, plugin, this.parentScanner); } catch (Exception e) { log.info("An exception occurred while trying to call beforeScan(msg, plugin) for one of the ScannerHooks: " + e.getMessage(), e); } } } } /** * ZAP: abstract plugin will call this method in order to invoke any extensions that have hooked into the active scanner * @param msg the message being scanned * @param plugin the plugin being run */ protected synchronized void performScannerHookAfterScan(HttpMessage msg, AbstractPlugin plugin) { Iterator<ScannerHook> iter = this.parentScanner.getScannerHooks().iterator(); while(iter.hasNext()){ ScannerHook hook = iter.next(); if(hook != null) { try { hook.afterScan(msg, plugin, this.parentScanner); } catch (Exception e) { log.info("An exception occurred while trying to call afterScan(msg, plugin) for one of the ScannerHooks: " + e.getMessage(), e); } } } } public String getHostAndPort() { return this.hostAndPort; } /** * @deprecated (2.5.0) No longer used/needed, Plugin's request count is automatically updated/maintained by * {@code HostProcess}. * @param pluginId the ID of the plugin * @param reqCount the number of requests sent */ @Deprecated public void setPluginRequestCount(int pluginId, int reqCount) { // No longer used. } /** * Gets the request count of the plugin with the give ID. * * @param pluginId the ID of the plugin * @return the request count * @since 2.4.3 * @see #getRequestCount() */ public int getPluginRequestCount(int pluginId) { PluginStats pluginStats = mapPluginStats.get(pluginId); if (pluginStats != null) { return pluginStats.getMessageCount(); } return 0; } /** * Gets the count of requests sent (and received) by all {@code Plugin}s and the {@code Analyser}. * * @return the count of request sent * @since 2.5.0 * @see #getPluginRequestCount(int) * @see #getAnalyser() */ public int getRequestCount() { synchronized (mapPluginStats) { int count = requestCount + getAnalyser().getRequestCount(); for (PluginStats stats : mapPluginStats.values()) { count += stats.getMessageCount(); } return count; } } /** * An action to be executed for each node traversed during the scan. * * @see #apply(StructuralNode) */ private interface TraverseAction { /** * Applies an action to the node traversed. * * @param node the node being traversed */ void apply(StructuralNode node); /** * Called after traversing a node, to know if the traversing should be stopped. * * @return {@code true} if the traversing should be stopped, {@code false} otherwise */ boolean isStopTraversing(); } /** * A {@code TraverseAction} that counts the nodes traversed and that can be scanned. * * @see #getCount() * @see HostProcess#canScanNode(StructuralNode) */ private class TraverseCounter implements TraverseAction { private int count; /** * Returns the number of nodes traversed and that can be scanned. * * @return the number of nodes traversed and that can be scanned. */ public int getCount() { return count; } @Override public void apply(StructuralNode node) { if (canScanNode(node)) { count++; } } @Override public boolean isStopTraversing() { return false; } } /** * The stats (and skip state and reason) of a {@link Plugin}, when the {@code Plugin} was started, how many messages were * sent and its scan progress. */ private static class PluginStats { private final long startTime; private int messageCount; private int progress; private boolean skipped; private String skippedReason; /** * Constructs a {@code PluginStats}, initialising the starting time of the plugin. */ public PluginStats() { startTime = System.currentTimeMillis(); } /** * Tells whether or not the plugin was skipped. * * @return {@code true} if the plugin was skipped, {@code false} otherwise * @see #skipped() */ public boolean isSkipped() { return skipped; } /** * Skips the plugin. * * @see #isSkipped() * @see #setSkippedReason(String) */ public void skipped() { this.skipped = true; } /** * Gets the reason why the plugin was skipped. * * @param reason the reason why the plugin was skipped, might be {@code null} * @see #getSkippedReason() * @see #isSkipped() */ public void setSkippedReason(String reason) { this.skippedReason = reason; } /** * Gets the reason why the plugin was skipped. * * @return the reason why the plugin was skipped, might be {@code null} * @see #setSkippedReason(String) * @see #isSkipped() */ public String getSkippedReason() { return skippedReason; } /** * Gets the time when the plugin was started, in milliseconds. * * @return time when the plugin was started * @see System#currentTimeMillis() */ public long getStartTime() { return startTime; } /** * Gets the count of messages sent by the plugin. * * @return the count of messages sent */ public int getMessageCount() { return messageCount; } /** * Increments the count of messages sent by the plugin. * <p> * Should be called when the plugin notifies that a message was sent. */ public void incMessageCount() { messageCount++; } /** * Gets the scan progress of the plugin. * * @return the scan progress */ public int getProgress() { return progress; } /** * Increments the scan progress of the plugin. * <p> * Should be called after scanning a message. */ public void incProgress() { this.progress++; } /** * Sets the scan progress of the plugin. * * @param progress the progress to set */ public void setProgress(int progress) { this.progress = progress; } } }