org.zaproxy.zap.extension.ascanrules.SourceCodeDisclosureWEBINF.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.ascanrules.SourceCodeDisclosureWEBINF.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.ascanrules;

import com.strobel.decompiler.Decompiler;
import com.strobel.decompiler.DecompilerSettings;
import com.strobel.decompiler.PlainTextOutput;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.lang.SystemUtils;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.scanner.AbstractHostPlugin;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.model.Vulnerabilities;
import org.zaproxy.zap.model.Vulnerability;

/**
 * a scanner that looks for Java classes disclosed via the WEB-INF folder and that decompiles them
 * to give the Java source code. The scanner also looks for easy pickings in the form of properties
 * files loaded by the Java class.
 *
 * @author 70pointer
 */
public class SourceCodeDisclosureWEBINF extends AbstractHostPlugin {

    // TODO: for imported classes that we do not find in the classes folder, map to jar file names,
    // which we might find in WEB-INF/lib/ ?
    // TODO: pull referenced properties files from WEB-INF?

    /** the set of files that commonly occur in the WEB-INF folder */
    private static final List<String> WEBINF_FILES = new LinkedList<String>(
            Arrays.asList(new String[] { "web.xml", "applicationContext.xml" // for Spring
            }));
    /**
     * match on Java class names (including the package info) we're "flexible" on the package names
     * and class names containing uppercase versus lowercase, by necessity.
     */
    private static final Pattern JAVA_CLASSNAME_PATTERN = Pattern.compile("[0-9a-zA-Z_.]+\\.[a-zA-Z0-9_]+");

    /** match on imports in the decompiled Java source, to find the names of more classes to pull */
    private static final Pattern JAVA_IMPORT_CLASSNAME_PATTERN = Pattern
            .compile("^import\\s+([0-9a-zA-Z_.]+\\.[a-zA-Z0-9_]+);", Pattern.MULTILINE);

    /** find references to properties files in the Java source code */
    private static final Pattern PROPERTIES_FILE_PATTERN = Pattern.compile("\"([/a-zA-Z0-9_-]+.properties)\"");

    /**
     * details of the vulnerability which we are attempting to find 34 = Predictable Resource
     * Location
     */
    private static final Vulnerability vuln = Vulnerabilities.getVulnerability("wasc_34");

    /** the logger object */
    private static final Logger log = Logger.getLogger(SourceCodeDisclosureWEBINF.class);

    /** returns the plugin id */
    @Override
    public int getId() {
        return 10045;
    }

    /** returns the name of the plugin */
    @Override
    public String getName() {
        return Constant.messages.getString("ascanrules.sourcecodedisclosurewebinf.name");
    }

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

    @Override
    public String getDescription() {
        if (vuln != null) {
            return vuln.getDescription();
        }
        return "Failed to load vulnerability description from file";
    }

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

    @Override
    public String getSolution() {
        if (vuln != null) {
            return vuln.getSolution();
        }
        return "Failed to load vulnerability solution from file";
    }

    @Override
    public String getReference() {
        if (vuln != null) {
            StringBuilder sb = new StringBuilder();
            for (String ref : vuln.getReferences()) {
                if (sb.length() > 0) {
                    sb.append('\n');
                }
                sb.append(ref);
            }
            return sb.toString();
        }
        return "Failed to load vulnerability reference from file";
    }

    @Override
    public void init() {
        // Does not work with Java 9+
        // https://github.com/zaproxy/zaproxy/issues/4038
        if (SystemUtils.isJavaVersionAtLeast(1.9f)) {
            getParent().pluginSkipped(this,
                    Constant.messages.getString("ascanrules.sourcecodedisclosurewebinf.skipJava9"));
        }
    }

