/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 2010 psiinon@gmail.com * * 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.awt.Dimension; import java.awt.Event; import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.security.InvalidParameterException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JOptionPane; import javax.swing.KeyStroke; import org.apache.commons.configuration.ConfigurationException; 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.ScannerParam; import org.parosproxy.paros.extension.CommandLineArgument; import org.parosproxy.paros.extension.CommandLineListener; import org.parosproxy.paros.extension.ExtensionAdaptor; import org.parosproxy.paros.extension.ExtensionHook; import org.parosproxy.paros.extension.SessionChangedListener; import org.parosproxy.paros.extension.history.ProxyListenerLog; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.view.AbstractParamPanel; import org.parosproxy.paros.view.View; import org.zaproxy.zap.ZAP; import org.zaproxy.zap.extension.alert.ExtensionAlert; import org.zaproxy.zap.extension.help.ExtensionHelp; import org.zaproxy.zap.extension.script.ExtensionScript; import org.zaproxy.zap.extension.script.ScriptType; import org.zaproxy.zap.model.ScanController; import org.zaproxy.zap.model.StructuralNode; import org.zaproxy.zap.model.StructuralSiteNode; import org.zaproxy.zap.model.Target; import org.zaproxy.zap.users.User; import org.zaproxy.zap.view.ZapMenuItem; public class ExtensionActiveScan extends ExtensionAdaptor implements SessionChangedListener, CommandLineListener, ScanController<ActiveScan> { private static final Logger logger = Logger.getLogger(ExtensionActiveScan.class); private static final int ARG_SCAN_IDX = 0; public static final String NAME = "ExtensionActiveScan"; private static final ImageIcon SCRIPT_ICON_ACTIVE = new ImageIcon(ZAP.class.getResource("/resource/icon/16/script-ascan.png")); private static final ImageIcon SCRIPT_ICON_VARIANT = new ImageIcon(ZAP.class.getResource("/resource/icon/16/script-variant.png")); public static final String SCRIPT_TYPE_ACTIVE = "active"; public static final String SCRIPT_TYPE_VARIANT = "variant"; //Could 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; private static final List<Class<?>> DEPENDENCIES; private AttackModeScanner attackModeScanner; private ActiveScanController ascanController = null; static { List<Class<?>> dep = new ArrayList<>(1); dep.add(ExtensionAlert.class); DEPENDENCIES = Collections.unmodifiableList(dep); } private ZapMenuItem menuItemPolicy = null; private ZapMenuItem menuItemCustomScan = null; private OptionsScannerPanel optionsScannerPanel = null; private OptionsVariantPanel optionsVariantPanel = null; private ActiveScanPanel activeScanPanel = null; private ScannerParam scannerParam = null; private final CommandLineArgument[] arguments = new CommandLineArgument[1]; private final List<AbstractParamPanel> policyPanels = new ArrayList<>(); private JButton policyButton = null; private CustomScanDialog customScanDialog = null; private PolicyManagerDialog policyManagerDialog = null; private PolicyManager policyManager = null; private List<CustomScanPanel> customScanPanels = new ArrayList<CustomScanPanel>(); private List<String> excludeList = Collections.emptyList(); private ActiveScanAPI activeScanApi; public ExtensionActiveScan() { super(NAME); this.setOrder(28); policyManager = new PolicyManager(this); ascanController = new ActiveScanController(this); } @Override public void postInit() { policyManager.init(); if (Control.getSingleton().getMode().equals(Mode.attack)) { if (View.isInitialised() && ! this.getScannerParam().isAllowAttackOnStart()) { // Disable attack mode for safeties sake (when running with the UI) View.getSingleton().getMainFrame().getMainToolbarPanel().setMode(Mode.standard); } else { // Needed to make sure the attackModeScanner starts up this.attackModeScanner.sessionModeChanged(Control.getSingleton().getMode()); } } } @Override public void hook(ExtensionHook extensionHook) { super.hook(extensionHook); attackModeScanner = new AttackModeScanner(this); if (getView() != null) { extensionHook.getHookMenu().addAnalyseMenuItem(getMenuItemPolicy()); extensionHook.getHookMenu().addToolsMenuItem(getMenuItemCustomScan()); extensionHook.getHookView().addStatusPanel(getActiveScanPanel()); extensionHook.getHookView().addOptionPanel(getOptionsScannerPanel()); extensionHook.getHookView().addOptionPanel(getOptionsVariantPanel()); View.getSingleton().addMainToolbarButton(this.getPolicyButton()); View.getSingleton().getMainFrame().getMainFooterPanel().addFooterToolbarRightLabel( attackModeScanner.getScanStatus().getCountLabel()); ExtensionHelp.enableHelpKey(getActiveScanPanel(), "ui.tabs.ascan"); } extensionHook.addSessionListener(this); extensionHook.addOptionsParamSet(getScannerParam()); // TODO this isnt currently implemented //extensionHook.addCommandLine(getCommandLineArguments()); ExtensionScript extScript = (ExtensionScript) Control.getSingleton().getExtensionLoader().getExtension(ExtensionScript.NAME); if (extScript != null) { extScript.registerScriptType(new ScriptType(SCRIPT_TYPE_ACTIVE, "ascan.scripts.type.active", SCRIPT_ICON_ACTIVE, true)); extScript.registerScriptType(new ScriptType(SCRIPT_TYPE_VARIANT, "variant.scripts.type.variant", SCRIPT_ICON_VARIANT, true)); } this.ascanController.setExtAlert((ExtensionAlert) Control.getSingleton().getExtensionLoader().getExtension(ExtensionAlert.NAME)); this.activeScanApi = new ActiveScanAPI(this); this.activeScanApi.addApiOptions(getScannerParam()); extensionHook.addApiImplementor(activeScanApi); } @Override public List<String> getActiveActions() { List<ActiveScan> activeScans = ascanController.getActiveScans(); if (activeScans.isEmpty()) { return null; } String activeActionPrefix = Constant.messages.getString("ascan.activeActionPrefix"); List<String> activeActions = new ArrayList<>(activeScans.size()); for (ActiveScan activeScan : activeScans) { if (activeScan instanceof AttackScan && ((AttackScan) activeScan).isDone()) { continue; } activeActions.add(MessageFormat.format(activeActionPrefix, activeScan.getDisplayName())); } return activeActions; } private ActiveScanPanel getActiveScanPanel() { if (activeScanPanel == null) { activeScanPanel = new ActiveScanPanel(this); } return activeScanPanel; } public void startScanAllInScope() { SiteNode snroot = (SiteNode) Model.getSingleton().getSession().getSiteTree().getRoot(); this.startScan(new Target(snroot, null, true, true)); } /** * Start the scanning process beginning to a specific node * @param startNode the start node where the scanning should begin to work * @return the ID of the scan */ public int startScan(SiteNode startNode) { return this.startScan(new Target(startNode, true)); } public int startScanNode(SiteNode startNode) { return this.startScan(new Target(startNode, false)); } public int startScan(Target target) { return this.startScan(target, null, null); } public int startScan(Target target, User user, Object[] contextSpecificObjects) { return this.startScan(target.getDisplayName(), target, user, contextSpecificObjects); } @Override public int startScan(String name, Target target, User user, Object[] contextSpecificObjects) { if (name == null) { name = target.getDisplayName(); } switch (Control.getSingleton().getMode()) { case safe: throw new InvalidParameterException("Scans are not allowed in Safe mode"); case protect: List<StructuralNode> nodes = target.getStartNodes(); if (nodes != null) { for (StructuralNode node : nodes) { if (node instanceof StructuralSiteNode) { SiteNode siteNode = ((StructuralSiteNode) node).getSiteNode(); if (!siteNode.isIncludedInScope()) { throw new InvalidParameterException("Scans are not allowed on nodes not in scope Protected mode " + target.getStartNode().getHierarchicNodeName()); } } } } // No problem break; case standard: // No problem break; case attack: // No problem break; } int id = this.ascanController.startScan(name, target, user, contextSpecificObjects); if (View.isInitialised()) { ActiveScan scanner = this.ascanController.getScan(id); scanner.addScannerListener(getActiveScanPanel()); // So the UI get updated this.getActiveScanPanel().scannerStarted(scanner); this.getActiveScanPanel().switchView(scanner); this.getActiveScanPanel().setTabFocus(); } return id; } private JButton getPolicyButton() { if (policyButton == null) { policyButton = new JButton(); policyButton.setIcon(new ImageIcon(ActiveScanPanel.class.getResource("/resource/icon/fugue/equalizer.png"))); policyButton.setToolTipText(Constant.messages.getString("menu.analyse.scanPolicy")); policyButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { showPolicyManagerDialog(); } }); } return policyButton; } /** * This method initializes menuItemPolicy * * @return javax.swing.JMenuItem */ private ZapMenuItem getMenuItemPolicy() { if (menuItemPolicy == null) { menuItemPolicy = new ZapMenuItem("menu.analyse.scanPolicy", KeyStroke.getKeyStroke(KeyEvent.VK_P, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false)); menuItemPolicy.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { showPolicyManagerDialog(); } }); } return menuItemPolicy; } protected void showPolicyDialog(PolicyManagerDialog parent) throws ConfigurationException { this.showPolicyDialog(parent, null); } protected void showPolicyDialog(PolicyManagerDialog parent, String name) throws ConfigurationException { ScanPolicy policy; if (name != null) { policy = this.getPolicyManager().getPolicy(name); } else { policy = this.getPolicyManager().getTemplatePolicy(); } PolicyDialog dialog = new PolicyDialog(this, parent, policy); dialog.initParam(getModel().getOptionsParam()); for (AbstractParamPanel panel : policyPanels) { dialog.addPolicyPanel(panel); } int result = dialog.showDialog(true); if (result == JOptionPane.OK_OPTION) { try { getModel().getOptionsParam().getConfig().save(); } catch (ConfigurationException ce) { logger.error(ce.getMessage(), ce); getView().showWarningDialog(Constant.messages.getString("scanner.save.warning")); } } } private ZapMenuItem getMenuItemCustomScan() { if (menuItemCustomScan == null) { menuItemCustomScan = new ZapMenuItem("menu.tools.ascanadv", KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | Event.ALT_MASK, false)); menuItemCustomScan.setEnabled(Control.getSingleton().getMode() != Mode.safe); menuItemCustomScan.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { showCustomScanDialog(null); } }); } return menuItemCustomScan; } @Override public void sessionChanged(final Session session) { if (EventQueue.isDispatchThread()) { sessionChangedEventHandler(session); } else { try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { sessionChangedEventHandler(session); } }); } catch (InterruptedException | InvocationTargetException e) { logger.error(e.getMessage(), e); } } } private void sessionChangedEventHandler(Session session) { // The scans are stopped in sessionAboutToChange(..) if (View.isInitialised()) { this.getActiveScanPanel().reset(); } this.attackModeScanner.stop(); if (session == null) { // Closedown return; } if (Control.getSingleton().getMode().equals(Mode.attack)) { // Start the attack mode scanner up again, have to rescan on change or it wont do anything this.attackModeScanner.start(); this.attackModeScanner.setRescanOnChange(true); } } /** * This method initializes optionsScannerPanel * * @return org.parosproxy.paros.extension.scanner.OptionsScannerPanel */ private OptionsScannerPanel getOptionsScannerPanel() { if (optionsScannerPanel == null) { optionsScannerPanel = new OptionsScannerPanel(this); } return optionsScannerPanel; } /** * This method initializes optionsVariantPanel * * @return org.zaproxy.zap.extension.ascan.OptionsVariantPanel */ private OptionsVariantPanel getOptionsVariantPanel() { if (optionsVariantPanel == null) { optionsVariantPanel = new OptionsVariantPanel(); } return optionsVariantPanel; } /** * This method initializes scannerParam * * @return org.parosproxy.paros.core.scanner.ScannerParam */ protected ScannerParam getScannerParam() { if (scannerParam == null) { scannerParam = new ScannerParam(); } return scannerParam; } // TODO @Override public void execute(CommandLineArgument[] args) { /* if (arguments[ARG_SCAN_IDX].isEnabled()) { System.out.println("Scanner started..."); startScan(); } else { return; } while (!getScanner().isStop()) { try { Thread.sleep(1000); } catch (InterruptedException e) { } } System.out.println("Scanner completed."); */ } @SuppressWarnings("unused") private CommandLineArgument[] getCommandLineArguments() { arguments[ARG_SCAN_IDX] = new CommandLineArgument("-scan", 0, null, "", "-scan : Run vulnerability scan depending on previously saved policy."); return arguments; } /** * Sets the exclude list. * * @param urls the new exclude list */ public void setExcludeList(List<String> urls) { if (urls == null || urls.isEmpty()) { excludeList = Collections.emptyList(); return; } this.excludeList = urls; } /** * Gets the exclude list. * * @return the exclude list */ public List<String> getExcludeList() { return excludeList; } public void addPolicyPanel(AbstractParamPanel panel) { this.policyPanels.add(panel); } @Override public List<Class<?>> getDependencies() { return DEPENDENCIES; } @Override public void sessionAboutToChange(final Session session) { // Stop, remove all scans and reset the scan id counter this.ascanController.reset(); this.attackModeScanner.stop(); if (View.isInitialised()) { this.getActiveScanPanel().reset(); if (customScanDialog != null) { customScanDialog.reset(); } } } @Override public String getAuthor() { return Constant.ZAP_TEAM; } @Override public String getDescription() { return Constant.messages.getString("ascan.desc"); } @Override public URL getURL() { try { return new URL(Constant.ZAP_HOMEPAGE); } catch (MalformedURLException e) { return null; } } @Override public void sessionScopeChanged(Session session) { if (View.isInitialised()) { this.getActiveScanPanel().sessionScopeChanged(session); } this.attackModeScanner.sessionScopeChanged(session); } @Override public void sessionModeChanged(Mode mode) { if (Mode.safe.equals(mode)) { this.ascanController.stopAllScans(); } if (View.isInitialised()) { getMenuItemCustomScan().setEnabled( ! Mode.safe.equals(mode)); this.getActiveScanPanel().sessionModeChanged(mode); } this.attackModeScanner.sessionModeChanged(mode); } @Override public void destroy() { this.ascanController.stopAllScans(); if (View.isInitialised()) { this.getActiveScanPanel().reset(); } } public void showCustomScanDialog(SiteNode node) { if (customScanDialog == null) { // Work out the tabs String[] tabs = CustomScanDialog.STD_TAB_LABELS; if (this.customScanPanels.size() > 0) { List<String> tabList = new ArrayList<String>(); for (String str : CustomScanDialog.STD_TAB_LABELS) { tabList.add(str); } for (CustomScanPanel csp : customScanPanels) { tabList.add(csp.getLabel()); } tabs = tabList.toArray(new String[tabList.size()]); } customScanDialog = new CustomScanDialog(this, tabs, this.customScanPanels, View.getSingleton().getMainFrame(), new Dimension(700, 500)); } if (customScanDialog.isVisible()) { customScanDialog.requestFocus(); // Its behind you! Actually not needed no the window is alwaysOnTop, but keeping in case we change that ;) customScanDialog.toFront(); return; } if (node != null) { customScanDialog.init(new Target(node)); } else { // Keep the previously selected target customScanDialog.init(null); } customScanDialog.setVisible(true); } public void addCustomScanPanel (CustomScanPanel panel) { this.customScanPanels.add(panel); customScanDialog = null; // Force it to be reinitialised } public void removeCustomScanPanel (CustomScanPanel panel) { this.customScanPanels.remove(panel); customScanDialog = null; // Force it to be reinitialised } public void showPolicyManagerDialog() { if (policyManagerDialog == null) { policyManagerDialog = new PolicyManagerDialog(View.getSingleton().getMainFrame()); policyManagerDialog.init(this); } // The policy names _may_ have changed, eg via the api policyManagerDialog.policyNamesChanged(); policyManagerDialog.setVisible(true); } @Override public boolean handleFile(File file) { // Cant handle any files return false; } @Override public List<String> getHandledExtensions() { // Cant handle any extensions return null; } @Override public List<ActiveScan> getAllScans() { return ascanController.getAllScans(); } @Override public List<ActiveScan> getActiveScans() { return ascanController.getActiveScans(); } @Override public ActiveScan getScan(int id) { return ascanController.getScan(id); } @Override public void stopScan(int id) { ascanController.stopScan(id); // Dont need to update the UI - this will happen automatically via the events } @Override public void pauseScan(int id) { ascanController.pauseScan(id); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getActiveScanPanel().updateScannerUI(); } } @Override public void resumeScan(int id) { ascanController.resumeScan(id); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getActiveScanPanel().updateScannerUI(); } } @Override public void stopAllScans() { ascanController.stopAllScans(); // Dont need to update the UI - this will happen automatically via the events } @Override public void pauseAllScans() { ascanController.pauseAllScans(); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getActiveScanPanel().updateScannerUI(); } } @Override public void resumeAllScans() { ascanController.removeAllScans(); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getActiveScanPanel().updateScannerUI(); } } @Override public ActiveScan removeScan(int id) { return ascanController.removeScan(id); } @Override public int removeAllScans() { return ascanController.removeAllScans(); } @Override public int removeFinishedScans() { return ascanController.removeFinishedScans(); } @Override public ActiveScan getLastScan() { return ascanController.getLastScan(); } public int registerScan(ActiveScan scanner) { int id = ascanController.registerScan(scanner); if (View.isInitialised()) { // Update the UI in case this was initiated from the API scanner.addScannerListener(getActiveScanPanel()); // So the UI get updated this.getActiveScanPanel().scannerStarted(scanner); this.getActiveScanPanel().switchView(scanner); this.getActiveScanPanel().setTabFocus(); } return id; } public PolicyManager getPolicyManager() { return policyManager; } public int getAttackModeStackSize() { return this.attackModeScanner.getStackSize(); } @Override public boolean supportsLowMemory() { return true; } /** * Part of the core set of features that should be supported by all db types */ @Override public boolean supportsDb(String type) { return true; } }