/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 2010 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.params; import java.awt.EventQueue; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; 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.db.DatabaseException; import org.parosproxy.paros.db.RecordParam; import org.parosproxy.paros.extension.ExtensionAdaptor; import org.parosproxy.paros.extension.ExtensionHook; import org.parosproxy.paros.extension.ExtensionHookView; import org.parosproxy.paros.extension.ExtensionLoader; import org.parosproxy.paros.extension.SessionChangedListener; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.network.HtmlParameter; import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpHeaderField; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.extension.anticsrf.ExtensionAntiCSRF; import org.zaproxy.zap.extension.help.ExtensionHelp; import org.zaproxy.zap.extension.httpsessions.ExtensionHttpSessions; import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.search.ExtensionSearch; import org.zaproxy.zap.view.SiteMapListener; import org.zaproxy.zap.view.SiteMapTreeCellRenderer; public class ExtensionParams extends ExtensionAdaptor implements SessionChangedListener, /*ProxyListener, */ SiteMapListener{ public static final String NAME = "ExtensionParams"; private ParamsPanel paramsPanel = null; private PopupMenuParamSearch popupMenuSearch = null; private PopupMenuAddAntiCSRF popupMenuAddAntiCsrf = null; private PopupMenuRemoveAntiCSRF popupMenuRemoveAntiCsrf = null; private PopupMenuAddSession popupMenuAddSession = null; private PopupMenuRemoveSession popupMenuRemoveSession = null; private Map <String, SiteParameters> siteParamsMap = new HashMap <>(); private Logger logger = Logger.getLogger(ExtensionParams.class); private ExtensionHttpSessions extensionHttpSessions; private ParamScanner paramScanner; public ExtensionParams() { super(NAME); this.setOrder(58); } @Override public void hook(ExtensionHook extensionHook) { super.hook(extensionHook); extensionHook.addApiImplementor(new ParamsAPI(this)); extensionHook.addSessionListener(this); extensionHook.addSiteMapListener(this); if (getView() != null) { @SuppressWarnings("unused") ExtensionHookView pv = extensionHook.getHookView(); extensionHook.getHookView().addStatusPanel(getParamsPanel()); final ExtensionLoader extLoader = Control.getSingleton().getExtensionLoader(); if (extLoader.isExtensionEnabled(ExtensionSearch.NAME)) { extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuParamSearch()); } if (extLoader.isExtensionEnabled(ExtensionAntiCSRF.NAME)) { extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuAddAntiCSRF()); extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuRemoveAntiCSRF()); } if (extLoader.isExtensionEnabled(ExtensionHttpSessions.NAME)) { extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuAddSession()); extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuRemoveSession()); } ExtensionHelp.enableHelpKey(getParamsPanel(), "ui.tabs.params"); } ExtensionPassiveScan extensionPassiveScan = (ExtensionPassiveScan) Control.getSingleton() .getExtensionLoader() .getExtension(ExtensionPassiveScan.NAME); if (extensionPassiveScan != null) { paramScanner = new ParamScanner(this); extensionPassiveScan.addPassiveScanner(new ParamScanner(this)); } } @Override public void unload() { ExtensionPassiveScan extensionPassiveScan = (ExtensionPassiveScan) Control.getSingleton() .getExtensionLoader() .getExtension(ExtensionPassiveScan.NAME); if (extensionPassiveScan != null) { extensionPassiveScan.removePassiveScanner(paramScanner); } super.unload(); } private PopupMenuParamSearch getPopupMenuParamSearch() { if (popupMenuSearch == null) { popupMenuSearch = new PopupMenuParamSearch(); popupMenuSearch.setExtension(this); } return popupMenuSearch; } private PopupMenuAddAntiCSRF getPopupMenuAddAntiCSRF() { if (popupMenuAddAntiCsrf == null) { popupMenuAddAntiCsrf = new PopupMenuAddAntiCSRF(); popupMenuAddAntiCsrf.setExtension(this); } return popupMenuAddAntiCsrf; } private PopupMenuRemoveAntiCSRF getPopupMenuRemoveAntiCSRF() { if (popupMenuRemoveAntiCsrf == null) { popupMenuRemoveAntiCsrf = new PopupMenuRemoveAntiCSRF(); popupMenuRemoveAntiCsrf.setExtension(this); } return popupMenuRemoveAntiCsrf; } private PopupMenuAddSession getPopupMenuAddSession() { if (popupMenuAddSession == null) { popupMenuAddSession = new PopupMenuAddSession(); popupMenuAddSession.setExtension(this); } return popupMenuAddSession; } private PopupMenuRemoveSession getPopupMenuRemoveSession() { if (popupMenuRemoveSession == null) { popupMenuRemoveSession = new PopupMenuRemoveSession(); popupMenuRemoveSession.setExtension(this); } return popupMenuRemoveSession; } protected ParamsPanel getParamsPanel() { if (paramsPanel == null) { paramsPanel = new ParamsPanel(this); } return paramsPanel; } @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) { logger.error(e.getMessage(), e); } } } /** * Gets the ExtensionHttpSessions, if it's enabled * * @return the Http Sessions extension or null, if it's not available */ protected ExtensionHttpSessions getExtensionHttpSessions() { if(extensionHttpSessions==null){ extensionHttpSessions = (ExtensionHttpSessions) Control.getSingleton().getExtensionLoader() .getExtension(ExtensionHttpSessions.NAME); } return extensionHttpSessions; } private void sessionChangedEventHandler(Session session) { // Clear all scans siteParamsMap = new HashMap <>(); if (getView() != null) { this.getParamsPanel().reset(); } if (session == null) { // Closedown return; } // Repopulate SiteNode root = (SiteNode)session.getSiteTree().getRoot(); @SuppressWarnings("unchecked") Enumeration<SiteNode> en = root.children(); while (en.hasMoreElements()) { String site = en.nextElement().getNodeName(); if (getView() != null) { this.getParamsPanel().addSite(site); } } try { List<RecordParam> params = Model.getSingleton().getDb().getTableParam().getAll(); for (RecordParam param : params) { SiteParameters sps = this.getSiteParameters(param.getSite()); sps.addParam(param.getSite(), param); } } catch (DatabaseException e) { logger.error(e.getMessage(), e); } } public boolean onHttpRequestSend(HttpMessage msg) { // Check we know the site String site = msg.getRequestHeader().getHostName() + ":" + msg.getRequestHeader().getHostPort(); if (getView() != null) { this.getParamsPanel().addSite(site); } SiteParameters sps = this.siteParamsMap.get(site); if (sps == null) { sps = new SiteParameters(this, site); this.siteParamsMap.put(site, sps); } // Cookie Parameters TreeSet<HtmlParameter> params; Iterator<HtmlParameter> iter; try { params = msg.getCookieParams(); iter = params.iterator(); while (iter.hasNext()) { persist(sps.addParam(site, iter.next(), msg)); } } catch (Exception e) { logger.error(e.getMessage(), e); } // URL Parameters params = msg.getUrlParams(); iter = params.iterator(); while (iter.hasNext()) { persist(sps.addParam(site, iter.next(), msg)); } // Form Parameters // TODO flag anti csrf url ones too? ExtensionAntiCSRF extAntiCSRF = (ExtensionAntiCSRF) Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF.NAME); params = msg.getFormParams(); iter = params.iterator(); HtmlParameter param; while (iter.hasNext()) { param = iter.next(); if (extAntiCSRF != null && extAntiCSRF.isAntiCsrfToken(param.getName())) { param.addFlag(HtmlParameter.Flags.anticsrf.name()); } persist(sps.addParam(site, param, msg)); } return true; } private String setToString (Set<String> set) { StringBuilder sb = new StringBuilder(); if (set == null) { return ""; } for (String str : set) { if (sb.length() > 0) { sb.append(','); } // Escape all commas in the values sb.append(str.replace(",", "%2C")); } return sb.toString(); } private void persist(HtmlParameterStats param) { try { if (param.getId() < 0) { // Its a new one RecordParam rp = Model.getSingleton().getDb().getTableParam().insert( param.getSite(), param.getType().name(), param.getName(), param.getTimesUsed(), setToString(param.getFlags()), setToString(param.getValues())); param.setId(rp.getParamId()); } else { // Its an existing one Model.getSingleton().getDb().getTableParam().update( param.getId(), param.getTimesUsed(), setToString(param.getFlags()), setToString(param.getValues())); } } catch (DatabaseException e) { logger.error(e.getMessage(), e); } } public boolean onHttpResponseReceive(HttpMessage msg) { // Check we know the site String site = msg.getRequestHeader().getHostName() + ":" + msg.getRequestHeader().getHostPort(); if (getView() != null) { this.getParamsPanel().addSite(site); } SiteParameters sps = this.getSiteParameters(site); // Cookie Parameters TreeSet<HtmlParameter> params = msg.getCookieParams(); Iterator<HtmlParameter> iter = params.iterator(); while (iter.hasNext()) { persist(sps.addParam(site, iter.next(), msg)); } // Header "Parameters" List<HttpHeaderField> headersList = msg.getResponseHeader().getHeaders(); List<String> setCookieHeaders = Arrays.asList(HttpHeader.SET_COOKIE.toLowerCase(), HttpHeader.SET_COOKIE2.toLowerCase()); for (HttpHeaderField hdrField:headersList) { if (setCookieHeaders.contains(hdrField.getName().toLowerCase())) { continue; } HtmlParameter headerParam = new HtmlParameter(HtmlParameter.Type.header, hdrField.getName(), hdrField.getValue()); persist(sps.addParam(site, headerParam, msg)); } // TODO Only do if response URL different to request? // URL Parameters /* params = msg.getUrlParams(); iter = params.iterator(); while (iter.hasNext()) { sps.addParam(iter.next()); } */ return true; } @Override public void nodeSelected(SiteNode node) { // Event from SiteMapListenner this.getParamsPanel().nodeSelected(node); } @Override public void onReturnNodeRendererComponent( SiteMapTreeCellRenderer component, boolean leaf, SiteNode value) { } protected void searchForSelectedParam() { HtmlParameterStats item = this.getParamsPanel().getSelectedParam(); if (item != null) { ExtensionSearch extSearch = (ExtensionSearch) Control.getSingleton().getExtensionLoader().getExtension(ExtensionSearch.NAME); if (extSearch != null) { if (HtmlParameter.Type.url.equals(item.getType())) { extSearch.search("[?&]" + item.getName() + "=.*", ExtensionSearch.Type.URL, true, false); } else if (HtmlParameter.Type.cookie.equals(item.getType())) { extSearch.search(/*".*" + */item.getName() + "=.*", ExtensionSearch.Type.Header, true, false); } else if (HtmlParameter.Type.header.equals(item.getType())) { extSearch.search(item.getName() + ":.*", ExtensionSearch.Type.Header, true, false); } else { // FORM extSearch.search(/*".*" + */item.getName() + "=.*", ExtensionSearch.Type.Request, true, false); } } } } public void addAntiCsrfToken() { HtmlParameterStats item = this.getParamsPanel().getSelectedParam(); ExtensionAntiCSRF extAntiCSRF = (ExtensionAntiCSRF) Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF.NAME); if (extAntiCSRF != null && item != null) { extAntiCSRF.addAntiCsrfTokenName(item.getName()); item.addFlag(HtmlParameter.Flags.anticsrf.name()); // Repaint so change shows up this.getParamsPanel().getParamsTable().repaint(); // Dont think we need to do this... at least until rescan option implemented ... //Control.getSingleton().getMenuToolsControl().options(Constant.messages.getString("options.acsrf.title")); } } public void removeAntiCsrfToken() { HtmlParameterStats item = this.getParamsPanel().getSelectedParam(); ExtensionAntiCSRF extAntiCSRF = (ExtensionAntiCSRF) Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF.NAME); if (extAntiCSRF != null && item != null) { extAntiCSRF.removeAntiCsrfTokenName(item.getName()); item.removeFlag(HtmlParameter.Flags.anticsrf.name()); // Repaint so change shows up this.getParamsPanel().getParamsTable().repaint(); // Dont think we need to do this... at least until rescan option implemented ... //Control.getSingleton().getMenuToolsControl().options(Constant.messages.getString("options.acsrf.title")); } } /** * Tells whether or not the given {@code site} was already seen. * * @param site the site that will be checked * @return {@code true} if the given {@code site} was already seen, {@code false} otherwise. * @since 2.5.0 * @see #hasParameters(String) */ public boolean hasSite(String site) { return siteParamsMap.containsKey(site); } /** * Tells whether or not the given {@code site} has parameters. * * @param site the site that will be checked * @return {@code true} if the given {@code site} has parameters, {@code false} if not, or was not yet seen. * @since 2.5.0 * @see #hasSite(String) */ public boolean hasParameters(String site) { SiteParameters siteParameters = siteParamsMap.get(site); if (siteParameters == null) { return false; } return siteParameters.hasParams(); } public SiteParameters getSiteParameters(String site) { SiteParameters sps = this.siteParamsMap.get(site); if (sps == null) { sps = new SiteParameters(this, site); siteParamsMap.put(site, sps); } return sps; } public Collection<SiteParameters> getAllSiteParameters() { Collection<SiteParameters> values = this.siteParamsMap.values(); return values; } /** * Adds a new session token from the selected parameter. Also notifies the * {@link ExtensionHttpSessions} if it's active. */ public void addSessionToken() { // Get the selected parameter HtmlParameterStats item = this.getParamsPanel().getSelectedParam(); if (item != null) { // If the HttpSessions extension is active, notify it of the new session token ExtensionHttpSessions extSession = this.getExtensionHttpSessions(); if (extSession != null) { extSession.addHttpSessionToken(this.getParamsPanel().getCurrentSite(), item.getName()); } // Flag the item accordingly item.addFlag(HtmlParameter.Flags.session.name()); // Repaint so change shows up this.getParamsPanel().getParamsTable().repaint(); } } /** * Removes the currently selected parameter as a session token. Also notifies the * {@link ExtensionHttpSessions} if it's active. */ public void removeSessionToken() { HtmlParameterStats item = this.getParamsPanel().getSelectedParam(); if (item != null) { // If the HttpSessions extension is active, notify it of the removed session token ExtensionHttpSessions extSession = this.getExtensionHttpSessions(); if (extSession != null) { extSession.removeHttpSessionToken(this.getParamsPanel().getCurrentSite(), item.getName()); } // Unflag the item accordingly item.removeFlag(HtmlParameter.Flags.session.name()); // Repaint so change shows up this.getParamsPanel().getParamsTable().repaint(); } } public HtmlParameterStats getSelectedParam() { return this.getParamsPanel().getSelectedParam(); } @Override public void sessionAboutToChange(Session session) { } @Override public void sessionScopeChanged(Session session) { } @Override public String getAuthor() { return Constant.ZAP_TEAM; } @Override public String getDescription() { return Constant.messages.getString("params.desc"); } @Override public URL getURL() { try { return new URL(Constant.ZAP_HOMEPAGE); } catch (MalformedURLException e) { return null; } } @Override public void sessionModeChanged(Mode mode) { // Ignore } }