Example usage for org.apache.commons.httpclient URI getAuthority

List of usage examples for org.apache.commons.httpclient URI getAuthority

Introduction

In this page you can find the example usage for org.apache.commons.httpclient URI getAuthority.

Prototype

public String getAuthority() throws URIException 

Source Link

Document

Get the authority.

Usage

From source file:com.limegroup.gnutella.licenses.LicenseFactory.java

/** Gets a CC license URI from the given license string. */
private static URI getCCLicenseURI(String license) {
    // find where the URL should begin.
    int verifyAt = license.indexOf(CCConstants.URL_INDICATOR);
    if (verifyAt == -1)
        return null;

    int urlStart = verifyAt + CCConstants.URL_INDICATOR.length();
    if (urlStart >= license.length())
        return null;

    String url = license.substring(urlStart).trim();
    URI uri = null;
    try {/*from w w w  .  ja v  a  2s  .com*/
        uri = new URI(url.toCharArray());

        // Make sure the scheme is HTTP.
        String scheme = uri.getScheme();
        if (scheme == null || !scheme.equalsIgnoreCase("http"))
            throw new URIException("Invalid scheme: " + scheme);
        // Make sure the scheme has some authority.
        String authority = uri.getAuthority();
        if (authority == null || authority.equals("") || authority.indexOf(' ') != -1)
            throw new URIException("Invalid authority: " + authority);

    } catch (URIException e) {
        uri = null;
        LOG.error("Unable to create URI", e);
    }

    return uri;
}

From source file:org.zaproxy.zap.extension.ascanrules.SourceCodeDisclosureWEBINF.java

@Override
public void scan() {
    try {/*from  w  w  w.j  a  v  a 2s .co m*/
        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);
    }
}

From source file:org.zaproxy.zap.extension.ascanrules.SourceCodeDisclosureWEBINF.java

/**
 * gets a candidate URI for a given class path.
 *
 * @param classname//  w  w w. j av a  2  s.  c o  m
 * @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);
}

From source file:org.zaproxy.zap.extension.ascanrules.SourceCodeDisclosureWEBINF.java

private URI getPropsFileURI(URI hostURI, String propsfilename) throws URIException {
    return new URI(hostURI.getScheme() + "://" + hostURI.getAuthority() + "/WEB-INF/classes/" + propsfilename,
            false);//ww  w  . ja va2  s .  c o  m
}

From source file:org.zaproxy.zap.extension.ascanrules.wpscan.java

@Override
public void scan() {
    try {/*from www.ja  v  a  2  s  .c  om*/
        URI originalURI = this.getBaseMsg().getRequestHeader().getURI();
        String target = originalURI.getScheme() + "://" + originalURI.getAuthority();
        log.info("Starting wpscan... Target is " + target);
        log.info(target);
        ProcessBuilder builder = new ProcessBuilder("/usr/bin/wpscan", "--url", target);
        builder.redirectErrorStream(true);
        Process process = builder.start();

        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

        String fileExistsPatternString = "^\\[31m\\[!\\]\\[0m The WordPress '(.*)' file exists$";
        Pattern fileExistsPattern = Pattern.compile(fileExistsPatternString, Pattern.MULTILINE);

        String backupExistsPatternString = "^\\[31m\\[!\\]\\[0m A (.*) backup file has been found in: '(.*)'$";
        Pattern backupExistsPattern = Pattern.compile(backupExistsPatternString, Pattern.MULTILINE);

        String vulnWithReferencePatternString = "^\\[31m\\[!\\]\\[0m(?:Title:)? (.*)\n    Reference: (.*)";
        Pattern vulnWithReferencePattern = Pattern.compile(vulnWithReferencePatternString, Pattern.MULTILINE);

        String vulnPatternString = "^\\[31m\\[!\\]\\[0m(?: Title\\:)? (.*)";
        Pattern vulnPattern = Pattern.compile(vulnPatternString);

        String blockDelimiterPatternString = ".*\n\n.*";
        Pattern blockDelimiterPattern = Pattern.compile(blockDelimiterPatternString, Pattern.MULTILINE);

        String line;
        String buffer = "";
        while ((line = reader.readLine()) != null) {
            buffer += line + "\n";

            Matcher blockDelimiterMatcher = blockDelimiterPattern.matcher(buffer);

            if (blockDelimiterMatcher.find()) { // new block, process it.
                Matcher fileExistsMatcher = fileExistsPattern.matcher(buffer);
                Matcher backupExistsMatcher = backupExistsPattern.matcher(buffer);
                Matcher vulnWithReferenceMatcher = vulnWithReferencePattern.matcher(buffer);
                Matcher vulnMatcher = vulnPattern.matcher(buffer);

                while (fileExistsMatcher.find()) {
                    bingo(Alert.RISK_INFO, Alert.WARNING, "WordPress installation file",
                            "A WordPress installation is present on the server.", fileExistsMatcher.group(1),
                            "", fileExistsMatcher.group(1), "",
                            "These files should be removed from the server upon installation.",
                            "Remove installation files.", getNewMsg());
                }
                while (backupExistsMatcher.find()) {
                    bingo(Alert.RISK_HIGH, Alert.WARNING,
                            "WordPress " + backupExistsMatcher.group(1) + " backup file",
                            "A WordPress configuration back file is present on the server.",
                            backupExistsMatcher.group(2), "", backupExistsMatcher.group(2), "",
                            "Backup files should not be accessible from the web server.",
                            "Backup your files at a safe place that is not acessible from the web server.",
                            getNewMsg());
                }
                if (vulnWithReferenceMatcher.find()) {
                    bingo(Alert.RISK_HIGH, Alert.WARNING, vulnWithReferenceMatcher.group(1),
                            "Wordpress Vulnerability.", vulnWithReferenceMatcher.group(1), "",
                            vulnWithReferenceMatcher.group(1), vulnWithReferenceMatcher.group(2), "", ".",
                            getNewMsg());
                } else if (vulnMatcher.find()) {
                    bingo(Alert.RISK_HIGH, Alert.WARNING, vulnMatcher.group(1), "Wordpress Vulnerability.",
                            vulnMatcher.group(1), "", vulnMatcher.group(1), "", "", ".", getNewMsg());
                }

                buffer = ""; //clear buffer
            }
        }

    } catch (Exception e) {
        log.info("Error" + e.getMessage());
    }
}

From source file:org.zaproxy.zap.extension.ascanrulesAlpha.BackupFileDisclosure.java

/**
 * attempts to find a backup file for the given file
 * @param uri the URI of a file/*from w ww .j  ava  2  s. c o  m*/
 * @return 
 */
