org.zaproxy.zap.extension.soap.SOAPXMLInjectionActiveScanner.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.soap.SOAPXMLInjectionActiveScanner.java

Source

/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2014 The ZAP Development Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zaproxy.zap.extension.soap;

import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPMessage;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.scanner.AbstractAppParamPlugin;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.network.HttpMessage;

/**
 * SOAP XML Injection Active Scanner
 *
 * @author Albertov91
 */
public class SOAPXMLInjectionActiveScanner extends AbstractAppParamPlugin {

    private static final String MESSAGE_PREFIX = "soap.soapxmlinjection.";

    private static final Logger LOG = Logger.getLogger(SOAPXMLInjectionActiveScanner.class);

    @Override
    public int getId() {
        return 90029;
    }

    @Override
    public String getName() {
        return Constant.messages.getString(MESSAGE_PREFIX + "name");
    }

    public String getDescription() {
        return Constant.messages.getString(MESSAGE_PREFIX + "desc");
    }

    public String getSolution() {
        return Constant.messages.getString(MESSAGE_PREFIX + "soln");
    }

    public String getReference() {
        return Constant.messages.getString(MESSAGE_PREFIX + "refs");
    }

    @Override
    public String[] getDependency() {
        return null;
    }

    @Override
    public int getCategory() {
        return Category.MISC;
    }

    @Override
    public void init() {
    }

    /*
     * This method is called by the active scanner for each GET and POST parameter
     * for every page
     *
     * @see
     * org.parosproxy.paros.core.scanner.AbstractAppParamPlugin#scan(org.parosproxy.
     * paros.network.HttpMessage, java.lang.String, java.lang.String)
     */
    @Override
    public void scan(HttpMessage msg, String paramName, String paramValue) {
        try {
            /* This scan is only applied to SOAP messages. */
            final String request = new String(msg.getRequestBody().getBytes());
            final String reqCharset = msg.getRequestBody().getCharset();
            if (this.isStop())
                return;
            if (isSoapMessage(request, reqCharset)) {
                String paramValue2 = paramValue + "_modified";
                String finalValue = paramValue + "</" + paramName + "><" + paramName + ">" + paramValue2;
                /* Request message that contains the modified value. */
                HttpMessage modifiedMsg = craftAttackMessage(msg, paramName, paramValue2);
                if (modifiedMsg == null)
                    return;
                /* Request message that contains the XML code to be injected. */
                HttpMessage attackMsg = craftAttackMessage(msg, paramName, finalValue);
                final String escapedContent = new String(attackMsg.getRequestBody().getBytes());
                final String unescapedContent = StringEscapeUtils.unescapeXml(escapedContent);
                attackMsg.setRequestBody(unescapedContent);
                /* Sends the modified request. */
                if (this.isStop())
                    return;
                sendAndReceive(modifiedMsg);
                if (this.isStop())
                    return;
                sendAndReceive(attackMsg);
                if (this.isStop())
                    return;
                /* Analyzes the response. */
                final String response = new String(attackMsg.getResponseBody().getBytes());
                final String resCharset = attackMsg.getResponseBody().getCharset();
                final HttpMessage originalMsg = getBaseMsg();
                if (this.isStop())
                    return;
                if (!isSoapMessage(response, resCharset)) {
                    /*
                     * Response has no SOAP format. It is still notified since it is an unexpected
                     * result.
                     */
                    bingo(Alert.RISK_LOW, Alert.CONFIDENCE_MEDIUM, null, null, finalValue,
                            Constant.messages.getString(MESSAGE_PREFIX + "warn1"), attackMsg);
                } else if (responsesAreEqual(modifiedMsg, attackMsg)
                        && !(responsesAreEqual(originalMsg, modifiedMsg))) {
                    /*
                     * The attack message has achieved the same result as the modified message, so
                     * XML injection attack worked.
                     */
                    bingo(Alert.RISK_HIGH, Alert.CONFIDENCE_MEDIUM, null, null, finalValue,
                            Constant.messages.getString(MESSAGE_PREFIX + "warn2"), attackMsg);
                }
            }
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
    }

    /* Checks whether server response follows a SOAP message format. */
    private boolean isSoapMessage(String content, String charset) {
        SOAPMessage soapMsg = null;
        if (content.length() <= 0)
            return false;
        MessageFactory factory;
        try {
            factory = MessageFactory.newInstance();
            soapMsg = factory.createMessage(new MimeHeaders(),
                    new ByteArrayInputStream(content.getBytes(Charset.forName(charset))));
            /* Content has been parsed correctly as SOAP content. */
            if (soapMsg != null)
                return true;
            else
                return false;
        } catch (Exception e) {
            /*
             * Error when trying to parse as SOAP content. It is considered as a non-SOAP
             * message.
             */
            return false;
        }
    }

    private HttpMessage craftAttackMessage(HttpMessage msg, String paramName, String finalValue) {
        /* Retrieves message configuration to craft a new one. */
        ImportWSDL wsdlSingleton = ImportWSDL.getInstance();
        SOAPMsgConfig soapConfig = wsdlSingleton.getSoapConfig(msg);
        if (soapConfig == null)
            return null;
        /* XML code injection. */
        boolean isParamChanged = soapConfig.changeParam(paramName, finalValue);
        /* Crafts the message. */
        WSDLCustomParser parser = new WSDLCustomParser();
        if (isParamChanged)
            return parser.createSoapRequest(soapConfig);
        else
            return null;
    }

    private boolean responsesAreEqual(HttpMessage original, HttpMessage crafted) {
        final String originalContent = new String(original.getResponseBody().getBytes());
        final String craftedContent = new String(crafted.getResponseBody().getBytes());
        if (originalContent.equals(craftedContent))
            return true;
        else
            return false;
    }

    @Override
    public int getRisk() {
        return Alert.RISK_HIGH;
    }

    @Override
    public int getCweId() {
        // The CWE id
        return 0;
    }

    @Override
    public int getWascId() {
        // The WASC ID
        return 0;
    }
}