All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zaproxy.zap.extension.spider.ExtensionSpider Maven / Gradle / Ivy

Go to download

The Zed Attack Proxy (ZAP) is an easy to use integrated penetration testing tool for finding vulnerabilities in web applications. It is designed to be used by people with a wide range of security experience and as such is ideal for developers and functional testers who are new to penetration testing. ZAP provides automated scanners as well as a set of tools that allow you to find security vulnerabilities manually.

There is a newer version: 2.15.0
Show newest version
/*
 * Zed Attack Proxy (ZAP) and its related class files.
 * 
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 * 
 * 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.
 * 
 * Note that this extension and the other classes in this package are heavily 
 * based on the original Paros ExtensionSpider! 
 */

package org.zaproxy.zap.extension.spider;

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;

import org.apache.commons.httpclient.URI;
import org.apache.commons.lang.StringUtils;
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.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.SessionChangedListener;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.model.SiteNode;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.extension.help.ExtensionHelp;
import org.zaproxy.zap.model.Context;
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.model.ValueGenerator;
import org.zaproxy.zap.model.DefaultValueGenerator;
import org.zaproxy.zap.spider.SpiderParam;
import org.zaproxy.zap.spider.filters.FetchFilter;
import org.zaproxy.zap.spider.filters.ParseFilter;
import org.zaproxy.zap.spider.filters.HttpPrefixFetchFilter;
import org.zaproxy.zap.spider.parser.SpiderParser;
import org.zaproxy.zap.users.User;
import org.zaproxy.zap.view.ZapMenuItem;

/**
 * The ExtensionSpider is the Extension that controls the Spider.
 */
public class ExtensionSpider extends ExtensionAdaptor implements SessionChangedListener, ScanController {

	private ValueGenerator generator = new DefaultValueGenerator();

	public static final int EXTENSION_ORDER = 30;
	
	/** The Constant logger. */
	private static final Logger log = Logger.getLogger(ExtensionSpider.class);

	/** The Constant defining the NAME of the extension. */
	public static final String NAME = "ExtensionSpider";

	/** The spider panel. */
	private SpiderPanel spiderPanel = null;

	SpiderDialog spiderDialog = null;

	private PopupMenuItemSpiderDialog popupMenuItemSpiderDialog;

	/** The options spider panel. */
	private OptionsSpiderPanel optionsSpiderPanel = null;

	/** The params for the spider. */
	private SpiderParam params = null;
	
	private List customParsers;
	private List customFetchFilters;
	private List customParseFilters;

	private SpiderAPI spiderApi;
	
	private SpiderScanController scanController = null;

	private Icon icon;

	/**
	 * The list of excluded patterns of sites. Patterns are added here with the ExcludeFromSpider
	 * Popup Menu.
	 */
	private List excludeList = Collections.emptyList();

	private ZapMenuItem menuItemCustomScan = null;

	/**
	 * Instantiates a new spider extension.
	 */
	public ExtensionSpider() {
		super(NAME);
		initialize();
	}

	/**
	 * This method initializes this extension.
	 */
	private void initialize() {
		this.setOrder(EXTENSION_ORDER);
		this.customParsers = new LinkedList<>();
		this.customFetchFilters = new LinkedList<>();
		this.customParseFilters = new LinkedList<>();
		this.scanController = new SpiderScanController(this);
	}

	public void setValueGenerator (ValueGenerator generator) {
		if (generator == null){
			throw new IllegalArgumentException("Parameter generator must not be null.");
		}
		this.generator = generator;
	}

	public ValueGenerator getValueGenerator() {
		return generator;
	}

	@Override
	public String getUIName() {
		return Constant.messages.getString("spider.name");
	}
	
	@Override
	public void hook(ExtensionHook extensionHook) {
		super.hook(extensionHook);
		// Register for listeners
		extensionHook.addSessionListener(this);

		// Initialize views
		if (getView() != null) {
			extensionHook.getHookMenu().addToolsMenuItem(getMenuItemCustomScan());
			extensionHook.getHookView().addStatusPanel(getSpiderPanel());
			extensionHook.getHookView().addOptionPanel(getOptionsSpiderPanel());
			extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuItemSpiderDialog());
			ExtensionHelp.enableHelpKey(getSpiderPanel(), "ui.tabs.spider");
		}

