/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 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.ascan; import java.util.ArrayList; import java.util.List; import javax.swing.ImageIcon; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.apache.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.scanner.Alert; import org.parosproxy.paros.core.scanner.HostProcess; import org.parosproxy.paros.core.scanner.Scanner; import org.parosproxy.paros.core.scanner.ScannerListener; import org.parosproxy.paros.model.HistoryReference; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SiteMapEventPublisher; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.view.View; import org.zaproxy.zap.ZAP; import org.zaproxy.zap.eventBus.Event; import org.zaproxy.zap.eventBus.EventConsumer; import org.zaproxy.zap.extension.alert.ExtensionAlert; import org.zaproxy.zap.extension.log4j.ExtensionLog4j; import org.zaproxy.zap.extension.ruleconfig.ExtensionRuleConfig; import org.zaproxy.zap.extension.ruleconfig.RuleConfigParam; import org.zaproxy.zap.view.ScanStatus; public class AttackModeScanner implements EventConsumer { private static final String ATTACK_ICON_RESOURCE = "/resource/icon/16/093.png"; private ExtensionActiveScan extension; private long lastUpdated; private ScanStatus scanStatus; private ExtensionAlert extAlert = null; private AttackModeThread attackModeThread = null; private boolean rescanOnChange = false; private Logger log = Logger.getLogger(AttackModeScanner.class); private List<SiteNode> nodeStack = new ArrayList<SiteNode>(); public AttackModeScanner(ExtensionActiveScan extension) { this.extension = extension; ZAP.getEventBus().registerConsumer(this, SiteMapEventPublisher.class.getCanonicalName()); if (extension.getView() != null) { lastUpdated = System.currentTimeMillis(); scanStatus = new ScanStatus( new ImageIcon( ExtensionLog4j.class.getResource("/resource/icon/fugue/target.png")), Constant.messages.getString("ascan.attack.icon.title")); } } public void start() { log.debug("Starting"); nodeStack.clear(); this.addAllInScope(); if (attackModeThread != null) { attackModeThread.shutdown(); } attackModeThread = new AttackModeThread(); Thread t = new Thread(attackModeThread, "ZAP-AttackMode"); t.setDaemon(true); t.start(); } private void addAllInScope() { if (this.rescanOnChange) { this.nodeStack.addAll(Model.getSingleton().getSession().getNodesInScopeFromSiteTree()); log.debug("Added existing in scope nodes to attack mode stack " + this.nodeStack.size()); updateCount(); } } public void stop() { log.debug("Stopping"); if (this.attackModeThread != null) { this.attackModeThread.shutdown(); } nodeStack.clear(); updateCount(); } @Override public void eventReceived(Event event) { if (this.attackModeThread != null && this.attackModeThread.isRunning()) { if (event.getEventType().equals(SiteMapEventPublisher.SITE_NODE_ADDED_EVENT) && event.getTarget().getStartNode().isIncludedInScope()) { if (event.getTarget().getStartNode().getHistoryReference().getHistoryType() != HistoryReference.TYPE_TEMPORARY) { // Add to the stack awaiting attack log.debug("Adding node to attack mode stack " + event.getTarget().getStartNode()); nodeStack.add(event.getTarget().getStartNode()); updateCount(); } } else if (event.getEventType().equals(SiteMapEventPublisher.SITE_NODE_REMOVED_EVENT)) { if (nodeStack.contains(event.getTarget().getStartNode())) { nodeStack.remove(event.getTarget().getStartNode()); } } } } /** * Gets the {@link ScanStatus}. * * @return the {@code ScanStatus}, or {@code null} if there's no view/UI. */ public ScanStatus getScanStatus() { return scanStatus; } public void sessionScopeChanged(Session session) { this.addAllInScope(); } public void sessionModeChanged(Mode mode) { if (mode.equals(Mode.attack)) { if (View.isInitialised() && extension.getScannerParam().isPromptInAttackMode()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int res = View.getSingleton().showYesNoRememberDialog( View.getSingleton().getMainFrame(), Constant.messages.getString("ascan.attack.prompt")); if (View.getSingleton().isRememberLastDialogChosen()) { extension.getScannerParam().setPromptInAttackMode(false); extension.getScannerParam().setRescanInAttackMode(res == JOptionPane.YES_OPTION); } rescanOnChange = (res == JOptionPane.YES_OPTION); start(); }}); } else { this.rescanOnChange = extension.getScannerParam().isRescanInAttackMode(); this.start(); } } else { this.stop(); } } /** * Updates the count of the {@link #scanStatus scan status}' label. * <p> * The call to this method has no effect if the view was not initialised. */ private void updateCount() { if (scanStatus == null) { return; } long now = System.currentTimeMillis(); if (now - this.lastUpdated > 200) { // Dont update too frequently, eg using the spider could hammer the UI unnecessarily this.lastUpdated = now; SwingUtilities.invokeLater(new Runnable(){ @Override public void run() { scanStatus.setScanCount(nodeStack.size()); }}); } } public int getStackSize() { int count = nodeStack.size(); if (count > 0) { // There are nodes to scan return count; } // Work out if any scanning is in progress if (this.attackModeThread != null && this.attackModeThread.isActive()) { return 0; } return -1; } public boolean isRescanOnChange() { return rescanOnChange; } public void setRescanOnChange(boolean rescanOnChange) { this.rescanOnChange = rescanOnChange; } private ExtensionAlert getExtensionAlert() { if (extAlert == null) { extAlert = (ExtensionAlert) Control.getSingleton().getExtensionLoader().getExtension(ExtensionAlert.NAME); } return extAlert; } private class AttackModeThread implements Runnable, ScannerListener, AttackModeScannerThread { private int scannerCount = 4; private List<Scanner> scanners = new ArrayList<Scanner>(); private AttackScan ascanWrapper; private boolean running = false; @Override public void run() { log.debug("Starting attack thread"); this.running = true; RuleConfigParam ruleConfigParam = null; ExtensionRuleConfig extRC = Control.getSingleton().getExtensionLoader().getExtension(ExtensionRuleConfig.class); if (extRC != null) { ruleConfigParam = extRC.getRuleConfigParam(); } ascanWrapper = new AttackScan(Constant.messages.getString("ascan.attack.scan"), extension.getScannerParam(), extension.getModel().getOptionsParam().getConnectionParam(), extension.getPolicyManager().getAttackScanPolicy(), ruleConfigParam, this); extension.registerScan(ascanWrapper); while (running) { if (scanStatus != null && scanStatus.getScanCount() != nodeStack.size()) { updateCount(); } if (nodeStack.size() == 0 || scanners.size() == scannerCount) { if (scanners.size() > 0) { // Check to see if any have finished scannerComplete(-1); } // Still scanning a node or nothing to scan now try { Thread.sleep(500); } catch (InterruptedException e) { // Ignore } continue; } while (nodeStack.size() > 0 && scanners.size() < scannerCount) { SiteNode node = nodeStack.remove(0); log.debug("Attacking node " + node.getNodeName()); Scanner scanner = new Scanner(extension.getScannerParam(), extension.getModel().getOptionsParam().getConnectionParam(), extension.getPolicyManager().getAttackScanPolicy(), ruleConfigParam); scanner.setStartNode(node); scanner.setScanChildren(false); scanner.addScannerListener(this); synchronized (this.scanners) { this.scanners.add(scanner); } if (View.isInitialised()) { // set icon to show its being scanned node.addCustomIcon(ATTACK_ICON_RESOURCE, false); } scanner.start(node); } } synchronized (this.scanners) { for (Scanner scanner : this.scanners) { scanner.stop(); } } log.debug("Attack thread finished"); } @Override public void scannerComplete(int id) { // Clear so we can attack the next node List<Scanner> stoppedScanners = new ArrayList<Scanner>(); synchronized (this.scanners) { for (Scanner scanner : this.scanners) { if (scanner.isStop()) { SiteNode node = scanner.getStartNode(); if (node != null) { log.debug("Finished attacking node " + node.getNodeName()); if (View.isInitialised()) { // Remove the icon node.removeCustomIcon(ATTACK_ICON_RESOURCE); } } stoppedScanners.add(scanner); } } for (Scanner scanner : stoppedScanners) { // Cant remove them in the above loop scanners.remove(scanner); } } updateCount(); } @Override public void hostNewScan(int id, String hostAndPort, HostProcess hostThread) { // Ignore } @Override public void hostProgress(int id, String hostAndPort, String msg, int percentage) { // Ignore } @Override public void hostComplete(int id, String hostAndPort) { // Ignore } @Override public void alertFound(Alert alert) { alert.setSource(Alert.Source.ACTIVE); getExtensionAlert().alertFound(alert, alert.getHistoryRef()); } @Override public void notifyNewMessage(HttpMessage msg) { ascanWrapper.notifyNewMessage(msg); } public void shutdown() { this.running = false; } @Override public boolean isRunning() { return this.running; } /** * Tells whether or not any of the scan threads are currently active. * @return {@code true} if there's at least one scan active, {@code false} otherwise */ @Override public boolean isActive() { synchronized (this.scanners) { for (Scanner scanner : this.scanners) { if (! scanner.isStop()) { return true; } } } return false; } } interface AttackModeScannerThread { boolean isRunning(); boolean isActive(); } }