    @Override
    public void scan() {
        try {
            URI originalURI = getBaseMsg().getRequestHeader().getURI();
            List<String> javaClassesFound = new LinkedList<String>();
            List<String> javaClassesHandled = new LinkedList<String>();

            // Pass 1: thru each of the WEB-INF files, looking for class names
            for (String filename : WEBINF_FILES) {

                HttpMessage webinffilemsg = new HttpMessage(new URI(
                        originalURI.getScheme() + "://" + originalURI.getAuthority() + "/WEB-INF/" + filename,
                        true));
                sendAndReceive(webinffilemsg, false); // do not follow redirects
                String body = new String(webinffilemsg.getResponseBody().getBytes());
                Matcher matcher = JAVA_CLASSNAME_PATTERN.matcher(body);
                while (matcher.find()) {
                    // we have a possible class *name*.
                    // Next: See if the class file lives in the expected location in the WEB-INF
                    // folder
                    // skip Java built-in classes
                    String classname = matcher.group();
                    if (!classname.startsWith("java.") && !classname.startsWith("javax.")
                            && !javaClassesFound.contains(classname)) {
                        javaClassesFound.add(classname);
                    }
                }
            }

            // for each class name found, try download the actual class file..
            // for ( String classname: javaClassesFound) {
            while (javaClassesFound.size() > 0) {
                String classname = javaClassesFound.get(0);
                URI classURI = getClassURI(originalURI, classname);
                if (log.isDebugEnabled())
                    log.debug("Looking for Class file: " + classURI.getURI());

                HttpMessage classfilemsg = new HttpMessage(classURI);
                sendAndReceive(classfilemsg, false); // do not follow redirects
                if (classfilemsg.getResponseHeader().getStatusCode() == HttpStatus.SC_OK) {
                    // to decompile the class file, we need to write it to disk..
                    // under the current version of the library, at least
                    File classFile = null;
                    try {
                        classFile = File.createTempFile("zap", ".class");
                        classFile.deleteOnExit();
                        OutputStream fos = new FileOutputStream(classFile);
                        fos.write(classfilemsg.getResponseBody().getBytes());
                        fos.close();

                        // now decompile it
                        DecompilerSettings decompilerSettings = new DecompilerSettings();

                        // set some options so that we can better parse the output, to get the names
                        // of more classes..
                        decompilerSettings.setForceExplicitImports(true);
                        decompilerSettings.setForceExplicitTypeArguments(true);

                        PlainTextOutput decompiledText = new PlainTextOutput();
                        Decompiler.decompile(classFile.getAbsolutePath(), decompiledText, decompilerSettings);
                        String javaSourceCode = decompiledText.toString();

                        if (javaSourceCode.startsWith("!!! ERROR: Failed to load class")) {
                            // Not a Java class file...
                            javaClassesFound.remove(classname);
                            javaClassesHandled.add(classname);
                            continue;
                        }

                        if (log.isDebugEnabled()) {
                            log.debug("Source Code Disclosure alert for: " + classname);
                        }

                        // bingo.
                        bingo(Alert.RISK_HIGH, Alert.CONFIDENCE_MEDIUM,
                                Constant.messages.getString("ascanrules.sourcecodedisclosurewebinf.name"),
                                Constant.messages.getString("ascanrules.sourcecodedisclosurewebinf.desc"), null, // originalMessage.getRequestHeader().getURI().getURI(),
                                null, // parameter being attacked: none.
                                "", // attack
                                javaSourceCode, // extrainfo
                                Constant.messages.getString("ascanrules.sourcecodedisclosurewebinf.soln"), "", // evidence, highlighted in the message
                                classfilemsg // raise the alert on the classfile, rather than on the
                        // web.xml (or other file where the class reference was
                        // found).
                        );

                        // and add the referenced classes to the list of classes to look for!
                        // so that we catch as much source code as possible.
                        Matcher importMatcher = JAVA_IMPORT_CLASSNAME_PATTERN.matcher(javaSourceCode);
                        while (importMatcher.find()) {
                            // we have another possible class name.
                            // Next: See if the class file lives in the expected location in the
                            // WEB-INF folder
                            String importClassname = importMatcher.group(1);

                            if ((!javaClassesFound.contains(importClassname))
                                    && (!javaClassesHandled.contains(importClassname))) {
                                javaClassesFound.add(importClassname);
                            }
                        }

                        // attempt to find properties files within the Java source, and try get them
                        Matcher propsFileMatcher = PROPERTIES_FILE_PATTERN.matcher(javaSourceCode);
                        while (propsFileMatcher.find()) {
                            String propsFilename = propsFileMatcher.group(1);
                            if (log.isDebugEnabled())
                                log.debug("Found props file: " + propsFilename);

                            URI propsFileURI = getPropsFileURI(originalURI, propsFilename);
                            HttpMessage propsfilemsg = new HttpMessage(propsFileURI);
                            sendAndReceive(propsfilemsg, false); // do not follow redirects
                            if (propsfilemsg.getResponseHeader().getStatusCode() == HttpStatus.SC_OK) {
                                // Holy sheet.. we found a properties file
                                bingo(Alert.RISK_HIGH, Alert.CONFIDENCE_MEDIUM,
                                        Constant.messages.getString(
                                                "ascanrules.sourcecodedisclosurewebinf.propertiesfile.name"),
                                        Constant.messages.getString(
                                                "ascanrules.sourcecodedisclosurewebinf.propertiesfile.desc"),
                                        null, // originalMessage.getRequestHeader().getURI().getURI(),
                                        null, // parameter being attacked: none.
                                        "", // attack
                                        Constant.messages.getString(
                                                "ascanrules.sourcecodedisclosurewebinf.propertiesfile.extrainfo",
                                                classURI), // extrainfo
                                        Constant.messages.getString(
                                                "ascanrules.sourcecodedisclosurewebinf.propertiesfile.soln"),
                                        "", // evidence, highlighted in the message
                                        propsfilemsg);
                            }
                        }
                        // do not return at this point.. there may be multiple classes referenced.
                        // We want to see as many of them as possible.
                    } finally {
                        // delete the temp file.
                        // this will be deleted when the VM is shut down anyway, but just in case!
                        if (classFile != null)
                            classFile.delete();
                    }
                }
                // remove the class from the set to handle, and add it to the list of classes
                // handled
                javaClassesFound.remove(classname);
                javaClassesHandled.add(classname);
            }
        } catch (Exception e) {
            log.error("Error scanning a Host for Source Code Disclosure via the WEB-INF folder: " + e.getMessage(),
                    e);
        }
    }

    /**
     * gets a candidate URI for a given class path.
     *
     * @param classname
     * @return
     * @throws URIException
     */
    private URI getClassURI(URI hostURI, String classname) throws URIException {
        return new URI(hostURI.getScheme() + "://" + hostURI.getAuthority() + "/WEB-INF/classes/"
                + classname.replaceAll("\\.", "/") + ".class", false);
    }

    private URI getPropsFileURI(URI hostURI, String propsfilename) throws URIException {
        return new URI(hostURI.getScheme() + "://" + hostURI.getAuthority() + "/WEB-INF/classes/" + propsfilename,
                false);
    }

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

    @Override
    public int getCweId() {
        return 541; // Information Exposure Through Include Source Code
    }

    @Override
    public int getWascId() {
        return 34; // Predictable Resource Location
    }
}