com.subgraph.vega.impl.scanner.urls.ResponseAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for com.subgraph.vega.impl.scanner.urls.ResponseAnalyzer.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Subgraph.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Subgraph - initial API and implementation
 ******************************************************************************/
package com.subgraph.vega.impl.scanner.urls;

import java.net.URI;
import java.net.URISyntaxException;

import org.apache.http.client.methods.HttpUriRequest;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.html2.HTMLDocument;

import com.subgraph.vega.api.analysis.IContentAnalyzer;
import com.subgraph.vega.api.analysis.IContentAnalyzerResult;
import com.subgraph.vega.api.html.IHTMLParseResult;
import com.subgraph.vega.api.http.requests.IHttpResponse;
import com.subgraph.vega.api.scanner.IInjectionModuleContext;
import com.subgraph.vega.api.scanner.IScannerConfig;
import com.subgraph.vega.impl.scanner.forms.FormProcessor;

public class ResponseAnalyzer {

    private final IContentAnalyzer contentAnalyzer;
    private final UriParser uriParser;
    private final UriFilter uriFilter;
    private final FormProcessor formProcessor;

    public ResponseAnalyzer(IScannerConfig config, IContentAnalyzer contentAnalyzer, UriParser uriParser,
            UriFilter uriFilter) {
        this.contentAnalyzer = contentAnalyzer;
        this.uriParser = uriParser;
        this.uriFilter = uriFilter;
        this.formProcessor = new FormProcessor(config, uriFilter, uriParser);
    }

    public IContentAnalyzer getContentAnalyzer() {
        return contentAnalyzer;
    }

    public void analyzePivot(IInjectionModuleContext ctx, HttpUriRequest req, IHttpResponse res) {

    }

    public void analyzePage(IInjectionModuleContext ctx, HttpUriRequest req, IHttpResponse res) {

        final IContentAnalyzerResult result = contentAnalyzer.processResponse(res, false, true);
        for (URI u : result.getDiscoveredURIs()) {
            if (uriFilter.filter(u))
                uriParser.processUri(u);
        }
        formProcessor.processForms(ctx, req, res);
    }

    public void analyzeContent(IInjectionModuleContext ctx, HttpUriRequest req, IHttpResponse res) {
        analyzeHtml(ctx, req, res);
        if (!filterInjectedPath(res.getRequestUri().getPath())) {
            contentAnalyzer.processResponse(res, false, false);
        }
    }

    private void analyzeHtml(IInjectionModuleContext ctx, HttpUriRequest req, IHttpResponse res) {
        IHTMLParseResult html = res.getParsedHTML();
        if (html == null)
            return;
        HTMLDocument document = html.getDOMDocument();
        NodeList elements = document.getElementsByTagName("*");
        for (int i = 0; i < elements.getLength(); i++) {
            Node node = elements.item(i);
            if (node instanceof Element) {
                analyzeHtmlElement(ctx, req, res, (Element) node);
            }
        }
    }

    private boolean filterInjectedPath(String path) {
        return path.contains("vega://") || path.contains("//vega.invalid") || pathContainsXssTag(path);
    }

    private boolean pathContainsXssTag(String path) {
        final int idx = path.indexOf("vvv");
        if (idx == -1) {
            return false;
        }
        return extractXssTag(path, idx) != null;
    }

    private void analyzeHtmlElement(IInjectionModuleContext ctx, HttpUriRequest req, IHttpResponse res,
            Element elem) {
        boolean remoteScript = false;
        NamedNodeMap attributes = elem.getAttributes();
        final String tag = elem.getTagName().toLowerCase();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node item = attributes.item(i);
            if (item instanceof Attr) {
                String n = ((Attr) item).getName().toLowerCase();
                String v = ((Attr) item).getValue().toLowerCase();
                if (match(tag, "script") && match(n, "src"))
                    remoteScript = true;
                if ((v != null) && (match(n, "href", "src", "action", "codebase")
                        || (match(n, "value") && !match(tag, "input")))) {
                    if (v.startsWith("vega://"))
                        alert(ctx, "vinfo-url-inject", "URL injection into <" + tag + "> tag", req, res);

                    if (v.startsWith("http://vega.invalid/") || v.startsWith("//vega.invalid/")) {
                        if (match(tag, "script", "link"))
                            alert(ctx, "vinfo-url-inject",
                                    "URL injection into actively fetched field in tag <" + tag + "> (high risk)",
                                    req, res);
                        else if (match(tag, "a"))
                            alert(ctx, "vinfo-url-inject", "URL injection into anchor tag (low risk)", req, res);
                        else
                            alert(ctx, "vinfo-url-inject", "URL injection into tag <" + tag + ">", req, res);
                    }

                }

                if ((v != null) && (n.startsWith("on") || n.equals("style"))) {
                    checkJavascriptXSS(ctx, req, res, v);
                }

                if (n.contains("vvv"))
                    possibleXssAlert(ctx, req, res, n, n.indexOf("vvv"), "xss-inject",
                            "Injected XSS tag into HTML attribute value");

            }
        }

