/*
* 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.scan;
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;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
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.WsdlTestRequestStepResult;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
import com.eviware.soapui.model.ModelItem;
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.AForm;
import com.eviware.x.form.support.AField.AFieldType;
import com.eviware.x.impl.swing.JFormDialog;
import com.eviware.x.impl.swing.JStringListFormField;
/**
* 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 )
{
WsdlTestRequestStepResult message = ( WsdlTestRequestStepResult )mutation.getTestStep().run(
( TestCaseRunner )securityTestRunner, context );
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";
}
}