private void findBackupFile(HttpMessage originalMessage) throws Exception {

    try {
        boolean gives404s = true;
        boolean parentgives404s = true;
        byte[] nonexistparentmsgdata = null;

        URI originalURI = originalMessage.getRequestHeader().getURI();

        //request a file in the same directory to see how it handles "File not found". Using a 404? Something else?
        String temppath = originalURI.getPath();
        if (temppath == null)
            temppath = "";
        int slashposition = temppath.lastIndexOf("/");
        if (slashposition < 0) {
            //WTF? there was no slash in the path..
            throw new Exception("The message has a path with a malformed path component");
        }
        String filename = originalMessage.getRequestHeader().getURI().getName();

        String randomfilename = RandomStringUtils.random(filename.length(),
                "abcdefghijklmoopqrstuvwxyz9123456789");
        String randomfilepath = temppath.substring(0, slashposition) + "/" + randomfilename;

        if (log.isDebugEnabled())
            log.debug("Trying non-existent file: " + randomfilepath);
        HttpMessage nonexistfilemsg = new HttpMessage(
                new URI(originalURI.getScheme(), originalURI.getAuthority(), randomfilepath, null, null));
        try {
            nonexistfilemsg.setCookieParams(originalMessage.getCookieParams());
        } catch (Exception e) {
            if (log.isDebugEnabled())
                log.debug("Could not set the cookies from the base request:" + e);
        }
        sendAndReceive(nonexistfilemsg, false);
        byte[] nonexistfilemsgdata = nonexistfilemsg.getResponseBody().getBytes();
        //does the server give a 404 for a non-existent file? 
        if (nonexistfilemsg.getResponseHeader().getStatusCode() != HttpStatus.SC_NOT_FOUND) {
            gives404s = false;
            if (log.isDebugEnabled())
                log.debug("The server does not return a 404 status for a non-existent path: "
                        + nonexistfilemsg.getRequestHeader().getURI().getURI());
        } else {
            gives404s = true;
            if (log.isDebugEnabled())
                log.debug("The server gives a 404 status for a non-existent path: "
                        + nonexistfilemsg.getRequestHeader().getURI().getURI());
        }

        //now request a different (and non-existent) parent directory, 
        //to see whether a non-existent parent folder causes a 404 
        String[] pathbreak = temppath.split("/");
        if (pathbreak.length > 2) { //the file has a parent folder that is not the root folder (ie, there is a parent folder to mess with)
            String[] temppathbreak = pathbreak;
            String parentfoldername = pathbreak[pathbreak.length - 2];
            String randomparentfoldername = RandomStringUtils.random(parentfoldername.length(),
                    "abcdefghijklmoopqrstuvwxyz9123456789");

            //replace the parent folder name with the random one, and build it back into a string
            temppathbreak[pathbreak.length - 2] = randomparentfoldername;
            String randomparentpath = StringUtils.join(temppathbreak, "/");

            if (log.isDebugEnabled())
                log.debug("Trying non-existent parent path: " + randomparentpath);
            HttpMessage nonexistparentmsg = new HttpMessage(
                    new URI(originalURI.getScheme(), originalURI.getAuthority(), randomparentpath, null, null));
            try {
                nonexistparentmsg.setCookieParams(originalMessage.getCookieParams());
            } catch (Exception e) {
                if (log.isDebugEnabled())
                    log.debug("Could not set the cookies from the base request:" + e);
            }
            sendAndReceive(nonexistparentmsg, false);
            nonexistparentmsgdata = nonexistparentmsg.getResponseBody().getBytes();
            //does the server give a 404 for a non-existent parent folder? 
            if (nonexistparentmsg.getResponseHeader().getStatusCode() != HttpStatus.SC_NOT_FOUND) {
                parentgives404s = false;
                if (log.isDebugEnabled())
                    log.debug("The server does not return a 404 status for a non-existent parent path: "
                            + nonexistparentmsg.getRequestHeader().getURI().getURI());
            } else {
                parentgives404s = true;
                if (log.isDebugEnabled())
                    log.debug("The server gives a 404 status for a non-existent parent path: "
                            + nonexistparentmsg.getRequestHeader().getURI().getURI());
            }
        }

        String actualfilename = originalURI.getName();
        String actualfileExtension = null;
        String path = originalURI.getPath();
        if (path == null)
            path = "";

        //record the position of the various injection points, always relative to the full path         
        int positionExtensionInjection = 0;
        int positionFileSuffixInjection = 0;
        if (actualfilename.contains(".")) {
            positionExtensionInjection = path.lastIndexOf(".");
            positionFileSuffixInjection = positionExtensionInjection;
            actualfileExtension = actualfilename.substring(actualfilename.lastIndexOf("."));
        } else {
            positionExtensionInjection = path.length();
            positionFileSuffixInjection = path.length();
            actualfileExtension = "";
        }
        int positionFilePrefixInjection = path.lastIndexOf("/") + 1;
        int positionDirectorySuffixInjection = path.lastIndexOf("/");
        int positionDirectoryPrefixInjection = 0;
        if (positionDirectorySuffixInjection >= 0)
            positionDirectoryPrefixInjection = path.substring(0, positionDirectorySuffixInjection)
                    .lastIndexOf("/") + 1;

        //the set of files we will try, in the order of insertion
        Set<URI> candidateBackupFileURIs = new LinkedHashSet<URI>();
        Set<URI> candidateBackupFileChangedFolderURIs = new LinkedHashSet<URI>(); //for a changed parent folder name, which we need to handle separately 

        log.debug("The path is " + path);

        //for each file extension to try (both appending, and replacing)
        int counted = 0;
        for (String fileExtensionToTry : fileExtensions) {
            //to append, inject the file extension at the end of the path  
            String candidateBackupFilePath = path + fileExtensionToTry;
            log.debug("File Extension (append): '" + candidateBackupFilePath + "'");
            candidateBackupFileURIs.add(new URI(originalURI.getScheme(), originalURI.getAuthority(),
                    candidateBackupFilePath, null, null));

            //to replace the extension, append the file extension at positionExtensionInjection
            candidateBackupFilePath = path.substring(0, positionExtensionInjection) + fileExtensionToTry;
            log.debug("File Extension (replace): '" + candidateBackupFilePath + "'");
            candidateBackupFileURIs.add(new URI(originalURI.getScheme(), originalURI.getAuthority(),
                    candidateBackupFilePath, null, null));

            //to switch the extension (if there was one), append the file extension at positionExtensionInjection
            if (!actualfileExtension.equals("") && doSwitchFileExtension) {
                candidateBackupFilePath = path.substring(0, positionExtensionInjection) + fileExtensionToTry
                        + actualfileExtension;
                log.debug("File Extension (switch): '" + candidateBackupFilePath + "'");
                candidateBackupFileURIs.add(new URI(originalURI.getScheme(), originalURI.getAuthority(),
                        candidateBackupFilePath, null, null));
            }
            counted++;
            if (counted > numExtensionsToTry) {
                break; //out of the loop.
            }
        }

        //for each file suffix to try
        counted = 0;
        for (String fileSuffixToTry : fileSuffixes) {
            //inject the file suffix at positionFileSuffixInjection
            String candidateBackupFilePath = path.substring(0, positionFileSuffixInjection) + fileSuffixToTry
                    + (positionFileSuffixInjection >= path.length() ? ""
                            : path.substring(positionFileSuffixInjection));
            log.debug("File Suffix (insert): '" + candidateBackupFilePath + "'");
            candidateBackupFileURIs.add(new URI(originalURI.getScheme(), originalURI.getAuthority(),
                    candidateBackupFilePath, null, null));
            counted++;
            if (counted > numSuffixesToTry) {
                break; //out of the loop.
            }
        }

        //for each file prefix to try
        counted = 0;
        for (String filePrefixToTry : filePrefixes) {
            //inject the file prefix at positionFilePrefixInjection
            String candidateBackupFilePath = path.substring(0, positionFilePrefixInjection) + filePrefixToTry
                    + (positionFilePrefixInjection >= path.length() ? ""
                            : path.substring(positionFilePrefixInjection));
            log.debug("File Prefix (insert): '" + candidateBackupFilePath + "'");
            candidateBackupFileURIs.add(new URI(originalURI.getScheme(), originalURI.getAuthority(),
                    candidateBackupFilePath, null, null));
            counted++;
            if (counted > numPrefixesToTry) {
                break; //out of the loop.
            }
        }

        //for each directory suffix/prefix to try (using the file prefixes/suffixes - or whatever the plural of prefix/suffix is)         
        counted = 0;
        if (pathbreak.length > 2) {
            //if there is a a parent folder to play with 
            for (String fileSuffixToTry : fileSuffixes) {
                //inject the directory suffix at positionDirectorySuffixInjection
                String candidateBackupFilePath = path.substring(0, positionDirectorySuffixInjection)
                        + fileSuffixToTry + (positionDirectorySuffixInjection >= path.length() ? ""
                                : path.substring(positionDirectorySuffixInjection));
                log.debug("Directory Suffix (insert): '" + candidateBackupFilePath + "'");
                candidateBackupFileChangedFolderURIs.add(new URI(originalURI.getScheme(),
                        originalURI.getAuthority(), candidateBackupFilePath, null, null));
                counted++;
                if (counted > numSuffixesToTry) {
                    break; //out of the loop.
                }
            }
            for (String filePrefixToTry : filePrefixes) {
                //inject the directory prefix at positionDirectorySuffixInjection
                String candidateBackupFilePath = path.substring(0, positionDirectoryPrefixInjection)
                        + filePrefixToTry + (positionDirectoryPrefixInjection >= path.length() ? ""
                                : path.substring(positionDirectoryPrefixInjection));
                log.debug("Directory Suffix (insert): '" + candidateBackupFilePath + "'");
                candidateBackupFileChangedFolderURIs.add(new URI(originalURI.getScheme(),
                        originalURI.getAuthority(), candidateBackupFilePath, null, null));
                counted++;
                if (counted > numSuffixesToTry) {
                    break; //out of the loop.
                }
            }
        }

        //now we have a set of candidate URIs appropriate to the attack strength chosen by the user
        //try each candidate URI in turn.
        for (URI candidateBackupFileURI : candidateBackupFileURIs) {
            byte[] disclosedData = {};
            if (log.isDebugEnabled())
                log.debug("Trying possible backup file path: " + candidateBackupFileURI.getURI());
            HttpMessage requestmsg = new HttpMessage(candidateBackupFileURI);
            try {
                requestmsg.setCookieParams(originalMessage.getCookieParams());
            } catch (Exception e) {
                if (log.isDebugEnabled())
                    log.debug("Could not set the cookies from the base request:" + e);
            }
            //Do not follow redirects. They're evil. Yep.
            sendAndReceive(requestmsg, false);
            disclosedData = requestmsg.getResponseBody().getBytes();
            int requestStatusCode = requestmsg.getResponseHeader().getStatusCode();

            //just to complicate things.. I have a test case which for the random file, does NOT give a 404 (so gives404s == false) 
            //but for a "Copy of" file, actually gives a 404 (for some unknown reason). We need to handle this case.
            if ((gives404s && requestStatusCode != HttpStatus.SC_NOT_FOUND)
                    || ((!gives404s) && requestStatusCode != HttpStatus.SC_NOT_FOUND
                            && (!Arrays.equals(disclosedData, nonexistfilemsgdata)))) {
                bingo(Alert.RISK_MEDIUM, Alert.WARNING,
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.name"),
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.desc"),
                        requestmsg.getRequestHeader().getURI().getURI(), // originalMessage.getRequestHeader().getURI().getURI(),
                        null, //parameter being attacked: none.
                        candidateBackupFileURI.getURI(), //attack
                        originalMessage.getRequestHeader().getURI().getURI(), //new String (disclosedData),  //extrainfo
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.soln"),
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.evidence", originalURI,
                                candidateBackupFileURI.getURI()),
                        requestmsg //originalMessage
                );
            }

            if (isStop()) {
                if (log.isDebugEnabled())
                    log.debug("The scanner was stopped in response to a user request");
                return;
            }
        }

        //now try the changed parent folders (if any)
        //the logic here needs to check using the parent 404 logic, and the output for a non-existent parent folder.
        for (URI candidateBackupFileURI : candidateBackupFileChangedFolderURIs) {
            byte[] disclosedData = {};
            if (log.isDebugEnabled())
                log.debug("Trying possible backup file path (with changed parent folder): "
                        + candidateBackupFileURI.getURI());
            HttpMessage requestmsg = new HttpMessage(candidateBackupFileURI);
            try {
                requestmsg.setCookieParams(originalMessage.getCookieParams());
            } catch (Exception e) {
                if (log.isDebugEnabled())
                    log.debug("Could not set the cookies from the base request:" + e);
            }
            //Do not follow redirects. They're evil. Yep.
            sendAndReceive(requestmsg, false);
            disclosedData = requestmsg.getResponseBody().getBytes();
            int requestStatusCode = requestmsg.getResponseHeader().getStatusCode();

            if ((parentgives404s && requestStatusCode != HttpStatus.SC_NOT_FOUND)
                    || ((!parentgives404s) && requestStatusCode != HttpStatus.SC_NOT_FOUND
                            && (!Arrays.equals(disclosedData, nonexistparentmsgdata)))) {
                bingo(Alert.RISK_MEDIUM, Alert.WARNING,
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.name"),
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.desc"),
                        requestmsg.getRequestHeader().getURI().getURI(), //originalMessage.getRequestHeader().getURI().getURI(),
                        null, //parameter being attacked: none.
                        candidateBackupFileURI.getURI(), //attack
                        originalMessage.getRequestHeader().getURI().getURI(), //new String (disclosedData),  //extrainfo
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.soln"),
                        Constant.messages.getString("ascanalpha.backupfiledisclosure.evidence", originalURI,
                                candidateBackupFileURI.getURI()),
                        requestmsg //originalMessage
                );
            }

            if (isStop()) {
                if (log.isDebugEnabled())
                    log.debug("The scanner was stopped in response to a user request");
                return;
            }
        }

    } catch (Exception e) {
        e.printStackTrace();
        log.error("Some error occurred when looking for a backup file for '"
                + originalMessage.getRequestHeader().getURI() + "': " + e);
        return;
    }

}

From source file:org.zaproxy.zap.extension.ascanrulesAlpha.CrossDomainScanner.java

/**
 * scans the node for cross-domain mis-configurations
 *//*from  w  ww .  j a va  2 s . com*/