        if (tag.startsWith("vvv"))
            possibleXssAlert(ctx, req, res, tag, 0, "vinfo-xss-inject", "Injected XSS tag into HTML tag name");

        if (tag.equals("style") || (tag.equals("script") && !remoteScript)) {
            String content = elem.getTextContent();
            if (content != null)
                checkJavascriptXSS(ctx, req, res, content);
        }
    }

    private void possibleXssAlert(IInjectionModuleContext ctx, HttpUriRequest req, IHttpResponse res, String text,
            int offset, String type, String message) {
        final int[] xids = extractXssTag(text, offset);
        if (xids == null)
            return;
        final HttpUriRequest xssReq = ctx.getPathState().getXssRequest(xids[0], xids[1]);
        if (xssReq != null)
            alert(ctx, type, message, xssReq, res);
        else
            alert(ctx, type, message + " (from previous scan)", req, res);
    }

    private boolean match(String s, String... options) {
        if (s == null)
            return false;
        for (int i = 0; i < options.length; i++)
            if (s.equals(options[i]))
                return true;
        return false;
    }

    private int[] extractXssTag(String text, int offset) {
        //    3     9
        // vvv000000v000000
        if (text.length() < (offset + 16))
            return null;
        if (text.charAt(offset + 9) != 'v')
            return null;

        final int[] res = new int[2];
        res[0] = extractXssId(text, offset + 3);
        res[1] = extractXssId(text, offset + 10);
        if (res[0] == -1 || res[1] == -1)
            return null;
        else
            return res;

    }

    private int extractXssId(String text, int offset) {
        if (text.length() < (offset + 6))
            return -1;
        final String idStr = text.substring(offset, offset + 6);
        try {
            return Integer.parseInt(idStr);
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    private void checkJavascriptXSS(IInjectionModuleContext ctx, HttpUriRequest req, IHttpResponse res,
            String text) {
        if (text == null)
            return;
        int lastWordIdx = 0;
        int idx = 0;
        boolean inQuote = false;
        boolean prevSpace = true;
        while (idx < text.length()) {
            idx = maybeSkipJavascriptComment(text, idx);
            if (idx >= text.length())
                return;
            char c = text.charAt(idx);
            if (!inQuote && (c == '\'' || c == '"')) {
                inQuote = true;
                if (matchStartsWith(text, lastWordIdx, "innerHTML", "open", "url", "href", "write")
                        && matchStartsWith(text, idx + 1, "//vega.invalid/", "http://vega.invalid", "vega:")) {
                    alert(ctx, "vinfo-url-inject", "Injected URL in JS/CSS code", req, res);
                }
            } else if (c == '\'' || c == '"') {
                inQuote = false;
            } else if (!inQuote && text.startsWith("vvv", idx)) {
                possibleXssAlert(ctx, req, res, text, idx, "vinfo-xss-inject", "Injected syntax into JS/CSS code");
            } else if (Character.isWhitespace(c) || c == '.') {
                prevSpace = true;
            } else if (prevSpace && Character.isLetterOrDigit(c)) {
                lastWordIdx = idx;
                prevSpace = false;
            }
            idx += 1;
        }
    }

    private boolean matchStartsWith(String str, int offset, String... options) {
        for (int i = 0; i < options.length; i++) {
            if (str.startsWith(options[i], offset))
                return true;
        }
        return false;
    }

    private int maybeSkipJavascriptComment(String text, int idx) {
        final int max = text.length();
        final int savedIdx = idx;
        if (idx >= max)
            return idx;
        final char c = text.charAt(idx++);
        // Skip escaped characters here too
        if (c == '\\')
            return idx + 1;
        if (c != '/')
            return savedIdx;
        if (idx >= max)
            return idx;
        final char c1 = text.charAt(idx++);
        if (c1 == '/') {
            while (idx < max) {
                char cc = text.charAt(idx);
                if (cc == '\n' || c == '\r')
                    return idx;
                idx += 1;
            }
            return max;
        } else if (c1 == '*') {
            int end = text.indexOf("*/", idx);
            if (end == -1)
                return max;
            else
                return end + 2;
        }
        return savedIdx;

    }

    private void alert(IInjectionModuleContext ctx, String type, String message, HttpUriRequest request,
            IHttpResponse response) {
        final String resource = request.getURI().toString();
        final String key = createAlertKey(ctx, type, request);
        ctx.publishAlert(type, key, message, request, response, "resource", resource);
    }

    private String createAlertKey(IInjectionModuleContext ctx, String type, HttpUriRequest request) {
        if (ctx.getPathState().isParametric()) {
            final String uri = stripQuery(request.getURI()).toString();
            return type + ":" + uri + ":" + ctx.getPathState().getFuzzableParameter().getName();
        } else {
            return type + ":" + request.getURI();
        }
    }

    private URI stripQuery(URI uri) {
        try {
            return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null,
                    null);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e);
        }
    }
}