/*
*
* 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: 2012/04/25 Added @Override annotation to the appropriate method and removed unnecessary casts.
// ZAP: 2012/08/31 Added support for AttackStrength
// ZAP: 2013/02/12 Added variant handling the parameters of OData urls
// ZAP: 2013/05/02 Re-arranged all modifiers into Java coding standard order
// ZAP: 2013/07/02 Changed Vector to generic List and added new varaints for GWT, JSON and Headers
// ZAP: 2013/07/03 Added variant handling attributes and data contained in XML requests
// ZAP: 2013/07/14 Issue 726: Catch active scan variants' exceptions
// ZAP: 2013/09/23 Issue 795: Allow param types scanned to be configured via UI
// ZAP: 2013/09/26 Reviewed Variant Panel configuration
// ZAP: 2014/01/10 Issue 974: Scan URL path elements
// ZAP: 2014/02/07 Issue 1018: Give AbstractAppParamPlugin implementations access to the parameter type
// ZAP: 2014/02/09 Add custom input vector scripting capabilities
// ZAP: 2014/08/14 Issue 1279: Active scanner excluded parameters not working when "Where" is "Any"
// ZAP: 2016/06/15 Add VariantHeader based on the current scan options
package org.parosproxy.paros.core.scanner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.log4j.Logger;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.extension.ascan.ExtensionActiveScan;
import org.zaproxy.zap.extension.script.ExtensionScript;
import org.zaproxy.zap.extension.script.ScriptWrapper;
public abstract class AbstractAppParamPlugin extends AbstractAppPlugin {
private final Logger logger = Logger.getLogger(this.getClass());
private final ArrayList<Variant> listVariant = new ArrayList<>();
private NameValuePair originalPair = null;
private Variant variant = null;
private ExtensionScript extension;
@Override
public void scan() {
ScannerParam scanOptions = this.getParent().getScannerParam();
int targets = scanOptions.getTargetParamsInjectable();
int enabledRPC = scanOptions.getTargetParamsEnabledRPC();
// First check URL query-string target configuration
if ((targets & ScannerParam.TARGET_QUERYSTRING) != 0) {
listVariant.add(new VariantURLQuery());
// ZAP: To handle parameters in OData urls
if ((enabledRPC & ScannerParam.RPC_ODATA) != 0) {
listVariant.add(new VariantODataIdQuery());
listVariant.add(new VariantODataFilterQuery());
}
}
// Then check POST data target configuration and RPC enabled methods
if ((targets & ScannerParam.TARGET_POSTDATA) != 0) {
listVariant.add(new VariantFormQuery());
// ZAP: To handle Multipart Form-Data POST requests
if ((enabledRPC & ScannerParam.RPC_MULTIPART) != 0) {
listVariant.add(new VariantMultipartFormQuery());
}
// ZAP: To handle XML based POST requests
if ((enabledRPC & ScannerParam.RPC_XML) != 0) {
listVariant.add(new VariantXMLQuery());
}
// ZAP: To handle JSON based POST requests
if ((enabledRPC & ScannerParam.RPC_JSON) != 0) {
listVariant.add(new VariantJSONQuery());
}
// ZAP: To handle GWT Serialized POST requests
if ((enabledRPC & ScannerParam.RPC_GWT) != 0) {
listVariant.add(new VariantGWTQuery());
}
// ZAP: To handle Direct Web Remoting (DWR) POST requests
if ((enabledRPC & ScannerParam.RPC_DWR) != 0) {
listVariant.add(new VariantDirectWebRemotingQuery());
}
}
if ((targets & ScannerParam.TARGET_HTTPHEADERS) != 0) {
boolean addVariant = scanOptions.isScanHeadersAllRequests();
if (!addVariant) {
// If not scanning all requests check if it looks like a dynamic or static page (based on query/post parameters)
HttpMessage message = getBaseMsg();
char[] query = message.getRequestHeader().getURI().getRawQuery();
addVariant = (query != null && query.length != 0) || message.getRequestBody().length() != 0;
}
if (addVariant) {
listVariant.add(new VariantHeader());
}
}
if ((targets & ScannerParam.TARGET_URLPATH) != 0) {
listVariant.add(new VariantURLPath());
}
// Currently usual plugins seems not
// suitable to cookie vulnerabilities
// 'cause the character RFC limitation
// is it useful?
if ((targets & ScannerParam.TARGET_COOKIE) != 0) {
listVariant.add(new VariantCookie());
}
// Now is time to initialize all the custom Variants
if ((enabledRPC & ScannerParam.RPC_CUSTOM) != 0) {
if (getExtension() != null) {
// List the scripts and create as many custom variants as the scripts
List<ScriptWrapper> scripts = getExtension().getScripts(ExtensionActiveScan.SCRIPT_TYPE_VARIANT);
for (ScriptWrapper script : scripts) {
if (script.isEnabled()) {
listVariant.add(new VariantCustom(script, getExtension()));
}
}
}
}
if ((enabledRPC & ScannerParam.RPC_USERDEF) != 0) {
listVariant.add(new VariantUserDefined());
}
for (int i = 0; i < listVariant.size() && !isStop(); i++) {
HttpMessage msg = getNewMsg();
// ZAP: Removed unnecessary cast.
variant = listVariant.get(i);
try {
variant.setMessage(msg);
scanVariant();
} catch (Exception e) {
logger.error("Error occurred while scanning with variant " + variant.getClass().getCanonicalName(), e);
}
// ZAP: Implement pause and resume
while (getParent().isPaused() && !isStop()) {
Util.sleep(500);
}
}
}
/**
* Scan the current message using the current Variant
*/
private void scanVariant() {
for (int i = 0; i < variant.getParamList().size() && !isStop(); i++) {
// ZAP: Removed unnecessary cast.
originalPair = variant.getParamList().get(i);
if (!isToExclude(originalPair)) {
// We need to use a fresh copy of the original message
// for further analysis inside all plugins
HttpMessage msg = getNewMsg();
try {
scan(msg, originalPair);
} catch (Exception e) {
logger.error("Error occurred while scanning a message:", e);
}
}
}
}
/**
* Inner methid to check if the current parameter should be excluded
* @param param the param object
* @return true if it need to be excluded
*/
private boolean isToExclude(NameValuePair param) {
List<ScannerParamFilter> excludedParameters = getParameterExclusionFilters(param);
// We can use the base one, we don't do anything with it
HttpMessage msg = getBaseMsg();
for (ScannerParamFilter filter : excludedParameters) {
if (filter.isToExclude(msg, param)) {
return true;
}
}
return false;
}
private List<ScannerParamFilter> getParameterExclusionFilters(NameValuePair parameter) {
List<ScannerParamFilter> globalExclusionFilters = this.getParent()
.getScannerParam()
.getExcludedParamList(NameValuePair.TYPE_UNDEFINED);
List<ScannerParamFilter> exclusionFilters = getParent().getScannerParam().getExcludedParamList(parameter.getType());
if (globalExclusionFilters == null) {
if (exclusionFilters != null) {
return exclusionFilters;
}
return Collections.emptyList();
} else if (exclusionFilters == null) {
return globalExclusionFilters;
}
List<ScannerParamFilter> allFilters = new ArrayList<>(globalExclusionFilters.size() + exclusionFilters.size());
allFilters.addAll(globalExclusionFilters);
allFilters.addAll(exclusionFilters);
return allFilters;
}
/**
* Plugin method that need to be implemented for the specific test.
* The passed message is a copy which maintains only the Request's information
* so if the plugin need to manage the original Response body a getBaseMsg()
* call should be done. the param name and the value are the original value
* retrieved by the crawler and the current applied Variant.
* @param msg a copy of the HTTP message currently under scanning
* @param param the name of the parameter under testing
* @param value the clean value (no escaping is needed)
*/
public abstract void scan(HttpMessage msg, String param, String value);
/**
* General method for a specific Parameter scanning, which allows developers
* to access all the settings specific of the parameters like the place/type
* where the name/value pair has been retrieved. This method can be overridden
* so that plugins that need a more deep access to the parameter context can
* benefit about this possibility.
* @param msg a copy of the HTTP message currently under scanning
* @param originalParam the parameter pair with all the context informations
*/
public void scan(HttpMessage msg, NameValuePair originalParam) {
scan(msg, originalParam.getName(), originalParam.getValue());
}
/**
* Sets the parameter into the given {@code message}. If both parameter name and value are {@code null}, the parameter will
* be removed.
*
* @param message the message that will be changed
* @param param the name of the parameter
* @param value the value of the parameter
* @return the parameter set
* @see #setEscapedParameter(HttpMessage, String, String)
*/
protected String setParameter(HttpMessage message, String param, String value) {
return variant.setParameter(message, originalPair, param, value);
}
/**
* Sets the parameter into the given {@code message}. If both parameter name and value are {@code null}, the parameter will
* be removed.
* <p>
* The value is expected to be properly encoded/escaped.
*
* @param message the message that will be changed
* @param param the name of the parameter
* @param value the value of the parameter
* @return the parameter set
* @see #setParameter(HttpMessage, String, String)
*/
protected String setEscapedParameter(HttpMessage message, String param, String value) {
return variant.setEscapedParameter(message, originalPair, param, value);
}
private ExtensionScript getExtension() {
if (extension == null) {
extension = (ExtensionScript) Control.getSingleton().getExtensionLoader().getExtension(ExtensionScript.NAME);
}
return extension;
}
}