@Override
public void scan() {

    try {
        //get the network details for the attack
        URI originalURI = this.getBaseMsg().getRequestHeader().getURI();

        //retrieve the Adobe cross domain policy file, and assess it
        HttpMessage crossdomainmessage = new HttpMessage(new URI(originalURI.getScheme(),
                originalURI.getAuthority(), "/" + ADOBE_CROSS_DOMAIN_POLICY_FILE, null, null));
        sendAndReceive(crossdomainmessage, false);
        byte[] crossdomainmessagebytes = crossdomainmessage.getResponseBody().getBytes();

        //parse the file. If it's not parseable, it might have been because of a 404
        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        ;
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        ;
        XPath xpath = (XPath) XPathFactory.newInstance().newXPath();

        try {
            //work around the "no protocol" issue by wrapping the content in a ByteArrayInputStream
            Document adobeXmldoc = docBuilder
                    .parse(new InputSource(new ByteArrayInputStream(crossdomainmessagebytes)));

            //check for cross domain read (data load) access
            XPathExpression exprAllowAccessFromDomain = xpath
                    .compile("/cross-domain-policy/allow-access-from/@domain"); //gets the domain attributes
            NodeList exprAllowAccessFromDomainNodes = (NodeList) exprAllowAccessFromDomain.evaluate(adobeXmldoc,
                    XPathConstants.NODESET);
            for (int i = 0; i < exprAllowAccessFromDomainNodes.getLength(); i++) {
                String domain = exprAllowAccessFromDomainNodes.item(i).getNodeValue();
                if (domain.equals("*")) {
                    //oh dear me.
                    if (log.isInfoEnabled())
                        log.info("Bingo!  <allow-access-from domain=\"*\"");
                    bingo(getRisk(), Alert.WARNING,
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE_READ + "name"),
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE + "desc"),
                            crossdomainmessage.getRequestHeader().getURI().getURI(), //the url field 
                            "", //parameter being attacked: none.
                            "", //attack
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE_READ + "extrainfo",
                                    "/" + ADOBE_CROSS_DOMAIN_POLICY_FILE), //extrainfo
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE_READ + "soln"), //solution
                            "<allow-access-from domain=\"*\"", // evidence
                            crossdomainmessage //the message on which to place the alert
                    );
                }
            }
            //check for cross domain send (upload) access
            XPathExpression exprRequestHeadersFromDomain = xpath
                    .compile("/cross-domain-policy/allow-http-request-headers-from/@domain"); //gets the domain attributes
            NodeList exprRequestHeadersFromDomainNodes = (NodeList) exprRequestHeadersFromDomain
                    .evaluate(adobeXmldoc, XPathConstants.NODESET);
            for (int i = 0; i < exprRequestHeadersFromDomainNodes.getLength(); i++) {
                String domain = exprRequestHeadersFromDomainNodes.item(i).getNodeValue();
                if (domain.equals("*")) {
                    //oh dear, dear me.
                    if (log.isInfoEnabled())
                        log.info("Bingo!  <allow-http-request-headers-from domain=\"*\"");
                    bingo(getRisk(), Alert.WARNING,
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE_SEND + "name"),
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE + "desc"),
                            crossdomainmessage.getRequestHeader().getURI().getURI(), //the url field 
                            "", //parameter being attacked: none.
                            "", //attack
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE_SEND + "extrainfo",
                                    "/" + ADOBE_CROSS_DOMAIN_POLICY_FILE), //extrainfo
                            Constant.messages.getString(MESSAGE_PREFIX_ADOBE_SEND + "soln"), //solution
                            "<allow-http-request-headers-from domain=\"*\"", // evidence
                            crossdomainmessage //the message on which to place the alert
                    );
                }
            }
        } catch (SAXException | IOException e) {
            log.error("An error occurred trying to parse " + ADOBE_CROSS_DOMAIN_POLICY_FILE + " as XML: " + e);
        }

        //retrieve the Silverlight client access policy file, and assess it.
        HttpMessage clientaccesspolicymessage = new HttpMessage(new URI(originalURI.getScheme(),
                originalURI.getAuthority(), "/" + SILVERLIGHT_CROSS_DOMAIN_POLICY_FILE, null, null));
        sendAndReceive(clientaccesspolicymessage, false);
        byte[] clientaccesspolicymessagebytes = clientaccesspolicymessage.getResponseBody().getBytes();

        //parse the file. If it's not parseable, it might have been because of a 404         
        try {
            //work around the "no protocol" issue by wrapping the content in a ByteArrayInputStream
            Document silverlightXmldoc = docBuilder
                    .parse(new InputSource(new ByteArrayInputStream(clientaccesspolicymessagebytes)));
            XPathExpression exprAllowFromUri = xpath
                    .compile("/access-policy/cross-domain-access/policy/allow-from/domain/@uri"); //gets the uri attributes
            //check the "allow-from" policies
            NodeList exprAllowFromUriNodes = (NodeList) exprAllowFromUri.evaluate(silverlightXmldoc,
                    XPathConstants.NODESET);
            for (int i = 0; i < exprAllowFromUriNodes.getLength(); i++) {
                String uri = exprAllowFromUriNodes.item(i).getNodeValue();
                if (uri.equals("*")) {
                    //tut, tut, tut.
                    if (log.isInfoEnabled())
                        log.info("Bingo! " + SILVERLIGHT_CROSS_DOMAIN_POLICY_FILE
                                + ", at /access-policy/cross-domain-access/policy/allow-from/domain/@uri");
                    bingo(getRisk(), Alert.WARNING,
                            Constant.messages.getString(MESSAGE_PREFIX_SILVERLIGHT + "name"),
                            Constant.messages.getString(MESSAGE_PREFIX_SILVERLIGHT + "desc"),
                            clientaccesspolicymessage.getRequestHeader().getURI().getURI(), //the url field 
                            "", //parameter being attacked: none.
                            "", //attack
                            Constant.messages.getString(MESSAGE_PREFIX_SILVERLIGHT + "extrainfo"), //extrainfo
                            Constant.messages.getString(MESSAGE_PREFIX_SILVERLIGHT + "soln"), //solution
                            "<domain uri=\"*\"", // evidence
                            clientaccesspolicymessage //the message on which to place the alert
                    );
                }
            }

        } catch (SAXException | IOException e) {
            log.error("An error occurred trying to parse " + SILVERLIGHT_CROSS_DOMAIN_POLICY_FILE + " as XML: "
                    + e);
        }

    } catch (Exception e) {
        //needed to catch exceptions from the "finally" statement 
        log.error("Error scanning a node for Cross Domain misconfigurations: " + e.getMessage(), e);
    }
}

From source file:org.zaproxy.zap.extension.ascanrulesAlpha.GitMetadata.java

/**
 * get data for a given SHA1 object, using either the loose or packed formats
 *
 * @param basemsg the base message to use when retrieving additional resources
 * @param gitbasepath the Git base path/*from   w w w.  j  ava  2 s . c om*/
 * @param filesha1 the SHA1 associated with the file in Git
 * @param trypacked try the packed format, or try the loose format
 * @return the binary data associated with the file in Git, as specified by the filesha1
 *     parameter
 * @throws Exception
 */
public byte[] getObjectData(HttpMessage basemsg, String gitbasepath, String filesha1, boolean trypacked)
        throws Exception {

    URI originaluri = basemsg.getRequestHeader().getURI();
    if (!trypacked) {
        // try the unpacked (loose) format
        URI gitobjecturi = new URI(originaluri.getScheme(), originaluri.getAuthority(),
                gitbasepath + "objects/" + filesha1.substring(0, 2) + "/" + filesha1.substring(2), null, null);

        if (log.isDebugEnabled())
            log.debug("The internal Git (loose) file name is " + gitobjecturi.getURI());
        byte[] data = getURIResponseBody(gitobjecturi, true, basemsg);

        ByteBuffer dataBuffer = ByteBuffer.wrap(data);
        StringBuilder sb = new StringBuilder();
        while (true) {
            byte b = dataBuffer.get();
            if (b == ' ')
                break;
            sb.append((char) b);
        }
        String objecttype = new String(sb);
        if (!objecttype.equals("blob")) {
            throw new Exception(
                    "The Git 'loose' file '" + gitobjecturi + "' is not of type 'blob': '" + objecttype + "'");
        }
        // read the size of data in the file (which appears as ASCII digits in the text), until
        // we get a 0x00
        sb = new StringBuilder();
        while (true) {
            byte b = dataBuffer.get();
            if (b == 0x00)
                break;
            sb.append((char) b);
        }
        int dataSize = Integer.parseInt(new String(sb));

        // now read that number of bytes from the bytebuffer, or at least attempt to..
        byte[] blobDecoded = new byte[dataSize];
        dataBuffer.get(blobDecoded);
        // that's it. we're done. return the decoded data, which will hopefully be source code
        // :)
        return blobDecoded;
    } else {
        // try the packed format

        // With the Git "packed" format, there are Git "pack index" files, and Git "pack" files.
        // They come as a set. You need both to get the contents of the file you're looking for.
        // The name of the Git "pack" files and "pack index" files is based on the SHA1 sum of
        // the SHA1 objects that it contains, and is not guessable.
        // This is an issue if you do not already know what pack files live in the directory
        // (unless you have a directory listing, for instance).
        // Luckily, in practice, in most cases (although not always) the name of the "pack" file
        // is contained in an ".git/objects/info/packs" file in the Git repo metadata.
        // The ".git/objects/info/packs" can also contain the names of multiple pack files,
        // which I have not seen in practice. That scenario is not currently supported here.

        // Both the "pack" and "pack index" files have an associated version number, but not
        // necessarily the same version number as each other.
        // There are constraints and interdependencies on these version numbers, however.

        // The Git "pack index" file currently comes in versions 1,2, and 3 (as of January 30,
        // 2014).

        // version 1 "pack index" files are not seen in the wild, but can be created using later
        // versions of Git, if necessary.  Version 1 is supported here.
        //            (Version 1 "pack index" files are seen in conjunction with Version 2 "pack" files,
        // but there is no reason (that I know of) why they should not also support Version 3 or
        // 4 pack files).
        // version 2 "pack index" files use either a version 2 or version 3 "pack" file. All
        // these versions are supported here.
        //             (Version 1 and 2 "pack index" file formats have structural differences, but not
        // not wildly dis-similar).
        // version 3 "pack index" file cannot yet be created by any currently known version of
        // Git, but the format is documented.
        //            (Version 3 "pack index" files require a version 4 "pack file". Both these versions
        // are tentatively supported here, although this code has never been tested)

        // The Git "pack" file currently comes in versions 1,2,3, and 4 (as of January 30,
        // 2014).
        // Version 1 "pack" files do not appear to be documented. They are not supported here.
        // Version 2 "pack files" are used with version 2 "pack index" files. This is a common
        // scenario in the wild. Both versions are supported here.
        // Version 3 "pack files" are (also) used with version 2 "pack index" files. Both
        // versions are supported here.
        //           (Version 3 "pack files" are identical in format to version 2, with only the
        // version number differing)
        // Version 4 "pack files" are used in conjunction with version 3 "pack index" files.
        // Both these versions are tentatively supported here, although this code has never been
        // tested.

        // There are also separate version numbers in the Git "index file" (unrelated to the
        // "pack index" files mentioned above), which are probably similarly inter-related.
        // I do not have a mapping of the Git version number (1.7.6 / 1.8.5, for instance) to
        // any of the the internal file version numbers that they create (by default) or
        // support. So sue me.

        URI uri = new URI(originaluri.getScheme(), originaluri.getAuthority(),
                gitbasepath + "objects/info/packs", null, null);

        if (log.isDebugEnabled())
            log.debug("The internal Git file containing the name of the pack file is " + uri);

        byte[] packinfofiledata = null;
        try {
            packinfofiledata = getURIResponseBody(uri, false, basemsg);
        } catch (FileNotFoundException e) {
            log.error("We could not read '" + uri
                    + "' to get the name of the pack file containing the content: " + e.getMessage());
            throw e;
        }
        ByteBuffer dataBuffer = ByteBuffer.wrap(packinfofiledata);
        StringBuilder sb = new StringBuilder();
        while (true) {
            byte b = dataBuffer.get();
            if (b == ' ')
                break;
            sb.append((char) b);
        }
        String objecttype = new String(sb);
        if (!objecttype.equals("P")) {
            throw new Exception("The pack info file is not of type 'P': '" + objecttype + "'");
        }

        // the file should  begin with "P ", and everything after that is the pack file name
        // (and exclude the 2 trailing newlines as well)
        // TODO: handle the case where this file contains the name of multiple pack files.
        // Currently, i have no test cases. Maybe in extremely large Git repositories?
        byte[] packfilenamebytes = new byte[packinfofiledata.length - 4];
        dataBuffer.get(packfilenamebytes);
        String packfilename = new String(packfilenamebytes);
        // validate that the file name looks like "pack*.pack"
        Matcher packfilenamematcher = Pattern.compile("^pack-[0-9a-f]{40}\\.pack$").matcher(packfilename);
        if (!packfilenamematcher.find()) {
            throw new Exception(
                    "The pack file name '" + packfilename + "' does not match the expected pattern");
        }

        // Now generate the full name of the pack file, and the pack index.
        URI packuri = new URI(originaluri.getScheme(), originaluri.getAuthority(),
                gitbasepath + "objects/pack/" + packfilename, null, null);
        URI packindexuri = new URI(originaluri.getScheme(), originaluri.getAuthority(),
                gitbasepath + "objects/pack/" + packfilename.substring(0, packfilename.length() - 5) + ".idx",
                null, null);

        // retrieve the content for the "pack index" file!
        byte[] packfileindexdata = null;
        try {
            packfileindexdata = getURIResponseBody(packindexuri, false, basemsg);
        } catch (FileNotFoundException e) {
            System.out.println("We could not read '" + packindexuri
                    + "', which is necessary to get the packed contents of the SHA1 requested: "
                    + e.getMessage());
            throw e;
        }

        // retrieve the content for the "pack" file!
        byte[] packfiledata = null;
        try {
            packfiledata = getURIResponseBody(packuri, false, basemsg);
        } catch (FileNotFoundException e) {
            System.out.println("We could not read '" + packuri
                    + "', which should contain the packed contents of the SHA1 requested: " + e.getMessage());
            throw e;
        }

        // now that we know we have both the "pack index" and the "pack" (data) file, parse the
        // data
        // first parse out some signature data info from the "pack" file
        ByteBuffer packfileheaderBuffer = ByteBuffer.wrap(packfiledata, 0, 12);
        byte[] packfileheaderSignatureArray = new byte[4]; // 4 bytes
        packfileheaderBuffer.get(packfileheaderSignatureArray);
        if (!new String(packfileheaderSignatureArray).equals("PACK")) {
            throw new Exception("The pack file header does not appear to be valid");
        }
        int packFileVersion = packfileheaderBuffer.getInt(); // 4 bytes
        int packEntryCount = packfileheaderBuffer.getInt(); // 4 bytes

        if (packFileVersion != 2 && packFileVersion != 3 && packFileVersion != 4) {
            throw new Exception(
                    "Only Git Pack File versions 2, 3, and 4 are currently supported. Git Pack File Version "
                            + packFileVersion + " was found. Contact the zaproxy (OWASP Zap) dev team");
        }

        // for pack file version 4, read the SHA1 tables from the "pack" file at this point
        // these used to live in the "pack index" file, in earlier versions.
        // Note: since at this point in time, there is no way to generate a v3 pack index file +
        // v4 pack file
        // so this particular block of code remains hypothetical.  it seems to comply with the
        // documented version 4 "pack" file format, however, and it
        // works for version 2 "pack index" and version 2 "pack" files, which appears to be the
        // most common combination seen in the wild.

        int sha1Index = Integer.MAX_VALUE;
        int packEntryOffsetArray[] = null;
        int packEntryOffsetArrayOrdered[] = null;
        int indexEntryCount = 0;

        if (packFileVersion >= 4) {
            sha1Index = Integer.MAX_VALUE;
            // the tables in the V4 tables in the pack file are variable length, so just grab
            // the data after the main header for now
            ByteBuffer packfileTablesBuffer = ByteBuffer.wrap(packfiledata, 12, packfiledata.length - 12);
            // read the series of 20 byte sha1 entries.
            // ours *should* be in here somewhere.. find it
            // make sure to read *all* of the entries from the file (or else seek to the end of
            // the data), so the parsing logic is not broken.
            // TODO: use a binary search to find this in a more efficient manner

            for (int i = 0; i < packEntryCount; i++) {
                byte[] packTableData = new byte[20];
                packfileTablesBuffer.get(packTableData);
                String packTableSha1 = Hex.encodeHexString(packTableData);
                // TODO: use more efficient byte based comparison to find the SHA1 here (and in
                // similar code in pack index version 2 logic, later..
                if (packTableSha1.equals(filesha1)) {
                    if (log.isDebugEnabled())
                        log.debug("FOUND our SHA1 " + packTableSha1 + " at entry " + i
                                + " in the v4 pack tables");
                    sha1Index = i;

                    // we do not need to "read past" all the entries.
                    break;
                }
            }
        }

        // try to parse the "pack index" as a version 1 "pack index" file, which has a different
        // layout to subsequent versions.
        // use a separate ByteBuffer for this, in case things don't work out (because they
        // probably will not work out) :)
        try {
            ByteBuffer packindexfileV1dataBuffer = ByteBuffer.wrap(packfileindexdata);
            byte packEntrySizeArray[] = new byte[256 * 4];
            packindexfileV1dataBuffer.get(packEntrySizeArray);

            if (
            /*packEntrySizeArray[0]== 0xFF && */
            packEntrySizeArray[1] == 't' && packEntrySizeArray[2] == 'O' && packEntrySizeArray[3] == 'c') {
                // the signature is a non-V1 signature.
                throw new NotV1GitPackIndexFileException();
            }
            // get the last 4 bytes as an int, network order.
            indexEntryCount = (packEntrySizeArray[(255 * 4) + 3] << 0);
            indexEntryCount |= (packEntrySizeArray[(255 * 4) + 2] << 8);
            indexEntryCount |= (packEntrySizeArray[(255 * 4) + 1] << 16);
            indexEntryCount |= (packEntrySizeArray[(255 * 4) + 0] << 24);

            // validate that this matches the number of entries in the "pack" file.
            if (indexEntryCount != packEntryCount) {
                throw new Exception("The entry count (" + indexEntryCount
                        + ") from the version 1 pack index file does not match the entry count ("
                        + packEntryCount + ") from the pack file ");
            }
            if (log.isDebugEnabled())
                log.debug("Got a pack index entry count of " + indexEntryCount
                        + " from the version 1 pack index file");

            // read the indexEntryCount * (4+20) byte entries (4 + 20 blackbirds baked in a
            // pie!)
            sha1Index = Integer.MAX_VALUE;
            packEntryOffsetArray = new int[indexEntryCount];
            packEntryOffsetArrayOrdered = new int[indexEntryCount];

            // TODO: use a binary search to find this in a more efficient manner
            for (int i = 0; i < indexEntryCount; i++) {
                // read 4 bytes offset (the offset of the SHA1's data in the "pack" file)
                packEntryOffsetArray[i] = packindexfileV1dataBuffer.getInt();
                packEntryOffsetArrayOrdered[i] = packEntryOffsetArray[i];

                // read 20 bytes SHA1
                byte[] indexEntryIdBuffer = new byte[20];
                packindexfileV1dataBuffer.get(indexEntryIdBuffer);
                String indexEntrySha1 = Hex.encodeHexString(indexEntryIdBuffer);
                if (indexEntrySha1.equals(filesha1)) {
                    if (log.isDebugEnabled())
                        log.debug("FOUND our SHA1 " + indexEntrySha1 + " at entry " + i + " in the SHA1 table");
                    sha1Index = i;
                }
            }
            // final sanity check, if all of the above panned out for version 1 index file.
            // Note: we *think* that that "pack index" file version 1 is compatible with "pack"
            // file version 3 and 4, but really, we don't know for sure.. Again, so sue me.
            int packindexFileVersion = 1;
            if (packFileVersion != 2 && packFileVersion != 3 && packFileVersion != 4) {
                throw new Exception("Pack index file version (" + packindexFileVersion
                        + ") is incompatible with pack file version (" + packFileVersion + ")");
            }

        } catch (NotV1GitPackIndexFileException e) {
            // so it's not a version 1 "pack index" file. Try parsing it as a version 2, 3, 4
            // (or later versions, once there are more versions, and we support them)
            if (log.isDebugEnabled())
                log.debug(
                        "The 'pack index' file looks like a > version 1 'pack index' file. Trying to parse it as later formats instead");

            // Parse the "pack index" file header
            ByteBuffer packindexfiledataBuffer = ByteBuffer.wrap(packfileindexdata);

            byte[] packindexfileheaderSignatureArray = new byte[4];
            packindexfiledataBuffer.get(packindexfileheaderSignatureArray);
            if (
            /*packindexfileheaderSignatureArray[0]!= 0xFF || */
            packindexfileheaderSignatureArray[1] != 't' || packindexfileheaderSignatureArray[2] != 'O'
                    || packindexfileheaderSignatureArray[3] != 'c') {
                throw new Exception(
                        "The pack index file header does not appear to be valid for pack index file version 2, 3, or 4: '"
                                + new String(packindexfileheaderSignatureArray) + "' was found");
            }

            // Note: version 1 is handled separately, so need to check for it here.
            int packindexFileVersion = packindexfiledataBuffer.getInt();
            if (packindexFileVersion != 2 && packindexFileVersion != 3) {
                throw new Exception("Pack index file version(" + packindexFileVersion + ") is not supported");
            }
            if ((packFileVersion == 2 || packFileVersion == 3) && packindexFileVersion != 2) {
                throw new Exception("Pack index file version (" + packindexFileVersion
                        + ") is incompatible with pack file version (" + packFileVersion + ")");
            }
            if (packindexFileVersion == 3 && packFileVersion != 4) {
                throw new Exception("Pack index file version (" + packindexFileVersion
                        + ") is only compatible with pack file version 4. Pack file version (" + packFileVersion
                        + ") was found");
            }

            int packEntrySizeArray[] = new int[256];
            for (int i = 0; i < 256; i++) {
                packEntrySizeArray[i] = packindexfiledataBuffer.getInt();
            }
            // get the total number of entries, as being the number of entries from the final
            // fanout table entry.
            indexEntryCount = packEntrySizeArray[255];
            // validate that this matches the number of entries in the pack file, according to
            // its header.
            if (indexEntryCount != packEntryCount) {
                throw new Exception("The entry count (" + indexEntryCount
                        + ") from the pack index does not match the entry count (" + packEntryCount
                        + ") from the pack file");
            }

            // in version 3 of the pack index file, the SHA1 table moves from the pack index
            // file to the pack file (necessitating a version 4 pack file, as noted earlier)
            // in versions < 3 of the index file, the SHA1 data lives in the index file in some
            // manner (differs between version 1, and versions 2,3).
            if (packindexFileVersion < 3) {
                sha1Index = Integer.MAX_VALUE;
                // read the series of 20 byte sha1 entries.
                // ours *should* be in here somewhere.. find it
                // make sure to read *all* of the entries from the file (or else seek to the end
                // of the data), so the parsing logic is not broken.
                // TODO: use a binary search to find this in a more efficient manner

                for (int i = 0; i < indexEntryCount; i++) {
                    byte[] indexEntryIdBuffer = new byte[20];
                    packindexfiledataBuffer.get(indexEntryIdBuffer);
                    String indexEntrySha1 = Hex.encodeHexString(indexEntryIdBuffer);
                    if (indexEntrySha1.equals(filesha1)) {
                        if (log.isDebugEnabled())
                            log.debug("FOUND our SHA1 " + indexEntrySha1 + " at entry " + i
                                    + " in the SHA11 table");
                        sha1Index = i;
                    }
                }
            }
            // read the CRCs for the various entries (and throw them away, for now)
            byte[] crcs = new byte[indexEntryCount * 4];
            packindexfiledataBuffer.get(crcs);

            // read the offsets for the various entries. We need to know the offset into the
            // pack file of the SHA11 entry we are looking at
            // NB: the various tables in the "pack index" file are sorted by the corresponding
            // SHA1.
            // 2 adjacent entries in the offset table (for consequtive SHA11 entries) could have
            // wildly different offsets into the "pack" file
            // and the offsets in the table are therefore not sorted by offset.
            // In order to calculate the deflated length of an entry in the pack file (which is
            // not stored anywhere),
            // we need to generate an extra offset table, ordered by the offset. We will then
            // look for the next ordered offset, and store it alongside
            // the offset of the SHA1 we're interested in.
            packEntryOffsetArray = new int[indexEntryCount];
            packEntryOffsetArrayOrdered = new int[indexEntryCount];
            for (int i = 0; i < indexEntryCount; i++) {
                packEntryOffsetArray[i] = packindexfiledataBuffer.getInt();
                packEntryOffsetArrayOrdered[i] = packEntryOffsetArray[i];
            }
        }
        // now we're out of the pack index file version 1 or 2/3 specific stuff.. the rest of
        // the logic is fairly common (except for the "pack" file version 4 stuff, of course! :)
        Arrays.sort(packEntryOffsetArrayOrdered);

        // take account of the 20 byte sha1 checksum after all the individual entries
        int nextOffset = packfiledata.length - 20;
        // get the first offset greater than the offset of our sha1. since the table is ordered
        // by offset, these 2 offsets gives us the deflated length of the entry
        for (int i = 0; i < indexEntryCount; i++) {
            if (packEntryOffsetArrayOrdered[i] > packEntryOffsetArray[sha1Index]) {
                nextOffset = packEntryOffsetArrayOrdered[i];
                // if (log.isDebugEnabled()) log.debug("Found the entry with the next offset: "+
                // nextOffset);
                if (nextOffset > (packfiledata.length - 1))
                    throw new Exception("A 'next' offset of " + nextOffset
                            + " is not feasible for a pack file with length " + packfiledata.length);
                break;
            }
        }
        // given the "pack" file offsets, we know the deflated length of the entry in there.
        int entryLength = (nextOffset - packEntryOffsetArray[sha1Index]);
        if (log.isDebugEnabled()) {
            log.debug("Our offset into the pack file is " + packEntryOffsetArray[sha1Index]);
            log.debug("The offset of the next entry into the pack file is " + nextOffset);
            log.debug("The deflated entry length, based on offset differences, is " + entryLength);
        }

        // get the data from the pack file and return it.
        byte[] inflatedData = getPackedObjectData(packfiledata, packEntryOffsetArray[sha1Index], entryLength,
                packFileVersion);
        return inflatedData;
    }
}

From source file:org.zaproxy.zap.extension.ascanrulesAlpha.ProxyDisclosureScanner.java

/**
 * scans for Proxy Disclosure issues, using the TRACE and OPTIONS method with 'Max-Forwards',
 * and the TRACK method. The code attempts to enumerate and identify all proxies identified
 * between the Zap instance and the origin web server.
 *//*from   ww w .  j a  v  a  2  s.co m*/
@Override
public void scan() {
    try {
        // where's what we're going to do (roughly):
        // 1: If TRACE is enabled on the origin web server, we're going to use it, and the
        // "Max-Forwards" header to verify
        //    if *no* proxy exists between Zap and the origin web server.
        // 2: If we can't do that, because TRACE is not supported, or because there appears to
        // be a proxy between Zap
        //   and the origin web server, we use the "Max-Forwards" compatible methods (TRACE and
        // OPTIONS) to
        //    iterate through each of the proxies between Zap and the origin web server.
        //    We will attempt to fingerprint each proxy / web server along the way, using various
        // techniques.
        // 3: At this point, depending on the proxies and their configurations, there is a
        // possibility that we have not
        //    identified *all* of the nodes (proxies / web servers) that the request/response
        // traverses.  We will use
        //    other HTTP methods, such as "TRACK" to obtain an error-type response. In all of the
        // cases we have tested so far,
        //   such an error response comes from the origin web server, rather than an
        // intermediate proxy.  We then fingerprint
        //    the origin web server.  If the origin web server's signature is not the same as the
        // final node that we have
        //    already identified, we consider the origin web server to be an additional node in
        // the path.
        // 4: Report the results.

        // Step 1: Using TRACE, identify if *no* proxies are used between the Zap instance and
        // the origin web server
        // int maxForwardsMaximum = 7;  //Anonymous only use 7 proxies, so that's good enough
        // for us too.. :)
        int step1numberOfProxies = 0;
        // this variable is to track proxies that set cookies, but are otherwise complete
        // invisible, and do not
        // respond per spec (RFC2616, RFC2965) to OPTIONS/TRACE with Max-Forwards.
        // They do not set headers that can be identified.
        // They are also inherently un-ordered, because we do not, and cannot be sure at what
        // point they fit into the topology
        // that we can otherwise document using OPTIONS/TRACE + Max-Forwards.
        Set<String> silentProxySet = new HashSet<String>();
        boolean endToEndTraceEnabled = false;
        boolean proxyTraceEnabled = false;

        URI traceURI = getBaseMsg().getRequestHeader().getURI();
        HttpRequestHeader traceRequestHeader = new HttpRequestHeader();
        traceRequestHeader.setMethod(HttpRequestHeader.TRACE);
        // go to the URL requested, in case the proxy is configured on a per-URL basis..
        // traceRequestHeader.setURI(new URI(traceURI.getScheme() + "://" +
        // traceURI.getAuthority()+ "/",true));
        traceRequestHeader.setURI(traceURI);
        traceRequestHeader.setVersion(HttpRequestHeader.HTTP11); // or 1.1?
        traceRequestHeader.setSecure(traceRequestHeader.isSecure());
        traceRequestHeader.setHeader("Max-Forwards", String.valueOf(MAX_FORWARDS_MAXIMUM));
        traceRequestHeader.setHeader("Cache-Control", "no-cache"); // we do not want cached content. we want content from the origin
        // server
        traceRequestHeader.setHeader("Pragma", "no-cache"); // similarly, for HTTP/1.0

        HttpMessage tracemsg = getNewMsg();
        tracemsg.setRequestHeader(traceRequestHeader);
        // create a random cookie, and set it up, so we can detect if the TRACE is enabled (in
        // which case, it should echo it back in the response)
        String randomcookiename = RandomStringUtils.random(15,
                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
        String randomcookievalue = RandomStringUtils.random(40,
                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
        TreeSet<HtmlParameter> cookies = tracemsg.getCookieParams();
        cookies.add(new HtmlParameter(HtmlParameter.Type.cookie, randomcookiename, randomcookievalue));
        tracemsg.setCookieParams(cookies);

        sendAndReceive(tracemsg, false); // do not follow redirects.
        // is TRACE enabled?
        String traceResponseBody = tracemsg.getResponseBody().toString();
        if (traceResponseBody.contains(randomcookievalue)) {
            // TRACE is enabled. Look at the Max-Forwards in the response, to see if it was
            // decremented
            // if it was decremented, there is definitely a proxy..
            // if not, it *suggests* there is no proxy (or any proxies present are not compliant
            // --> all bets are off)
            boolean proxyActuallyFound = false;
            // found a TRACE from Zap all the way through to the Origin server.. not good!!
            endToEndTraceEnabled = true; // this will raise the risk from Medium to High if a Proxy Disclosure
            // was found!
            // TODO: raise a "TRACE" type alert (if no proxy disclosure is found, but TRACE
            // enabled?)

            Matcher matcher = MAX_FORWARDS_RESPONSE_PATTERN.matcher(traceResponseBody);
            if (matcher.find()) {
                String maxForwardsResponseValue = matcher.group(1);
                if (log.isDebugEnabled())
                    log.debug("TRACE with \"Max-Forwards: " + MAX_FORWARDS_MAXIMUM
                            + "\" causes response body Max-Forwards value '" + maxForwardsResponseValue + "'");
                if (maxForwardsResponseValue.equals(String.valueOf(MAX_FORWARDS_MAXIMUM))) {
                    // (probably) no proxy!
                    if (log.isDebugEnabled())
                        log.debug("TRACE with \"Max-Forwards: " + MAX_FORWARDS_MAXIMUM
                                + "\" indicates that there is *NO* proxy in place. Note: the TRACE method is supported.. that's an issue in itself! :)");

                    // To be absolutely certain, check that the cookie info in the response
                    // header,
                    // and proxy request headers in the response body (via TRACE) do not leak
                    // the presence of a proxy
                    // This would indicate a non-RFC2606 compliant proxy, since these are
                    // supposed to decrement the Max-Forwards.
                    // it does happen in the wild..
                    String traceResponseHeader = tracemsg.getResponseHeader().toString();

                    // look for cookies set by the proxy, which will be in the response header
                    Iterator<Pattern> cookiePatternIterator = PROXY_COOKIES.keySet().iterator();
                    while (cookiePatternIterator.hasNext() && !proxyActuallyFound) {
                        Pattern cookiePattern = cookiePatternIterator.next();
                        String proxyServer = PROXY_COOKIES.get(cookiePattern);
                        Matcher cookieMatcher = cookiePattern.matcher(traceResponseHeader);
                        if (cookieMatcher.find()) {
                            String cookieDetails = cookieMatcher.group(1);
                            if (log.isDebugEnabled()) {
                                proxyActuallyFound = true;
                                if (!proxyServer.equals("") && !silentProxySet.contains(proxyServer))
                                    silentProxySet.add(proxyServer);
                                if (log.isDebugEnabled())
                                    log.debug("TRACE with \"Max-Forwards: " + MAX_FORWARDS_MAXIMUM
                                            + "\" indicates that there is *NO* proxy in place, but a known proxy cookie ("
                                            + cookieDetails + ", which indicates proxy server '" + proxyServer
                                            + "') in the response header contradicts this..");
                            }
                        }
                    }
                    // look for request headers set by the proxy, which will end up in the
                    // response body if the TRACE succeeded
                    Iterator<Pattern> requestHeaderPatternIterator = PROXY_REQUEST_HEADERS.keySet().iterator();
                    while (requestHeaderPatternIterator.hasNext() && !proxyActuallyFound) {
                        Pattern proxyHeaderPattern = requestHeaderPatternIterator.next();
                        String proxyServer = PROXY_REQUEST_HEADERS.get(proxyHeaderPattern);
                        Matcher proxyHeaderMatcher = proxyHeaderPattern.matcher(traceResponseBody);
                        if (proxyHeaderMatcher.find()) {
                            String proxyHeaderName = proxyHeaderMatcher.group(1);
                            if (log.isDebugEnabled()) {
                                proxyActuallyFound = true;
                                if (log.isDebugEnabled())
                                    log.debug("TRACE with \"Max-Forwards: " + MAX_FORWARDS_MAXIMUM
                                            + "\" indicates that there is *NO* proxy in place, but a known proxy request header ("
                                            + proxyHeaderName + ", which indicates proxy server '" + proxyServer
                                            + "') in the response body contradicts this..");
                            }
                        }
                    }

                } else {
                    // Trace indicates there is a proxy in place.. (or multiple proxies)
                    // Note: this number cannot really be trusted :( we don't use it, other than
                    // for informational purposes
                    step1numberOfProxies = MAX_FORWARDS_MAXIMUM - Integer.parseInt(maxForwardsResponseValue);
                    if (log.isDebugEnabled())
                        log.debug("TRACE with \"Max-Forwards: " + MAX_FORWARDS_MAXIMUM
                                + "\" indicates that there *IS* at least one proxy in place (Likely number: "
                                + step1numberOfProxies + "). Note: the TRACE method is also supported!");
                    proxyActuallyFound = true;
                }
            } else {
                // The Max-Forwards does not appear in the response body, even though the cookie
                // value appeared in the response body, using TRACE.. Why?
                if (log.isDebugEnabled())
                    log.debug(
                            "TRACE support is indicated via an echoed cookie, but the Max-Forwards value from the request is not echoed in the response. Why? Load balancer? WAF?");
                proxyActuallyFound = true;
            }
            // no conflicting evidence (ie, no proxy indicated) ==> return
            if (!proxyActuallyFound)
                return;
        } else {
            // TRACE is NOT enabled, so we can't use this technique to tell if there is *no*
            // proxy between Zap and the origin server
            if (log.isDebugEnabled())
                log.debug(
                        "TRACE is not supported, so we cannot quickly check for *no* proxies. Falling back to the hard way");
        }

        // bale out if we were asked nicely. it's nice to be nice.
        if (isStop()) {
            if (log.isDebugEnabled())
                log.debug("Stopping the scan due to a user request (after step 1)");
            return;
        }

        // Step 2: Use Max-Forwards with OPTIONS and TRACE to iterate through each of the
        // proxies
        HttpRequestHeader baseRequestHeader = getBaseMsg().getRequestHeader();
        URI baseRequestURI = baseRequestHeader.getURI();
        int step2numberOfNodes = 0;
        String[] nodeServers = new String[MAX_FORWARDS_MAXIMUM + 2]; // up to n proxies, and an origin server.

        // for each of the methods
        for (String httpMethod : MAX_FORWARD_METHODS) {
            // for each method, increment the Max-Forwards, and look closely at the response
            // TODO: loop from 0 to numberOfProxies -1????
            int step2numberOfNodesForMethod = 0;
            String[] nodeServersForMethod = new String[MAX_FORWARDS_MAXIMUM + 2];
            String previousServerDetails = RandomStringUtils.random(15, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
            int previousResponseStatusCode = 0;
            int responseStatusCode = 0;
            boolean httpHandled = false; // a flag to handle an extra HTTP request for this method, if the URL
            // is HTTPS

            // if the TRACE worked in step 1, and we know how many proxies there are, do that
            // number + 1, else just do the maximum defined on the attack strength
            for (int maxForwards = 0; maxForwards < (step1numberOfProxies > 0 ? step1numberOfProxies + 1
                    : MAX_FORWARDS_MAXIMUM); maxForwards++) {

                HttpMessage testMsg = getNewMsg(); // get a new message, with the request attributes cloned
                // from the base message
                HttpRequestHeader origRequestHeader = testMsg.getRequestHeader();

                if (log.isDebugEnabled())
                    log.debug("Trying method " + httpMethod + " with MAX-FORWARDS: "
                            + Integer.toString(maxForwards));

                // if we're on the right iteration (Max-Forwards=0, ie first proxy, and a HTTPS
                // request, then
                // then prepare to try an additional HTTP request..
                boolean tryHttp = (!httpHandled && maxForwards == 0 && baseRequestHeader.isSecure());

                HttpRequestHeader requestHeader = new HttpRequestHeader();
                requestHeader.setMethod(httpMethod);
                // requestHeader.setURI(new URI(origURI.getScheme() + "://" +
                // origURI.getAuthority()+ "/",true));
                requestHeader.setURI(baseRequestURI);
                requestHeader.setVersion(HttpRequestHeader.HTTP11); // OPTIONS and TRACE are supported under 1.0, but for
                // multi-homing, we need to use 1.1
                if (tryHttp) {
                    if (log.isDebugEnabled())
                        log.debug(
                                "Blind-spot testing, using a HTTP connection, to try detect an initial proxy, which we might not see via HTTPS");
                    requestHeader.setSecure(false);
                    requestHeader.setHeader("Max-Forwards", "0");
                } else {
                    requestHeader.setSecure(origRequestHeader.isSecure());
                    requestHeader.setHeader("Max-Forwards", Integer.toString(maxForwards));
                }
                requestHeader.setHeader("Cache-Control", "no-cache"); // we do not want cached content. we want content from the
                // origin server
                requestHeader.setHeader("Pragma", "no-cache"); // similarly, for HTTP/1.0

                HttpMessage mfMethodMsg = getNewMsg();
                mfMethodMsg.setRequestHeader(requestHeader);

                // create a random cookie, and set it up, so we can detect if the TRACE is
                // enabled (in which case, it should echo it back in the response)
                String randomcookiename2 = RandomStringUtils.random(15,
                        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
                String randomcookievalue2 = RandomStringUtils.random(40,
                        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
                TreeSet<HtmlParameter> cookies2 = mfMethodMsg.getCookieParams();
                cookies2.add(
                        new HtmlParameter(HtmlParameter.Type.cookie, randomcookiename2, randomcookievalue2));
                mfMethodMsg.setCookieParams(cookies2);

                try {
                    sendAndReceive(mfMethodMsg, false); // do not follow redirects.
                } catch (Exception e) {
                    log.error(
                            "Failed to send a request in step 2 with method " + httpMethod + ", Max-Forwards: "
                                    + requestHeader.getHeader("Max-Forwards") + ": " + e.getMessage());
                    break; // to the next method
                }

                // if the response from the proxy/origin server echoes back the cookie (TRACE,
                // or other method), that's serious, so we need to check.
                String methodResponseBody = mfMethodMsg.getResponseBody().toString();
                if (methodResponseBody.contains(randomcookievalue2)) {
                    proxyTraceEnabled = true; // this will raise the risk from Medium to High if a Proxy
                    // Disclosure was found!
                    // TODO: raise a "TRACE" type alert (if no proxy disclosure is found, but
                    // TRACE enabled?)
                }

                // check if the Server response header differs
                // the server header + powered by list is what we will record if a key attribute
                // changes between requests.
                HttpResponseHeader responseHeader = mfMethodMsg.getResponseHeader();
                String serverHeader = responseHeader.getHeader("Server");
                if (serverHeader == null)
                    serverHeader = "";

                String poweredBy;
                Vector<String> poweredByList = responseHeader.getHeaders("X-Powered-By");
                if (poweredByList != null)
                    poweredBy = poweredByList.toString(); // uses format: "[a,b,c]"
                else
                    poweredBy = "";
                String serverDetails = serverHeader
                        + (poweredBy.equals("") || poweredBy.equals("[]") ? "" : poweredBy);
                responseStatusCode = responseHeader.getStatusCode();

                if (!serverDetails.equals(previousServerDetails)) {
                    // it's a new node that we don't appear to have previously seen (for this
                    // HTTP method).
                    nodeServersForMethod[step2numberOfNodesForMethod] = serverDetails;
                    step2numberOfNodesForMethod++;
                    if (log.isDebugEnabled())
                        log.debug("Identified a new node for method " + httpMethod + ", by server details: "
                                + serverDetails + ". That makes " + step2numberOfNodesForMethod
                                + " nodes so far");
                } else {
                    // else check if the HTTP status code differs
                    if (responseStatusCode != previousResponseStatusCode) {
                        // if the status code is different, this likely indicates a different
                        // node
                        nodeServersForMethod[step2numberOfNodesForMethod] = serverDetails;
                        step2numberOfNodesForMethod++;
                        if (log.isDebugEnabled())
                            log.debug("Identified a new node for method " + httpMethod
                                    + ", by response status : " + responseStatusCode + ". That makes "
                                    + step2numberOfNodesForMethod + " nodes so far");
                    }
                }
                previousServerDetails = serverDetails;
                previousResponseStatusCode = responseStatusCode;

                // if the base URL is HTTPS, and we just did an extra "blind spot" check for
                // HTTP, go into the next iteration with the same
                // "Max-Forwards" value that we just handled, but set the flag to false so that
                // we don't attempt to do the HTTP "blind spot" request again.
                if (tryHttp) {
                    maxForwards--;
                    httpHandled = true;
                }

                // bale out if we were asked nicely. it's nice to be nice.
                if (isStop()) {
                    if (log.isDebugEnabled())
                        log.debug("Stopping the scan due to a user request");
                    return;
                }
            }
            // if the number of nodes (proxies+origin web server) detected using this HTTP
            // method is greater than the number detected thus far, use the data
            // gained using this HTTP method..
            if (log.isDebugEnabled())
                log.debug("The number of nodes detected using method " + httpMethod + " is "
                        + step2numberOfNodesForMethod);
            if (step2numberOfNodesForMethod > step2numberOfNodes) {
                step2numberOfNodes = step2numberOfNodesForMethod;
                nodeServers = nodeServersForMethod;
            }
        }
        if (log.isDebugEnabled())
            log.debug("The maximum number of nodes detected using any Max-Forwards method  is "
                    + step2numberOfNodes);

        // Step 3: For the TRACK, use a random URL, to force an error, and to bypass any cached
        // file.
        URI trackURI = getBaseMsg().getRequestHeader().getURI();
        HttpRequestHeader trackRequestHeader = new HttpRequestHeader();
        trackRequestHeader.setMethod("TRACK"); // There is no suitable constant on HttpRequestHeader
        // go to a similar (but random) URL requested
        //   - in case a proxy is configured on a per-URL basis.. (this is the case on some of my
        // real world test servers)
        //   - to try to ensure we get an error message that we can fingerprint
        //   - to bypass caching (if it's a random filename, if won't have been seen before, and
        // won't be cached)
        //     yes, I know TRACK requests should *not* be cached, but not all servers are
        // compliant.
        String randompiece = RandomStringUtils.random(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
        trackRequestHeader.setURI(new URI(
                trackURI.getScheme() + "://" + trackURI.getAuthority() + getPath(trackURI) + randompiece,
                true));

        trackRequestHeader.setVersion(HttpRequestHeader.HTTP11); //
        trackRequestHeader.setSecure(trackRequestHeader.isSecure());
        trackRequestHeader.setHeader("Max-Forwards", String.valueOf(MAX_FORWARDS_MAXIMUM));
        trackRequestHeader.setHeader("Cache-Control", "no-cache"); // we do not want cached content. we want content from the origin
        // server
        trackRequestHeader.setHeader("Pragma", "no-cache"); // similarly, for HTTP/1.0

        HttpMessage trackmsg = getNewMsg();
        trackmsg.setRequestHeader(trackRequestHeader);

        sendAndReceive(trackmsg, false); // do not follow redirects.

        // TODO: fingerprint more origin web servers response to a TRACK request for a file that
        // does not exist.
        String trackResponseBody = trackmsg.getResponseBody().toString();
        Matcher unsupportedApacheMatcher = NOT_SUPPORTED_APACHE_PATTERN.matcher(trackResponseBody);
        if (unsupportedApacheMatcher.find()) {
            String originServerName = unsupportedApacheMatcher.group(1);
            if (log.isDebugEnabled())
                log.debug("Identified the origin node using TRACK, with server header: " + originServerName);
            // check if this is the same as the last node we've identified, and if so, discard
            // it. If not, add it to to the end (as the origin server).
            if (!nodeServers[step2numberOfNodes - 1].equals(originServerName)) {
                // it's different to the last one seen.. add it.
                if (log.isDebugEnabled())
                    log.debug(
                            "The origin node was not already recorded using the Max-Forwards method, so adding it in.");
                nodeServers[step2numberOfNodes] = originServerName;
                step2numberOfNodes++;
            }
        }

        // TODO: compare step2numberOfProxies and step1numberOfProxies?

        // log the nodes we have noted so far
        if (log.isDebugEnabled()) {
            for (int nodei = 0; nodei < step2numberOfNodes; nodei++) {
                log.debug("Node " + nodei + " is "
                        + (!nodeServers[nodei].equals("") ? nodeServers[nodei] : "Unknown"));
            }
            // log the "silent" proxies that we saw.
            for (String silentServer : silentProxySet) {
                log.debug("Silent Proxy: " + (!silentServer.equals("") ? silentServer : "Unknown"));
            }
        }

        // Note: there will always be an origin web server, so check for >1, not <0 number of
        // nodes.
        if (step2numberOfNodes > 1 || silentProxySet.size() > 0) {
            // bingo with the list of nodes (proxies+origin web server) that we detected.
            String unknown = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.unknown");
            String proxyServerHeader = Constant.messages
                    .getString(MESSAGE_PREFIX + "extrainfo.proxyserver.header");
            String webServerHeader = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.webserver.header");
            String silentProxyServerHeader = Constant.messages
                    .getString(MESSAGE_PREFIX + "extrainfo.silentproxyserver.header");

            // get the proxy server information (ie, all but the last node)
            String proxyServerInfo = "";
            if (step2numberOfNodes > 0) {
                StringBuilder sb = new StringBuilder();
                sb.append(proxyServerHeader);
                sb.append("\n");
                for (int nodei = 0; nodei < step2numberOfNodes - 1; nodei++) {
                    String proxyServerNode = Constant.messages.getString(
                            MESSAGE_PREFIX + "extrainfo.proxyserver",
                            (!nodeServers[nodei].equals("") ? nodeServers[nodei] : unknown));
                    sb.append(proxyServerNode);
                    sb.append("\n");
                }
                proxyServerInfo = sb.toString();
            }
            // get the origin web server information (ie, the last node)
            String webServerInfo = "";
            if (step2numberOfNodes > 0) {
                StringBuilder sb = new StringBuilder();
                sb.append(webServerHeader);
                sb.append("\n");
                String webServerNode = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.webserver",
                        (!nodeServers[step2numberOfNodes - 1].equals("") ? nodeServers[step2numberOfNodes - 1]
                                : unknown));
                sb.append(webServerNode);
                sb.append("\n");
                webServerInfo = sb.toString();
            }
            // get the silent proxy information
            String silentProxyServerInfo = "";
            if (silentProxySet.size() > 0) {
                StringBuilder sb = new StringBuilder();
                sb.append(silentProxyServerHeader);
                sb.append("\n");
                for (String silentServer : silentProxySet) {
                    // log.debug("Silent Proxy:
                    // "+(!silentServer.equals("")?silentServer:"Unknown"));
                    String silentProxyServerNode = Constant.messages.getString(
                            MESSAGE_PREFIX + "extrainfo.silentproxyserver",
                            (!silentServer.equals("") ? silentServer : unknown));
                    sb.append(silentProxyServerNode);
                    sb.append("\n");
                }
                silentProxyServerInfo = sb.toString();
            }
            String traceInfo = "";
            if (endToEndTraceEnabled || proxyTraceEnabled) {
                traceInfo = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.traceenabled");
            }

            // all the info is collated nicely. raise the alert.
            String extraInfo = "";
            if (!proxyServerInfo.equals("")) {
                extraInfo += proxyServerInfo;
            }
            if (!webServerInfo.equals("")) {
                extraInfo += webServerInfo;
            }
            if (!silentProxyServerInfo.equals("")) {
                extraInfo += silentProxyServerInfo;
            }
            if (!traceInfo.equals("")) {
                extraInfo += traceInfo;
            }

            // raise the alert on the original message
            // there are multiple messages on which the issue could have been raised, but each
            // individual atatck message
            // tells only a small part of the story. Explain it in the "extra info" instead.
            bingo(endToEndTraceEnabled || proxyTraceEnabled ? Alert.RISK_HIGH : getRisk(),
                    Alert.CONFIDENCE_MEDIUM, getName(),
                    Constant.messages.getString(MESSAGE_PREFIX + "desc",
                            step2numberOfNodes - 1 + silentProxySet.size()),
                    getBaseMsg().getRequestHeader().getURI().getURI(), // url
                    "", // there is no parameter of interest
                    getAttack(), // the attack. who'd have thunk it?
                    extraInfo, // extra info.. all the detail of the proxy, etc.
                    getSolution(), "", // evidence
                    this.getCweId(), this.getWascId(), getBaseMsg() // the message on which we place the alert
            );
        }

    } catch (Exception e) {
        // Do not try to internationalise this.. we need an error message in any event..
        // if it's in English, it's still better than not having it at all.
        log.error("An error occurred checking for proxy disclosure", e);
    }
}

From source file:org.zaproxy.zap.extension.ascanrulesAlpha.RelativePathConfusionScanner.java

@Override
public void scan() {

    // get the base message. What else did you think this line of code might do??
    HttpMessage originalMsg = getBaseMsg();

    if (log.isDebugEnabled()) {
        log.debug("Attacking at Attack Strength: " + this.getAttackStrength());
        log.debug("Checking [" + originalMsg.getRequestHeader().getMethod() + "] ["
                + originalMsg.getRequestHeader().getURI() + "], for Relative Path Confusion issues");
    }/*ww w  .j  a  v  a  2  s .c  o  m*/

    try {
        URI baseUri = originalMsg.getRequestHeader().getURI();
        String filename = baseUri.getName();
        String fileext = "";

        // is there a file extension at the end of the file name?
        if (filename != null && filename.length() > 0) {
            fileext = FilenameUtils.getExtension(filename);
        }

        // only filenames that have a file extension are potentially vulnerable to Relative Path
        // Confusion
        // (based on the instances of this that I've in seen in the wild, at least)
        if (fileext != null && fileext.length() > 0) {
            if (log.isDebugEnabled())
                log.debug("The file extension of " + baseUri.getURI() + " is " + fileext);

            // 1: First manipulate the URL, using a URL which is ambiguous..
            URI originalURI = originalMsg.getRequestHeader().getURI();
            String path = originalURI.getPath();
            if (path == null)
                path = "";
            String query = originalURI.getQuery();
            if (query == null)
                query = "";

            URI hackedUri = new URI(originalURI.getScheme(), originalURI.getAuthority(),
                    path + RANDOM_ATTACK_PATH + "?" + query, null, null);
            HttpMessage hackedMessage = new HttpMessage(hackedUri);
            try {
                hackedMessage.setCookieParams(originalMsg.getCookieParams());
            } catch (Exception e) {
                log.warn("Could not set the cookies from the base request:" + e);
            }
            try {
                sendAndReceive(hackedMessage, true); // follow redirects
            } catch (CircularRedirectException e) {
                log.warn("Ignoring a CircularRedirectException" + e);
            }

            // get ready to parse the HTML
            Document doc = Jsoup.parse(new String(hackedMessage.getResponseBody().getBytes()));
            String extraInfo = null;

            // 2: check if the response has a "<base>" tag specifying the base location for any
            // relative URLs
            // there can be a max of 1 <base> element in a document, and it must be inside the
            // <head> element
            // Example: <head><base href="http://www.w3schools.com/images/"
            // target="_blank"></head>
            Elements baseHrefInstances = doc.select("html > head > base[href]");
            if (!baseHrefInstances.isEmpty() && baseHrefInstances.size() == 1) {
                // a single base was specified, in line with HTTP spec
                if (log.isDebugEnabled())
                    log.debug(
                            "A base was specified, so there should be no confusion over relative paths (unless the User Agent is completely broken)");
                return;
            } else {
                if (!baseHrefInstances.isEmpty() && baseHrefInstances.size() > 1) {
                    extraInfo = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.morethanonebasetag");
                    if (log.isDebugEnabled())
                        log.debug("There more than one base (which is not valid HTML) specified for the page");
                } else {
                    if (extraInfo == null)
                        extraInfo = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.nobasetag");
                    if (log.isDebugEnabled())
                        log.debug("There is no base specified for the page");
                }
            }

            // 3: check if there are any resources that are loaded using relative URLs in the
            // response. (images, CSS, etc)
            boolean relativeReferenceFound = false;
            String relativeReferenceEvidence = "";

            Set<String> loadingHtmlAttributes = RELATIVE_LOADING_ATTRIBUTE_TO_TAGS.keySet();
            Iterator<String> i = loadingHtmlAttributes.iterator();
            for (; i.hasNext() && !relativeReferenceFound;) {
                String loadingHtmlAttribute = i.next();
                String[] loadingHtmlTags = RELATIVE_LOADING_ATTRIBUTE_TO_TAGS.get(loadingHtmlAttribute);

                for (int tagIndex = 0; tagIndex < loadingHtmlTags.length
                        && !relativeReferenceFound; tagIndex++) {
                    String tag = loadingHtmlTags[tagIndex];

                    // get instances of the specified HTML attribute and tag from the original
                    // response
                    // and see if is loading a relative URL.
                    // (ie, could it be confused if the server side can confuse the client side
                    // as to the absolute path to use when loading)
                    String selectStatement = (tag.equals("") ? "" : tag)
                            + (loadingHtmlAttribute.equals("") ? "" : "[" + loadingHtmlAttribute + "]");
                    Elements loadingTagInstances = doc.select(selectStatement);
                    int size = loadingTagInstances.size();

                    for (int index = 0; index < size && !relativeReferenceFound; index++) {
                        Element tagInstance = loadingTagInstances.get(index);

                        // handle style tags differently to other tags (for which we look at an
                        // attribute)
                        if (tag.toUpperCase().equals("STYLE")) {
                            // for the style tag, look at the entire body, not an attribute..
                            String styleBody = tagInstance.data();
                            if (log.isDebugEnabled())
                                log.debug("Got <style> data: " + styleBody);
                            Matcher matcher = STYLE_URL_LOAD.matcher(styleBody);
                            if (matcher.find()) {
                                relativeReferenceFound = true;
                                relativeReferenceEvidence = matcher.group();
                                if (log.isDebugEnabled())
                                    log.debug("Got relative STYLE reference in a style tag. Evidence: "
                                            + relativeReferenceEvidence);
                            }
                        } else {
                            // it's not the style tag, so look at the named attribute.
                            String attributeValue = tagInstance.attr(loadingHtmlAttribute);

                            if (log.isDebugEnabled())
                                log.debug("Got " + attributeValue + " for statement " + selectStatement);

                            // is it a relative reference?
                            String attributeUpper = attributeValue.toUpperCase().trim();
                            // if the reference starts with a scheme, it's absolute
                            // if it starts with "/", it's probably an absolute path (the host
                            // and scheme are inferred)
                            // if it starts with "//, it's a reference to the host and path (but
                            // not the scheme), and it's essentially an absolute reference..
                            if (!loadingHtmlAttribute.equals("style")) {
                                if (!attributeUpper.startsWith("HTTP://")
                                        && !attributeUpper.startsWith("HTTPS://")
                                        && !attributeUpper.startsWith("/")) {
                                    // it's a relative reference..
                                    relativeReferenceFound = true;
                                    // Note: since we parsed the HTML, and are reconstructing
                                    // the tag, this value may not exactly mirror the original
                                    // value in the HTML, but it's better than nothing. Whatcha
                                    // gonna do?
                                    // relativeReferenceEvidence = "<"+ tag + " " +
                                    // loadingHtmlAttribute + "=\"" + attributeValue + "\"";
                                    relativeReferenceEvidence = tagInstance.outerHtml();

                                    if (log.isDebugEnabled())
                                        log.debug("Got relative reference: " + attributeValue
                                                + " for statement " + selectStatement + ". Evidence: "
                                                + relativeReferenceEvidence);
                                }
                            } else {
                                // for the style attribute (on various tags), look for a pattern
                                // like "background: url(image.png)"
                                Matcher matcher = STYLE_URL_LOAD.matcher(attributeUpper);
                                if (matcher.find()) {
                                    relativeReferenceFound = true;
                                    relativeReferenceEvidence = attributeValue; // matcher.group();
                                    if (log.isDebugEnabled())
                                        log.debug("Got relative STYLE reference: " + attributeValue + " for "
                                                + tag + "." + loadingHtmlAttribute + ". Evidence: "
                                                + relativeReferenceEvidence);
                                }
                            }
                        }
                    }
                }
            }
            // TODO: what if the relative reference is occurring in the JavaScript??
            // if there are no relative references in the response, bale out, because there is
            // nothing to worry about
            if (!relativeReferenceFound) {
                if (log.isDebugEnabled())
                    log.debug(
                            "No relative references were found in the original response, so there is no possibility for confusion over relative path references)");
                return;
            }

            // 4: Now check the content type of the response.
            // If no Content Type was specified, happy days, we can move to the next check in
            // the knowledge that the content can
            // be interpreted a non-HTML content type by the web browser, if we can fool the
            // browser into loading the page.
            // If a content type is "Content-Type: text/html", we need to see if there is a way
            // to override the Content Type.
            // Known ways are:
            // a: Get the browser to render in Quirks Mode
            //      Note 1: Quirks mode might have been set in the response, without us having to do
            // anything else.. check!
            //      Note 2: Quirks mode is set if the response does not set a doctype, or uses an
            // old doctype
            //      Note 3: If quirks mode is not enabled, we may be able to enable it by setting it
            // on a framing page (if the page in question allows framing)
            //
            // TODO: Pass in a random filename (something like the
            // aaa/bbb/blah.php/xxx/yyy/zzz?a=1&b=2 request we use here)
            //       that ends in ".css", to see if the web server changes the content type to
            // "text/css" (unlikely!)
            String contentType = hackedMessage.getResponseHeader().getHeader(HttpHeader.CONTENT_TYPE);
            if (contentType != null) {

                if (log.isDebugEnabled())
                    log.debug("Content Type is set, so we need to see if there is a way to bypass it");
                boolean quirksMode = false;
                if (extraInfo == null)
                    extraInfo = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.contenttypeenabled",
                            contentType);
                else
                    extraInfo += "\n" + Constant.messages
                            .getString(MESSAGE_PREFIX + "extrainfo.contenttypeenabled", contentType);

                // a: Quirks mode!
                // Is it already enabled?
                // In the HEAD.. (Note: X-UA-Compatible trumps the doctype in IE)
                // <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">
                // <meta http-equiv="X-UA-Compatible" content="IE=8" />
                // <meta http-equiv="x-ua-compatible" content="IE=9">
                // <meta http-equiv="x-ua-compatible" content="IE=edge" >   sets the page to
                // HTML5 mode, not quirks mode!!!

                // HTML 5: <!doctype html>                           sets the page to HTML5 mode

                Elements httpEquivInstances = doc.select("html > head > meta[http-equiv]");
                int size = httpEquivInstances.size();

                for (int index = 0; index < size; index++) {
                    Element e = httpEquivInstances.get(index);
                    String httpEquivAttributeValue = e.attr("http-equiv");
                    String contentAttributeValue = e.attr("content");

                    if (log.isDebugEnabled())
                        log.debug("Got " + httpEquivAttributeValue + " for html > head > meta[http-equiv]");
                    if (httpEquivAttributeValue.toUpperCase().trim().equals("X-UA-COMPATIBLE")
                            && !contentAttributeValue.toUpperCase().trim().equals("IE=EDGE")) {
                        // Quirks mode is already enabled!
                        // Note: if this is present, it overrides any "<!doctype html>" that
                        // would otherwise set the page to HTML5 mode
                        quirksMode = true;
                        if (log.isDebugEnabled())
                            log.debug(
                                    "Quirks mode is explicitly enabled via <meta http-equiv=\"x-ua-compatible\" (which overrides any \"<!doctype html>\" HTML 5 directive) ... This allows the specified Content Type to be bypassed");
                        if (extraInfo == null)
                            extraInfo = Constant.messages.getString(
                                    MESSAGE_PREFIX + "extrainfo.quirksmodeenabledexplicitly",
                                    httpEquivAttributeValue);
                        else
                            extraInfo += "\n" + Constant.messages.getString(
                                    MESSAGE_PREFIX + "extrainfo.quirksmodeenabledexplicitly",
                                    httpEquivAttributeValue);
                    }
                }
                // is quirks mode implicitly enabled via the absence of a doctype?
                // is quirks mode implicitly enabled via an old doctype?
                if (!quirksMode) {
                    boolean docTypeSpecified = false;
                    List<Node> nodes = doc.childNodes();
                    for (Node node : nodes) {
                        if (node instanceof DocumentType) {
                            docTypeSpecified = true;
                            DocumentType documentType = (DocumentType) node;
                            String docTypePublicId = documentType.attr("publicid");
                            // is the doctype old enough to enable quirks mode?
                            for (String doctypePublicIdTiggerQuirks : DOCTYPE_PUBLIC_IDS_TRIGGERING_QUIRKS_MODE) {
                                if (docTypePublicId.toUpperCase()
                                        .equals(doctypePublicIdTiggerQuirks.toUpperCase())) {
                                    // this doctype is know to trigger quirks mode in some
                                    // browsers..
                                    quirksMode = true;
                                    if (log.isDebugEnabled())
                                        log.debug(
                                                "Quirks mode is implicitly triggered via the use of old doctype "
                                                        + docTypePublicId
                                                        + ". This allows the specified Content Type to be bypassed");
                                    if (extraInfo == null)
                                        extraInfo = Constant.messages.getString(
                                                MESSAGE_PREFIX + "extrainfo.quirksmodeenabledimplicitly",
                                                docTypePublicId);
                                    else
                                        extraInfo += "\n" + Constant.messages.getString(
                                                MESSAGE_PREFIX + "extrainfo.quirksmodeenabledimplicitly",
                                                docTypePublicId);
                                    break;
                                }
                            }
                            if (log.isDebugEnabled())
                                log.debug("DocType public id: " + docTypePublicId + ". Entire thing: "
                                        + documentType);
                        }
                    }
                    if (!docTypeSpecified) {
                        quirksMode = true;
                        if (log.isDebugEnabled())
                            log.debug(
                                    "Quirks mode is implicitly enabled via the absence of a doctype... This allows the specified Content Type to be bypassed");
                        if (extraInfo == null)
                            extraInfo = Constant.messages.getString(
                                    MESSAGE_PREFIX + "extrainfo.quirksmodeenabledimplicitlynodoctype");
                        else
                            extraInfo += "\n" + Constant.messages.getString(
                                    MESSAGE_PREFIX + "extrainfo.quirksmodeenabledimplicitlynodoctype");
                    }
                }

                // if quirksMode is enabled, we do not need to check to see if framing attacks
                // are feasible
                boolean framingAttackPossible = false;
                if (!quirksMode) {
                    // if the framing attack does not work, check for a framing attack
                    String frameHeader = hackedMessage.getResponseHeader().getHeader(HttpHeader.X_FRAME_OPTION);
                    if (frameHeader != null) {
                        if (frameHeader.toUpperCase().equals("DENY")) {
                            // definitely rules out the framing attack (unless the user is using
                            // a dozy web browser that doesn't understand "X-FRAME-OPTIONS:
                            // DENY")
                            framingAttackPossible = false;
                            if (log.isDebugEnabled())
                                log.debug(
                                        "\"X-FRAME-OPTIONS: DENY\" rules out a framing attack, unless a really old browser is used (IE < 8.0, for instance)");
                        } else if (frameHeader.toUpperCase().equals("SAMEORIGIN")) {
                            // let's say this rules it out (unless the attacker has a persistent
                            // XSS, or already owns the site)
                            framingAttackPossible = false;
                            if (log.isDebugEnabled())
                                log.debug(
                                        "\"X-FRAME-OPTIONS: SAMEORIGIN\" rules out a framing attack, unless a really old browser is used (IE < 8.0, for instance)");
                        } else if (frameHeader.toUpperCase().startsWith("ALLOW-FROM")) {
                            // let's say this rules it out (unless the attacker has a persistent
                            // XSS, or already owns the site)
                            framingAttackPossible = false;
                            if (log.isDebugEnabled())
                                log.debug(
                                        "\"X-FRAME-OPTIONS: ALLOW-FROM\" probably rules out a framing attack, unless the attacker owns the website in the ALLOW-FROM, which is generally very unlikely");
                        }
                    } else {
                        // no framing headers were specified, so a framing attack is possible to
                        // force quicks mode, to bypass the Content-Type, which was specified
                        framingAttackPossible = true;
                        if (extraInfo == null)
                            extraInfo = Constant.messages
                                    .getString(MESSAGE_PREFIX + "extrainfo.framingallowed");
                        else
                            extraInfo += "\n"
                                    + Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.framingallowed");
                    }
                }

                // if quirks mode is off, and a framing attack is not possible, we can't "break
                // out" of the content type.. boo hoo..
                if ((!quirksMode) && (!framingAttackPossible)) {
                    if (log.isDebugEnabled())
                        log.debug(
                                "Can't see a way to break out of the Content-Type, since Quirks mode is off (both explicit and implicit), and the page cannot be framed.");
                    return;
                }
            } else {
                // happy days. Content type is not set, so no hacks required to bypass it.
                if (log.isDebugEnabled())
                    log.debug("Content Type is not set, so no hacks are required to bypass it!");
                if (extraInfo == null)
                    extraInfo = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.nocontenttype");
                else
                    extraInfo += "\n" + Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.nocontenttype");
            }

            // alert it..
            bingo(getRisk(), Alert.CONFIDENCE_MEDIUM, getName(), getDescription(),
                    getBaseMsg().getRequestHeader().getURI().getURI(), null, // parameter being attacked: none.
                    hackedUri.getURI(), // the attack is the URL itself
                    extraInfo, getSolution(), relativeReferenceEvidence, this.getCweId(), this.getWascId(),
                    hackedMessage);

            if (log.isDebugEnabled()) {
                log.debug("A Relative Path Confusion issue exists on "
                        + getBaseMsg().getRequestHeader().getURI().getURI());
            }
            return;

        } else {
            if (log.isDebugEnabled()) {
                log.debug(
                        "The URI has no filename component, so there is unlikely to be any ambiguity over any relative paths");
            }
        }
    } catch (Exception e) {
        log.error("Error scanning a request for Relative Path confusion: " + e.getMessage(), e);
    }
}