/*
* Copyright 2004-2014 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.assertion;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.CrossSiteScriptingScanConfig;
import com.eviware.soapui.config.HttpRequestConfig;
import com.eviware.soapui.config.TestAssertionConfig;
import com.eviware.soapui.config.TestStepConfig;
import com.eviware.soapui.impl.support.HttpUtils;
import com.eviware.soapui.impl.wsdl.panels.assertions.AssertionListEntry;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.AbstractGroovyEditorModel;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.impl.wsdl.teststeps.HttpTestRequestStep;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlMessageAssertion;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
import com.eviware.soapui.impl.wsdl.teststeps.assertions.AbstractTestAssertionFactory;
import com.eviware.soapui.impl.wsdl.teststeps.registry.HttpRequestStepFactory;
import com.eviware.soapui.impl.wsdl.teststeps.registry.WsdlTestStepFactory;
import com.eviware.soapui.impl.wsdl.teststeps.registry.WsdlTestStepRegistry;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.TestPropertyHolder;
import com.eviware.soapui.model.iface.MessageExchange;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.testsuite.Assertable;
import com.eviware.soapui.model.testsuite.AssertionError;
import com.eviware.soapui.model.testsuite.AssertionException;
import com.eviware.soapui.model.testsuite.ResponseAssertion;
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.scan.CrossSiteScriptingScan;
import com.eviware.soapui.support.SecurityScanUtil;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.GroovyEditorComponent;
import com.eviware.soapui.support.scripting.SoapUIScriptEngine;
import com.eviware.soapui.support.scripting.SoapUIScriptEngineRegistry;
import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder;
import com.eviware.soapui.support.xml.XmlObjectConfigurationReader;
import com.eviware.x.form.XFormDialog;
import com.eviware.x.form.XFormField;
import com.eviware.x.form.XFormFieldListener;
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 org.apache.xmlbeans.XmlObject;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JScrollPane;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
public class CrossSiteScriptAssertion extends WsdlMessageAssertion implements ResponseAssertion {
public static final String ID = "CrosSiteScript";
public static final String LABEL = "Cross Site Scripting Detection";
public static final String DESCRIPTION = "Cross Site Scripting....assertion for...";
public static final String GROOVY_SCRIPT = "groovyScript";
public static final String CHECK_RESPONSE = "checkResponse";
public static final String CHECK_SEPARATE_HTML = "checkSeparateHTML";
private XFormDialog dialog;
private String script;
private GroovyEditorModel groovyEditorModel;
private SoapUIScriptEngine scriptEngine;
MessageExchange messageExchange;
SubmitContext context;
private boolean checkResponse;
private boolean checkSeparateHTML;
public CrossSiteScriptAssertion(TestAssertionConfig assertionConfig, Assertable assertable) {
super(assertionConfig, assertable, false, true, false, true);
groovyEditorModel = new GroovyEditorModel(this);
init();
scriptEngine = SoapUIScriptEngineRegistry.create(this);
}
private void init() {
XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader(getConfiguration());
script = reader.readString(GROOVY_SCRIPT, "");
checkResponse = reader.readBoolean(CHECK_RESPONSE, true);
checkSeparateHTML = reader.readBoolean(CHECK_SEPARATE_HTML, false);
groovyEditorModel.setScript(script);
}
@Override
protected String internalAssertResponse(MessageExchange messageExchange, SubmitContext context)
throws AssertionException {
TestStep testStep = (TestStep) context.getProperty(CrossSiteScriptingScan.TEST_STEP);
testStep = SecurityTestRunnerImpl.cloneTestStepForSecurityScan((WsdlTestStep) testStep);
SecurityTestRunner securityTestRunner = (SecurityTestRunner) context
.getProperty(CrossSiteScriptingScan.TEST_CASE_RUNNER);
List<String> urls = submitScript(messageExchange, context);
CrossSiteScriptingScanConfig parameterExposureCheckConfig = (CrossSiteScriptingScanConfig) context
.getProperty(CrossSiteScriptingScan.PARAMETER_EXPOSURE_SCAN_CONFIG);
List<AssertionError> assertionErrorList = new ArrayList<AssertionError>();
boolean throwExceptionCheckResponse = false;
if (checkResponse) {
throwExceptionCheckResponse = checkResponse(messageExchange, context, parameterExposureCheckConfig,
assertionErrorList);
}
boolean throwExceptionCheckSeparateHTML = false;
if (checkSeparateHTML) {
throwExceptionCheckSeparateHTML = checkSeparateHTML(messageExchange, context, testStep, securityTestRunner,
urls, parameterExposureCheckConfig, assertionErrorList);
}
if (throwExceptionCheckResponse || throwExceptionCheckSeparateHTML) {
throw new AssertionException(assertionErrorList.toArray(new AssertionError[assertionErrorList.size()]));
}
return "OK";
}
private boolean checkSeparateHTML(MessageExchange messageExchange, SubmitContext context, TestStep testStep,
SecurityTestRunner securityTestRunner, List<String> urls,
CrossSiteScriptingScanConfig parameterExposureCheckConfig, List<AssertionError> assertionErrorList) {
boolean throwException = false;
for (String url : urls) {
HttpTestRequestStep httpRequest = createHttpRequest((WsdlTestStep) testStep, url);
MessageExchange messageExchange2 = (MessageExchange) httpRequest.run((TestCaseRunner) securityTestRunner,
(SecurityTestRunContext) context);
for (String value : parameterExposureCheckConfig.getParameterExposureStringsList()) {
value = context.expand(value);// property expansion support
String match = SecurityScanUtil.contains(context, new String(messageExchange2.getRawResponseData()),
value, false);
if (match != null) {
String shortValue = value.length() > 25 ? value.substring(0, 22) + "... " : value;
String message = "XSS content sent in request '" + shortValue + "' is exposed in response on link "
+ url + " . Possibility for XSS script attack in: " + messageExchange.getModelItem().getName();
assertionErrorList.add(new AssertionError(message));
throwException = true;
}
}
}
return throwException;
}
private boolean checkResponse(MessageExchange messageExchange, SubmitContext context,
CrossSiteScriptingScanConfig parameterExposureCheckConfig, List<AssertionError> assertionErrorList) {
boolean throwException = false;
for (String value : parameterExposureCheckConfig.getParameterExposureStringsList()) {
value = context.expand(value);// property expansion support
String match = SecurityScanUtil.contains(context, new String(messageExchange.getRawResponseData()), value,
false);
if (match != null) {
String shortValue = value.length() > 25 ? value.substring(0, 22) + "... " : value;
String message = "Content that is sent in request '" + shortValue
+ "' is exposed in response. Possibility for XSS script attack in: "
+ messageExchange.getModelItem().getName();
assertionErrorList.add(new AssertionError(message));
throwException = true;
}
}
return throwException;
}
private List<String> submitScript(MessageExchange messageExchange, SubmitContext context) {
List<String> urls = new ArrayList<String>();
scriptEngine.setScript(script);
scriptEngine.setVariable("urls", urls);
scriptEngine.setVariable("messageExchange", messageExchange);
this.messageExchange = messageExchange;
scriptEngine.setVariable("context", context);
this.context = context;
scriptEngine.setVariable("log", SoapUI.ensureGroovyLog());
try {
Object result = scriptEngine.run();
if (result instanceof List) {
urls = (List<String>) result;
}
} catch (Exception ex) {
SoapUI.logError(ex);
} finally {
scriptEngine.clearVariables();
}
return urls;
}
private HttpTestRequestStep createHttpRequest(WsdlTestStep testStep2, String url) {
HttpRequestConfig httpRequest = HttpRequestConfig.Factory.newInstance();
httpRequest.setEndpoint(HttpUtils.completeUrlWithHttpIfProtocolIsNotHttpOrHttpsOrPropertyExpansion(url));
httpRequest.setMethod("GET");
TestStepConfig testStepConfig = TestStepConfig.Factory.newInstance();
testStepConfig.setType(HttpRequestStepFactory.HTTPREQUEST_TYPE);
testStepConfig.setConfig(httpRequest);
testStepConfig.setName("Separate Request");
WsdlTestStepFactory factory = WsdlTestStepRegistry.getInstance().getFactory(
(HttpRequestStepFactory.HTTPREQUEST_TYPE));
return (HttpTestRequestStep) factory.buildTestStep((WsdlTestCase) testStep2.getTestCase(), testStepConfig,
false);
}
protected String internalAssertProperty(TestPropertyHolder source, String propertyName,
MessageExchange messageExchange, SubmitContext context) throws AssertionException {
// return internalAssertResponse( messageExchange, context );
return null;
}
public static class Factory extends AbstractTestAssertionFactory {
public Factory() {
super(CrossSiteScriptAssertion.ID, CrossSiteScriptAssertion.LABEL, CrossSiteScriptAssertion.class,
CrossSiteScriptingScan.class);
}
@Override
public String getCategory() {
return "";
}
@Override
public Class<? extends WsdlMessageAssertion> getAssertionClassType() {
return CrossSiteScriptAssertion.class;
}
@Override
public AssertionListEntry getAssertionListEntry() {
return new AssertionListEntry(CrossSiteScriptAssertion.ID, CrossSiteScriptAssertion.LABEL,
CrossSiteScriptAssertion.DESCRIPTION);
}
}
@Override
protected String internalAssertRequest(MessageExchange messageExchange, SubmitContext context)
throws AssertionException {
return null;
}
protected XmlObject createConfiguration() {
XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
builder.add(GROOVY_SCRIPT, script);
builder.add(CHECK_RESPONSE, checkResponse);
builder.add(CHECK_SEPARATE_HTML, checkSeparateHTML);
return builder.finish();
}
public boolean configure() {
if (dialog == null) {
buildDialog();
}
dialog.show();
if (dialog.getReturnValue() == XFormDialog.OK_OPTION) {
checkResponse = Boolean.valueOf(dialog.getFormField(CrossSiteScripSeparateHTMLConfigDialog.CHECK_RESPONSE)
.getValue());
checkSeparateHTML = Boolean.valueOf(dialog.getFormField(
CrossSiteScripSeparateHTMLConfigDialog.CHECK_SEPARATE_HTML).getValue());
setConfiguration(createConfiguration());
}
return true;
}
private class GroovyEditorModel extends AbstractGroovyEditorModel {
@Override
public Action createRunAction() {
return new AbstractAction() {
public void actionPerformed(ActionEvent e) {
Object result = null;
List<String> urls = new ArrayList<String>();
scriptEngine.setScript(script);
scriptEngine.setVariable("urls", urls);
scriptEngine.setVariable("messageExchange", messageExchange);
scriptEngine.setVariable("context", context);
scriptEngine.setVariable("log", SoapUI.ensureGroovyLog());
try {
result = scriptEngine.run();
if (result instanceof List) {
urls = (List<String>) result;
}
String generatedUrls = "";
for (String url : urls) {
generatedUrls += "\n" + url;
}
UISupport.showInfoMessage("Generated urls :" + generatedUrls + " \n\nScript result"
+ ((result == null) ? "" : ": " + result + ""));
} catch (Exception ex) {
SoapUI.logError(ex);
} finally {
scriptEngine.clearVariables();
}
}
};
}
public GroovyEditorModel(ModelItem modelItem) {
super(new String[]{"urls", "log", "context", "messageExchange"}, modelItem, "");
}
public String getScript() {
return script;
}
public void setScript(String text) {
script = text;
}
}
protected GroovyEditorComponent buildGroovyPanel() {
return new GroovyEditorComponent(groovyEditorModel, null);
}
protected void buildDialog() {
dialog = ADialogBuilder.buildDialog(CrossSiteScripSeparateHTMLConfigDialog.class);
dialog.setSize(600, 600);
dialog.setBooleanValue(CrossSiteScripSeparateHTMLConfigDialog.CHECK_RESPONSE, checkResponse);
dialog.setBooleanValue(CrossSiteScripSeparateHTMLConfigDialog.CHECK_SEPARATE_HTML, checkSeparateHTML);
final GroovyEditorComponent groovyEditorComponent = buildGroovyPanel();
dialog.getFormField(CrossSiteScripSeparateHTMLConfigDialog.GROOVY).setProperty("component",
new JScrollPane(groovyEditorComponent));
dialog.getFormField(CrossSiteScripSeparateHTMLConfigDialog.GROOVY).setProperty("dimension",
new Dimension(450, 400));
dialog.getFormField(CrossSiteScripSeparateHTMLConfigDialog.CHECK_SEPARATE_HTML).addFormFieldListener(
new XFormFieldListener() {
@Override
public void valueChanged(XFormField sourceField, String newValue, String oldValue) {
groovyEditorComponent.setEnabled(new Boolean(newValue));
}
});
groovyEditorComponent.setEnabled(checkSeparateHTML);
}
@Override
public void release() {
if (dialog != null) {
dialog.release();
}
super.release();
}
@AForm(description = "", name = "Cross Site Scripting on Separate HTML", helpUrl = HelpUrls.SECURITY_XSS_ASSERTION_HELP)
protected interface CrossSiteScripSeparateHTMLConfigDialog {
@AField(description = "Check Imediate Response", name = "###Check Response", type = AFieldType.BOOLEAN)
public final static String CHECK_RESPONSE = "###Check Response";
@AField(description = "Check Response from URLs specified in Custom Script", name = "###Check Separate HTML", type = AFieldType.BOOLEAN)
public final static String CHECK_SEPARATE_HTML = "###Check Separate HTML";
@AField(description = "", name = "Enter Custom Script that returns a list of URLs to check for Cross Site Scripts ", type = AFieldType.LABEL)
public final static String LABEL = "Enter Custom Script that returns a list of URLs to check for Cross Site Scripts ";
@AField(description = "Groovy script", name = "###Groovy url list", type = AFieldType.COMPONENT)
public final static String GROOVY = "###Groovy url list";
}
}