		// Register the params
		extensionHook.addOptionsParamSet(getSpiderParam());

		// Register as an API implementor
		spiderApi = new SpiderAPI(this);
		spiderApi.addApiOptions(getSpiderParam());
		extensionHook.addApiImplementor(spiderApi);
	}

	private PopupMenuItemSpiderDialog getPopupMenuItemSpiderDialog() {
		if (popupMenuItemSpiderDialog == null) {
			popupMenuItemSpiderDialog = new PopupMenuItemSpiderDialog(this);
		}
		return popupMenuItemSpiderDialog;
	}

	@Override
	public List getActiveActions() {
		List activeSpiders = scanController.getActiveScans();
		if (activeSpiders.isEmpty()) {
			return null;
		}

		String spiderActionPrefix = Constant.messages.getString("spider.activeActionPrefix");
		List activeActions = new ArrayList<>(activeSpiders.size());
		for (SpiderScan activeSpider : activeSpiders) {
			activeActions.add(MessageFormat.format(spiderActionPrefix, activeSpider.getDisplayName()));
		}
		return activeActions;
	}

	/**
	 * Gets the spider parameters (options).
	 * 
	 * @return the spider parameters
	 */
	protected SpiderParam getSpiderParam() {
		if (params == null) {
			params = new SpiderParam();
		}
		return params;
	}

	/**
	 * Gets the spider panel.
	 * 
	 * @return the spider panel
	 */
	protected SpiderPanel getSpiderPanel() {
		if (spiderPanel == null) {
			spiderPanel = new SpiderPanel(this, getSpiderParam());
		}
		return spiderPanel;
	}
	
	@Override
	public void sessionAboutToChange(Session session) {
		// Shut all of the scans down and remove them
		this.scanController.reset();
		if (View.isInitialised()) {
			this.getSpiderPanel().reset();
			if (spiderDialog != null) {
				spiderDialog.reset();
			}
		}
	}

	@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 (Exception e) {
				log.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * Session changed event handler.
	 * 
	 * @param session the session
	 */
	private void sessionChangedEventHandler(Session session) {
		// Clear all scans
		if (View.isInitialised()) {
			this.getSpiderPanel().reset();
		}
		if (session == null) {
			// Closedown
			return;
		}
	}

	/**
	 * Gets the options spider panel.
	 * 
	 * @return the options spider panel
	 */
	private OptionsSpiderPanel getOptionsSpiderPanel() {
		if (optionsSpiderPanel == null) {
			optionsSpiderPanel = new OptionsSpiderPanel();
		}
		return optionsSpiderPanel;
	}

	/**
	 * Sets the exclude list.
	 * 
	 * @param ignoredRegexs the new exclude list
	 */
	public void setExcludeList(List ignoredRegexs) {
		if (ignoredRegexs == null || ignoredRegexs.isEmpty()) {
			excludeList = Collections.emptyList();
			return;
		}

		this.excludeList = ignoredRegexs;
	}

	/**
	 * Gets the exclude list.
	 * 
	 * @return the exclude list
	 */
	public List getExcludeList() {
		return excludeList;
	}


	@Override
	public String getAuthor() {
		return Constant.ZAP_TEAM;
	}

	@Override
	public String getDescription() {
		return Constant.messages.getString("spider.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.getSpiderPanel().sessionScopeChanged(session);
		}
	}

	@Override
	public void sessionModeChanged(Mode mode) {
		if (View.isInitialised()) {
			this.getSpiderPanel().sessionModeChanged(mode);
			getMenuItemCustomScan().setEnabled( ! Mode.safe.equals(mode));
		}
	}

	/**
	 * Start scan node.
	 * 
	 * @param node the node
	 */
	public void startScanNode(SiteNode node) {
		Target target = new Target(node);
		target.setRecurse(true);
		this.startScan(target, null, null);
	}
	
	/**
	 * Start the scan of an URL (Node) from the POV of a User.
	 * 
	 * @param node the node
	 */
	public void startScanNode(SiteNode node, User user) {
		Target target = new Target(node);
		target.setRecurse(true);
		this.startScan(target, user, null);
	}

	/**
	 * Start scan all in scope.
	 */
	public void startScanAllInScope() {
		Target target = new Target(true);
		target.setRecurse(true);
		this.startScan(target, null, null);
	}

	/**
	 * Start scan.
	 * 
	 * @param startNode the start node
	 */
	public void startScan(SiteNode startNode) {
		Target target = new Target(startNode);
		target.setRecurse(true);
		this.startScan(target, null, null);
	}

	/**
	 * Start scan all in context, from the POV of an User.
	 */
	public void startScanAllInContext(Context context, User user) {
		Target target = new Target(context);
		target.setRecurse(true);
		this.startScan(target, user, null);
	}
	
	
	@Override
    public void destroy() {
		// Shut all of the scans down
		this.stopAllScans();
		if (View.isInitialised()) {
			this.getSpiderPanel().reset();
		}
	}

	/**
	 * Gets the custom parsers loaded.
	 *
	 * @return the custom parsers
	 */
	public List getCustomParsers() {
		return customParsers;
	}
	
	/**
	 * Gets the custom fetch filters loaded.
	 *
	 * @return the custom fetch filters
	 */
	public List getCustomFetchFilters() {
		return customFetchFilters;
	}

	/**
	 * Gets the custom parse filters loaded.
	 *
	 * @return the custom parse filters
	 */
	public List getCustomParseFilters() {
		return customParseFilters;
	}

	/**
	 * Adds a new custom Spider Parser. The parser is added at the beginning of the parsers list so
	 * it will be processed before other already loaded parsers and before the default parsers.
	 * 

* This method should be used to customize the Spider from any other extension of ZAP. The * parsers added will be loaded whenever starting any scan. * * @param parser the parser * @throws IllegalArgumentException if the given parameter is {@code null}. * @see #removeCustomParser(SpiderParser) */ public void addCustomParser(SpiderParser parser) { validateParameterNonNull(parser, "parser"); this.customParsers.add(parser); } private static void validateParameterNonNull(Object object, String name) { if (object == null) { throw new IllegalArgumentException("Parameter " + name + " must not be null."); } } /** * Removes the given spider parser. *

* Nothing happens if the given parser was not previously added. * * @param parser the parser * @throws IllegalArgumentException if the given parameter is {@code null}. * @since 2.6.0 * @see #addCustomParser(SpiderParser) */ public void removeCustomParser(SpiderParser parser) { validateParameterNonNull(parser, "parser"); this.customParsers.remove(parser); } /** * Adds a custom fetch filter that would be used during the spidering. *

* This method should be used to customize the Spider from any other extension of ZAP. The * filters added will be loaded whenever starting any scan. * * @param filter the filter * @throws IllegalArgumentException if the given parameter is {@code null}. * @see #removeCustomFetchFilter(FetchFilter) */ public void addCustomFetchFilter(FetchFilter filter) { validateParameterNonNull(filter, "filter"); this.customFetchFilters.add(filter); } /** * Removes the given fetch filter. *

* Nothing happens if the given filter was not previously added. * * @param filter the filter * @throws IllegalArgumentException if the given parameter is {@code null}. * @since 2.6.0 * @see #addCustomFetchFilter(FetchFilter) */ public void removeCustomFetchFilter(FetchFilter filter) { validateParameterNonNull(filter, "filter"); this.customFetchFilters.remove(filter); } /** * Adds a custom parse filter that would be used during the spidering. *

* This method should be used to customize the Spider from any other extension of ZAP. The * filters added will be loaded whenever starting any scan. * * @param filter the filter * @throws IllegalArgumentException if the given parameter is {@code null}. * @see #removeCustomParseFilter(ParseFilter) */ public void addCustomParseFilter(ParseFilter filter) { validateParameterNonNull(filter, "filter"); this.customParseFilters.add(filter); } /** * Removes the given parse filter. *

* Nothing happens if the given filter was not previously added. * * @param filter the filter * @throws IllegalArgumentException if the given parameter is {@code null}. * @since 2.6.0 * @see #addCustomParseFilter(ParseFilter) */ public void removeCustomParseFilter(ParseFilter filter) { validateParameterNonNull(filter, "filter"); this.customParseFilters.remove(filter); } /** * Starts a new spider scan using the given target and, optionally, spidering from the perspective of a user and with custom * configurations. *

* The spider scan will use the most appropriate display name created from the given target, user and custom configurations. * * @param target the target that will be spidered * @param user the user that will be used to spider, might be {@code null} * @param customConfigurations other custom configurations for the spider, might be {@code null} * @return the ID of the spider scan * @since 2.5.0 * @see #startScan(String, Target, User, Object[]) * @throws IllegalStateException if the target or custom configurations are not allowed in the current * {@link org.parosproxy.paros.control.Control.Mode mode}. */ public int startScan(Target target, User user, Object[] customConfigurations) { return startScan(createDisplayName(target, customConfigurations), target, user, customConfigurations); } /** * Creates the display name for the given target and, optionally, the given custom configurations. * * @param target the target that will be spidered * @param customConfigurations other custom configurations for the spider, might be {@code null} * @return a {@code String} containing the display name, never {@code null} */ private String createDisplayName(Target target, Object[] customConfigurations) { HttpPrefixFetchFilter subtreeFecthFilter = getUriPrefixFecthFilter(customConfigurations); if (subtreeFecthFilter != null) { return abbreviateDisplayName(subtreeFecthFilter.getNormalisedPrefix()); } if (target.getContext() != null) { return Constant.messages.getString("context.prefixName", target.getContext().getName()); } else if (target.isInScopeOnly()) { return Constant.messages.getString("target.allInScope"); } else if (target.getStartNode() == null) { if (customConfigurations != null) { for (Object customConfiguration : customConfigurations) { if (customConfiguration instanceof URI) { return abbreviateDisplayName(((URI) customConfiguration).toString()); } } } return Constant.messages.getString("target.empty"); } return abbreviateDisplayName(target.getStartNode().getHierarchicNodeName(false)); } /** * Gets the {@code HttpPrefixFetchFilter} from the given {@code customConfigurations}. * * @param customConfigurations the custom configurations of the spider * @return the {@code HttpPrefixFetchFilter} found, {@code null} otherwise. */ private HttpPrefixFetchFilter getUriPrefixFecthFilter(Object[] customConfigurations) { if (customConfigurations != null) { for (Object customConfiguration : customConfigurations) { if (customConfiguration instanceof HttpPrefixFetchFilter) { return (HttpPrefixFetchFilter) customConfiguration; } } } return null; } /** * Abbreviates (the middle of) the given display name if greater than 30 characters. * * @param displayName the display name that might be abbreviated * @return the, possibly, abbreviated display name */ private static String abbreviateDisplayName(String displayName) { return StringUtils.abbreviateMiddle(displayName, "..", 30); } /** * Starts a new spider scan, with the given display name, using the given target and, optionally, spidering from the * perspective of a user and with custom configurations. *

* Note: The preferred method to start the scan is with {@link #startScan(Target, User, Object[])}, unless * a custom display name is really needed. * * @param target the target that will be spidered * @param user the user that will be used to spider, might be {@code null} * @param customConfigurations other custom configurations for the spider, might be {@code null} * @return the ID of the spider scan * @throws IllegalStateException if the target or custom configurations are not allowed in the current * {@link org.parosproxy.paros.control.Control.Mode mode}. */ @SuppressWarnings({"fallthrough"}) @Override public int startScan(String displayName, Target target, User user, Object[] customConfigurations) { switch (Control.getSingleton().getMode()) { case safe: throw new IllegalStateException("Scans are not allowed in Safe mode"); case protect: String uri = getTargetUriOutOfScope(target, customConfigurations); if (uri != null) { throw new IllegalStateException("Scans are not allowed on targets not in scope when in Protected mode: " + uri); } //$FALL-THROUGH$ case standard: case attack: // No problem break; } int id = this.scanController.startScan(displayName, target, user, customConfigurations); if (View.isInitialised()) { addScanToUi(this.scanController.getScan(id)); } return id; } private void addScanToUi(final SpiderScan scan) { if (!EventQueue.isDispatchThread()) { EventQueue.invokeLater(new Runnable() { @Override public void run() { addScanToUi(scan); } }); return; } this.getSpiderPanel().scannerStarted(scan); scan.setListener(getSpiderPanel()); // So the UI gets updated this.getSpiderPanel().switchView(scan); this.getSpiderPanel().setTabFocus(); } /** * Returns the first URI that is out of scope in the given {@code target}. * * @param target the target that will be checked * @return a {@code String} with the first URI out of scope, {@code null} if none found * @since 2.5.0 * @see Session#isInScope(String) */ protected String getTargetUriOutOfScope(Target target) { return getTargetUriOutOfScope(target, null); } /** * Returns the first URI that is out of scope in the given {@code target} or {@code contextSpecificObjects}. * * @param target the target that will be checked * @param contextSpecificObjects other {@code Objects} used to enhance the target * @return a {@code String} with the first URI out of scope, {@code null} if none found * @since 2.5.0 * @see Session#isInScope(String) */ protected String getTargetUriOutOfScope(Target target, Object[] contextSpecificObjects) { List nodes = target.getStartNodes(); if (nodes != null) { for (StructuralNode node : nodes) { if (node == null) { continue; } if (node instanceof StructuralSiteNode) { SiteNode siteNode = ((StructuralSiteNode) node).getSiteNode(); if (!siteNode.isIncludedInScope()) { return node.getURI().toString(); } } else { String uri = node.getURI().toString(); if (!isTargetUriInScope(uri)) { return uri; } } } } if (contextSpecificObjects != null) { for (Object obj : contextSpecificObjects) { if (obj instanceof URI) { String uri = ((URI) obj).toString(); if (!isTargetUriInScope(uri)) { return uri; } } } } return null; } /** * Tells whether or not the given {@code uri} is in scope. * * @param uri the uri that will be checked * @return {@code true} if the {@code uri} is in scope, {@code false} otherwise * @since 2.5.0 * @see Session#isInScope(String) */ protected boolean isTargetUriInScope(String uri) { if (uri == null) { return false; } return getModel().getSession().isInScope(uri); } @Override public List getAllScans() { return this.scanController.getAllScans(); } @Override public List getActiveScans() { return this.scanController.getActiveScans(); } @Override public SpiderScan getScan(int id) { return this.scanController.getScan(id); } @Override public void stopScan(int id) { this.scanController.stopScan(id); } @Override public void stopAllScans() { this.scanController.stopAllScans(); } @Override public void pauseScan(int id) { this.scanController.pauseScan(id); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getSpiderPanel().updateScannerUI(); } } @Override public void pauseAllScans() { this.scanController.pauseAllScans(); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getSpiderPanel().updateScannerUI(); } } @Override public void resumeScan(int id) { this.scanController.resumeScan(id); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getSpiderPanel().updateScannerUI(); } } @Override public void resumeAllScans() { this.scanController.resumeAllScans(); if (View.isInitialised()) { // Update the UI in case this was initiated from the API this.getSpiderPanel().updateScannerUI(); } } @Override public SpiderScan removeScan(int id) { return this.scanController.removeScan(id); } @Override public int removeAllScans() { return this.scanController.removeAllScans(); } @Override public int removeFinishedScans() { return this.scanController.removeFinishedScans(); } @Override public SpiderScan getLastScan() { return this.scanController.getLastScan(); } private ZapMenuItem getMenuItemCustomScan() { if (menuItemCustomScan == null) { menuItemCustomScan = new ZapMenuItem("menu.tools.spider", KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | KeyEvent.ALT_DOWN_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) { showSpiderDialog(null); } }); } return menuItemCustomScan; } public void showSpiderDialog(SiteNode node) { if (spiderDialog == null) { spiderDialog = new SpiderDialog(this, View.getSingleton().getMainFrame(), new Dimension(700, 430)); } if (spiderDialog.isVisible()) { // Its behind you! Actually not needed no the window is alwaysOnTop, but keeping in case we change that ;) spiderDialog.toFront(); return; } if (node != null) { spiderDialog.init(new Target(node)); } else { // Keep the previous target spiderDialog.init(null); } spiderDialog.setVisible(true); } @Override public boolean supportsLowMemory() { return true; } /** * No database tables used, so all supported */ @Override public boolean supportsDb(String type) { return true; } /** * Gets the icon for spider related functionality. * * @return the icon */ public Icon getIcon() { if (icon == null) { icon = new ImageIcon(ExtensionSpider.class.getResource("/resource/icon/16/spider.png")); } return icon; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy