/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI 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 GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.security.assertion;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JScrollPane;
import org.apache.xmlbeans.XmlObject;
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.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.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;
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 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.ensureEndpointStartsWithProtocol( 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 );
}
public static class Factory extends AbstractTestAssertionFactory
{
public Factory()
{
super( CrossSiteScriptAssertion.ID, CrossSiteScriptAssertion.LABEL, CrossSiteScriptAssertion.class,
CrossSiteScriptingScan.class );
}
@Override
public Class<? extends WsdlMessageAssertion> getAssertionClassType()
{
return CrossSiteScriptAssertion.class;
}
}
@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();
}
// TODO : update help URL
@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";
}
}