/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.security.scan;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.CrossSiteScriptingScanConfig;
import com.eviware.soapui.config.SecurityScanConfig;
import com.eviware.soapui.config.StrategyTypeConfig;
import com.eviware.soapui.impl.wsdl.teststeps.RestRequestStepResult;
import com.eviware.soapui.impl.wsdl.teststeps.RestTestRequestStep;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStepResult;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.MessageExchange;
import com.eviware.soapui.model.security.SecurityCheckedParameter;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.security.SecurityTestRunContext;
import com.eviware.soapui.security.SecurityTestRunner;
import com.eviware.soapui.security.SecurityTestRunnerImpl;
import com.eviware.soapui.security.assertion.CrossSiteScriptAssertion;
import com.eviware.soapui.support.SecurityScanUtil;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.xml.XmlObjectTreeModel;
import com.eviware.soapui.support.xml.XmlObjectTreeModel.XmlTreeNode;
import com.eviware.x.form.support.ADialogBuilder;
import com.eviware.x.form.support.AField;
import com.eviware.x.form.support.AField.AFieldType;
import com.eviware.x.form.support.AForm;
import com.eviware.x.impl.swing.JFormDialog;
import com.eviware.x.impl.swing.JStringListFormField;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* This checks whether any parameters sent in the request are included in the
* response, If they do appear, this is a good parameter to look at as a
* possible attack vector for XSS
*
* @author nebojsa.tasic
*/
public class CrossSiteScriptingScan extends AbstractSecurityScanWithProperties {
public static final String TYPE = "CrossSiteScriptingScan";
public static final String NAME = "Cross Site Scripting";
public static final String PARAMETER_EXPOSURE_SCAN_CONFIG = "CrossSiteScriptingScanConfig";
public static final String TEST_CASE_RUNNER = "testCaseRunner";
public static final String TEST_STEP = "testStep";
private CrossSiteScriptingScanConfig cssConfig;
StrategyTypeConfig.Enum strategy = StrategyTypeConfig.ONE_BY_ONE;
List<String> defaultParameterExposureStrings = new ArrayList<String>();
private JFormDialog dialog;
public CrossSiteScriptingScan(TestStep testStep, SecurityScanConfig config, ModelItem parent, String icon) {
super(testStep, config, parent, icon);
if (config.getConfig() == null || !(config.getConfig() instanceof CrossSiteScriptingScanConfig)) {
initConfig();
} else {
cssConfig = (CrossSiteScriptingScanConfig) getConfig().getConfig();
}
}
private void initDefaultVectors() {
try {
InputStream in = SoapUI.class.getResourceAsStream("/com/eviware/soapui/resources/security/XSS-vectors.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String strLine;
while ((strLine = br.readLine()) != null) {
defaultParameterExposureStrings.add(strLine);
}
in.close();
} catch (Exception e) {
SoapUI.logError(e);
}
}
@Override
protected void initAssertions() {
super.initAssertions();
if (assertionsSupport.getAssertionByName(CrossSiteScriptAssertion.LABEL) == null) {
assertionsSupport.addWsdlAssertion(CrossSiteScriptAssertion.LABEL);
}
}
private void initConfig() {
initDefaultVectors();
getConfig().setConfig(CrossSiteScriptingScanConfig.Factory.newInstance());
cssConfig = (CrossSiteScriptingScanConfig) getConfig().getConfig();
cssConfig.setParameterExposureStringsArray(defaultParameterExposureStrings
.toArray(new String[defaultParameterExposureStrings.size()]));
}
@Override
public void updateSecurityConfig(SecurityScanConfig config) {
super.updateSecurityConfig(config);
if (cssConfig != null) {
cssConfig = (CrossSiteScriptingScanConfig) getConfig().getConfig();
}
}
@Override
protected void execute(SecurityTestRunner securityTestRunner, TestStep testStep, SecurityTestRunContext context) {
sendToContext(context, testStep, securityTestRunner);
PropertyMutation mutation = PropertyMutation.popMutation(context);
if (mutation != null) {
if (testStep instanceof RestTestRequestStep) {
RestRequestStepResult message = (RestRequestStepResult) mutation.getTestStep().run(
(TestCaseRunner) securityTestRunner, context);
message.setRequestContent("");
createMessageExchange(mutation.getMutatedParameters(), message, context);
} else {
MessageExchange message = (MessageExchange) mutation.getTestStep().run(
(TestCaseRunner) securityTestRunner, context);
if (message instanceof WsdlTestRequestStepResult) {
((WsdlTestRequestStepResult) message).setRequestContent("", false);
}
createMessageExchange(mutation.getMutatedParameters(), message, context);
}
}
}
private void sendToContext(SecurityTestRunContext context, TestStep testStep, SecurityTestRunner securityTestRunner) {
context.put(TEST_CASE_RUNNER, securityTestRunner);
context.put(TEST_STEP, testStep);
}
private void removeFromContext(SecurityTestRunContext context) {
context.remove(TEST_CASE_RUNNER);
context.remove(TEST_STEP);
}
@Override
public JComponent getComponent() {
JPanel p = UISupport.createEmptyPanel(5, 75, 0, 5);
p.add(new JLabel("Strings for Cross Site Scripting can be configured under Advanced Settings"));
return p;
}
@Override
public String getType() {
return TYPE;
}
@SuppressWarnings("unchecked")
@Override
protected boolean hasNext(TestStep testStep, SecurityTestRunContext context) {
if (!context.hasProperty(PropertyMutation.REQUEST_MUTATIONS_STACK)) {
Stack<PropertyMutation> requestMutationsList = new Stack<PropertyMutation>();
context.put(PropertyMutation.REQUEST_MUTATIONS_STACK, requestMutationsList);
context.put(PARAMETER_EXPOSURE_SCAN_CONFIG, cssConfig);
try {
extractMutations(testStep, context);
} catch (Exception e) {
SoapUI.logError(e);
}
return checkIfEmptyStack(context);
}
Stack<PropertyMutation> stack = (Stack<PropertyMutation>) context.get(PropertyMutation.REQUEST_MUTATIONS_STACK);
if (stack.empty()) {
context.remove(PropertyMutation.REQUEST_MUTATIONS_STACK);
context.remove(PARAMETER_EXPOSURE_SCAN_CONFIG);
removeFromContext(context);
return false;
} else {
return true;
}
}
@SuppressWarnings("unchecked")
private boolean checkIfEmptyStack(SecurityTestRunContext context) {
Stack<PropertyMutation> stack = (Stack<PropertyMutation>) context.get(PropertyMutation.REQUEST_MUTATIONS_STACK);
if (stack.empty()) {
return false;
} else {
return true;
}
}
private void extractMutations(TestStep testStep, SecurityTestRunContext context) {
strategy = getExecutionStrategy().getStrategy();
for (String value : cssConfig.getParameterExposureStringsList()) {
// property expansion support
value = context.expand(value);
PropertyMutation allAtOncePropertyMutation = new PropertyMutation();
TestStep testStepCopy = null;
XmlObjectTreeModel model = null;
List<SecurityCheckedParameter> scpList = getParameterHolder().getParameterList();
StringToStringMap stsmap = new StringToStringMap();
for (SecurityCheckedParameter scp : scpList) {
if (strategy.equals(StrategyTypeConfig.ONE_BY_ONE)) {
stsmap = new StringToStringMap();
model = SecurityScanUtil.getXmlObjectTreeModel(testStep, scp);
testStepCopy = SecurityTestRunnerImpl.cloneTestStepForSecurityScan((WsdlTestStep) testStep);
} else {
if (model == null) {
model = SecurityScanUtil.getXmlObjectTreeModel(testStep, scp);
}
if (testStepCopy == null) {
testStepCopy = SecurityTestRunnerImpl.cloneTestStepForSecurityScan((WsdlTestStep) testStep);
}
}
// if parameter is xml
if (scp.isChecked() && scp.getXpath().trim().length() > 0) {
XmlTreeNode[] treeNodes = null;
treeNodes = model.selectTreeNodes(context.expand(scp.getXpath()));
if (treeNodes.length > 0) {
XmlTreeNode mynode = treeNodes[0];
// work only for simple types
if (mynode.isLeaf()) {
mynode.setValue(1, value);
if (strategy.equals(StrategyTypeConfig.ONE_BY_ONE)) {
PropertyMutation oneByOnePropertyMutation = new PropertyMutation();
oneByOnePropertyMutation.setPropertyName(scp.getName());
oneByOnePropertyMutation.setPropertyValue(unescapEscaped(model.getXmlObject().toString()));
stsmap.put(scp.getLabel(), mynode.getNodeText());
oneByOnePropertyMutation.setMutatedParameters(stsmap);
oneByOnePropertyMutation.updateRequestProperty(testStepCopy);
oneByOnePropertyMutation.setTestStep(testStepCopy);
oneByOnePropertyMutation.addMutation(context);
} else {
allAtOncePropertyMutation.setPropertyName(scp.getName());
allAtOncePropertyMutation.setPropertyValue(unescapEscaped(model.getXmlObject().toString()));
stsmap.put(scp.getLabel(), mynode.getNodeText());
allAtOncePropertyMutation.setMutatedParameters(stsmap);
allAtOncePropertyMutation.updateRequestProperty(testStepCopy);
allAtOncePropertyMutation.setTestStep(testStepCopy);
}
}
}
}
// non xml parameter
else {
if (strategy.equals(StrategyTypeConfig.ONE_BY_ONE)) {
PropertyMutation oneByOnePropertyMutation = new PropertyMutation();
oneByOnePropertyMutation.setPropertyName(scp.getName());
oneByOnePropertyMutation.setPropertyValue(value);
stsmap.put(scp.getLabel(), value);
oneByOnePropertyMutation.setMutatedParameters(stsmap);
oneByOnePropertyMutation.updateRequestProperty(testStepCopy);
oneByOnePropertyMutation.setTestStep(testStepCopy);
oneByOnePropertyMutation.addMutation(context);
} else {
allAtOncePropertyMutation.setPropertyName(scp.getName());
allAtOncePropertyMutation.setPropertyValue(value);
stsmap.put(scp.getLabel(), value);
allAtOncePropertyMutation.setMutatedParameters(stsmap);
allAtOncePropertyMutation.updateRequestProperty(testStepCopy);
allAtOncePropertyMutation.setTestStep(testStepCopy);
}
}
}
if (strategy.equals(StrategyTypeConfig.ALL_AT_ONCE)) {
allAtOncePropertyMutation.addMutation(context);
}
}
}
private String unescapEscaped(String value) {
return value.replaceAll("<", "<");
}
@Override
public String getConfigDescription() {
return "Configures parameter exposure security scan";
}
@Override
public String getConfigName() {
return "Cross Site Scripting Scan";
}
@Override
public String getHelpURL() {
return "http://soapui.org/Security/cross-site-scripting.html";
}
@Override
public JComponent getAdvancedSettingsPanel() {
dialog = (JFormDialog) ADialogBuilder.buildDialog(AdvancedSettings.class);
JStringListFormField stringField = (JStringListFormField) dialog
.getFormField(AdvancedSettings.PARAMETER_EXPOSURE_STRINGS);
stringField.setOptions(cssConfig.getParameterExposureStringsList().toArray());
stringField.setProperty("dimension", new Dimension(470, 150));
stringField.getComponent().addPropertyChangeListener("options", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String[] newOptions = (String[]) evt.getNewValue();
String[] oldOptions = (String[]) evt.getOldValue();
// added
if (newOptions.length > oldOptions.length) {
// new element is always added to the end
String[] newValue = (String[]) evt.getNewValue();
String itemToAdd = newValue[newValue.length - 1];
cssConfig.addParameterExposureStrings(itemToAdd);
}
// removed
if (newOptions.length < oldOptions.length) {
/*
* items with same index should me same. first one in oldOptions
* that does not match is element that is removed.
*/
for (int cnt = 0; cnt < oldOptions.length; cnt++) {
if (cnt < newOptions.length) {
if (newOptions[cnt] != oldOptions[cnt]) {
cssConfig.removeParameterExposureStrings(cnt);
break;
}
} else {
// this is border case, last lement in array is removed.
cssConfig.removeParameterExposureStrings(oldOptions.length - 1);
}
}
}
}
});
return dialog.getPanel();
}
@Override
public void release() {
if (dialog != null) {
dialog.release();
}
super.release();
}
@AForm(description = "Cross Site Scripting", name = "Cross Site Scripting")
protected interface AdvancedSettings {
@AField(description = "Cross Site Scripting Vectors", name = "###Cross Site Scripting", type = AFieldType.STRINGLIST)
public final static String PARAMETER_EXPOSURE_STRINGS = "###Cross Site Scripting";
}
}