org.zaproxy.zap.extension.domxss.TestDomXSS.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.domxss.TestDomXSS.java

Source

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.UnhandledAlertException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.SessionNotFoundException;
import org.openqa.selenium.remote.UnreachableBrowserException;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.proxy.OverrideMessageProxyListener;
import org.parosproxy.paros.core.proxy.ProxyParam;
import org.parosproxy.paros.core.proxy.ProxyServer;
import org.parosproxy.paros.core.scanner.AbstractAppPlugin;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.core.scanner.Plugin;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.model.Vulnerabilities;
import org.zaproxy.zap.model.Vulnerability;
import org.zaproxy.zap.utils.Stats;

public class TestDomXSS extends AbstractAppPlugin {
    private static Vulnerability vuln = Vulnerabilities.getVulnerability("wasc_8");
    private static Logger log = Logger.getLogger(TestDomXSS.class);

    private static final String HASH_SCRIPT_ALERT = "#<script>alert(1)</script>";
    private static final String HASH_IMG_ALERT = "#<img src=\"random.gif\" onerror=alert(1)>";
    private static final String HASH_HASH_ALERT = "#abc#<script>alert(1)</script>";
    private static final String QUERY_IMG_ALERT = "?name=<img src=\"random.gif\" onerror=alert(1)>";
    private static final String HASH_HASH_IMG_ALERT = "#abc#<img src='random.gif' onerror=alert(1)";
    private static final String HASH_JAVASCRIPT_ALERT = "#javascript:alert(1)";
    private static final String HASH_ALERT = "#alert(1)";
    private static final String QUERY_HASH_IMG_ALERT = "?name=abc#<img src=\"random.gif\" onerror=alert(1)>";

    // In order of effectiveness vs benchmark apps
    public static final String[] ATTACK_STRINGS = { HASH_SCRIPT_ALERT, HASH_IMG_ALERT, HASH_HASH_ALERT,
            QUERY_IMG_ALERT, HASH_HASH_IMG_ALERT, HASH_JAVASCRIPT_ALERT, HASH_ALERT, QUERY_HASH_IMG_ALERT };

    private static Stack<WebDriverWrapper> freeFirefoxDrivers = new Stack<WebDriverWrapper>();
    private static List<WebDriverWrapper> takenFirefoxDrivers = new ArrayList<WebDriverWrapper>();

    private static Thread reaperThread = null;
    private static Object reaperThreadSync = new Object();

    private static ProxyServer proxy = null;
    private static int proxyPort = -1;

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

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

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

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

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

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

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

    @Override
    public void init() {
        getProxy();
    }

    /*
     * We use a separate port so that we dont polute the sites tree
     * and show the requests in the Active Scan tab 
     */
    private ProxyServer getProxy() {
        if (proxy == null) {
            proxy = new ProxyServer();
            proxy.setConnectionParam(Model.getSingleton().getOptionsParam().getConnectionParam());
            proxy.addOverrideMessageProxyListener(new OverrideMessageProxyListener() {

                @Override
                public int getArrangeableListenerOrder() {
                    return 0;
                }

                @Override
                public boolean onHttpRequestSend(HttpMessage msg) {
                    try {
                        // Ideally it should check that the message belongs to the scanned target before sending
                        sendAndReceive(msg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return true;
                }

                @Override
                public boolean onHttpResponseReceived(HttpMessage arg0) {
                    // Shouldn't be called, since the messages are being overridden
                    return true;
                }
            });
            proxyPort = proxy.startServer("127.0.0.1", 0, true);

        }
        return proxy;
    }

    private WebDriver getNewFirefoxDriver() {
        ProxyParam proxyParams = Model.getSingleton().getOptionsParam().getProxyParam();

        /*
         * TODO look at supporting other browsers
         * Notes:
         *    HtmlUnit just logs a _load_ of errors
         *    Chrome doesnt seem to find anything, possibly due to its XSS protection
         *    Phantom JS doesnt find anything as 'alerts' arent yet supported
         */

        //WebDriver driver = ExtensionSelenium.getWebDriver(
        //      Browser.FIREFOX, proxyParams.getProxyIp(), proxyPort);

        // Proxy through ZAP
        String zapProxy = proxyParams.getProxyIp() + ":" + proxyPort;
        Proxy proxy = new Proxy();
        proxy.setHttpProxy(zapProxy).setSslProxy(zapProxy);
        DesiredCapabilities cap = new DesiredCapabilities();
        cap.setCapability(CapabilityType.PROXY, proxy);
        WebDriver driver = new FirefoxDriver(cap);

        driver.manage().timeouts().pageLoadTimeout(10, TimeUnit.SECONDS);
        driver.manage().timeouts().setScriptTimeout(10, TimeUnit.SECONDS);

        return driver;

    }

    private WebDriverWrapper getFirefoxDriver() {
        WebDriverWrapper fxDriver;
        try {
            fxDriver = freeFirefoxDrivers.pop();
        } catch (Exception e) {
            // Proxy through ZAP
            ProxyParam proxyParams = Model.getSingleton().getOptionsParam().getProxyParam();
            String zapProxy = proxyParams.getProxyIp() + ":" + proxyParams.getProxyPort();
            Proxy proxy = new Proxy();
            proxy.setHttpProxy(zapProxy).setSslProxy(zapProxy);
            DesiredCapabilities cap = new DesiredCapabilities();
            cap.setCapability(CapabilityType.PROXY, proxy);
            fxDriver = new WebDriverWrapper(getNewFirefoxDriver());
        }
        synchronized (takenFirefoxDrivers) {
            takenFirefoxDrivers.add(fxDriver);
        }

        if (reaperThread == null) {
            synchronized (reaperThreadSync) {
                if (reaperThread == null) {
                    reaperThread = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            log.info("Reaper thread starting");
                            reaperThread.setName("ZAP-DomXssReaper");
                            do {
                                try {
                                    Thread.sleep(5000);
                                } catch (InterruptedException e) {
                                    // Ignore
                                }
                                Date now = new Date();
                                // concurrent modification exception :(
                                synchronized (takenFirefoxDrivers) {

                                    Iterator<WebDriverWrapper> iter = takenFirefoxDrivers.iterator();
                                    while (iter.hasNext()) {
                                        WebDriverWrapper wrapper = iter.next();
                                        if ((now.getTime() - wrapper.getLastAccessed().getTime()) / 1000 > 10) {
                                            log.debug("Driver hung " + wrapper.getDriver().hashCode());
                                            wrapper.getDriver().quit();
                                            wrapper.setDriver(getNewFirefoxDriver());
                                            log.debug("New driver " + wrapper.getDriver().hashCode());
                                        }
                                    }
                                }
                            } while (takenFirefoxDrivers.size() > 0);
                            log.info("Reaper thread exiting " + takenFirefoxDrivers.size());

                            reaperThread = null;
                        }
                    });
                    reaperThread.start();
                }
            }
        }
        return fxDriver;
    }

    private void returnFirefoxDriver(WebDriverWrapper fxDriver) {
        synchronized (takenFirefoxDrivers) {
            if (takenFirefoxDrivers.remove(fxDriver)) {
                freeFirefoxDrivers.push(fxDriver);
            } else {
                log.debug("Driver not in 'taken' list");
            }
        }
    }

    @Override
    public void setTimeFinished() {
        super.setTimeFinished();
        // Tidy up...
        // Dont kill drivers in the 'taken' list as there may be multiple scans
        WebDriverWrapper fxDriver;
        while (!freeFirefoxDrivers.isEmpty()) {
            try {
                fxDriver = freeFirefoxDrivers.pop();
                fxDriver.getDriver().quit();
            } catch (Exception e) {
                // Ignore
            }
        }
    }

    private void getHelper(WebDriverWrapper wrapper, String url) {
        this.getHelper(wrapper, url, 3);
    }

    private void getHelper(WebDriverWrapper wrapper, String url, int retry) {
        try {
            Stats.incCounter("domxss.gets.count");
            wrapper.getDriver().get(url);

        } catch (UnhandledAlertException uae) {
            throw uae;
        } catch (SessionNotFoundException enve) {
            // Pause, retry
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 
            }
            if (retry >= 0) {
                this.getHelper(wrapper, url, retry - 1);
            }
        } catch (UnreachableBrowserException ube) {
            // Pause, retry
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 
            }
            if (retry >= 0) {
                this.getHelper(wrapper, url, retry - 1);
            }
        } catch (ElementNotVisibleException enve) {
            log.debug(enve);
        } catch (TimeoutException wde) {
            log.debug(wde);
        } catch (WebDriverException wde) {
            log.debug(wde);
        }
    }

    private List<WebElement> findHelper(WebDriverWrapper wrapper, By by) {
        return this.findHelper(wrapper, by, 3);
    }

    private List<WebElement> findHelper(WebDriverWrapper wrapper, By by, int retry) {
        try {
            Stats.incCounter("domxss.gets.count");
            return wrapper.getDriver().findElements(by);

        } catch (UnhandledAlertException uae) {
            throw uae;
        } catch (SessionNotFoundException enve) {
            // Pause, retry
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // Ignore
            }
            if (retry >= 0) {
                return this.findHelper(wrapper, by, retry - 1);
            }
        } catch (UnreachableBrowserException ube) {
            // Pause, retry
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // Ignore
            }
            if (retry >= 0) {
                return this.findHelper(wrapper, by, retry - 1);
            }
        } catch (ElementNotVisibleException enve) {
            log.debug(enve);
        } catch (TimeoutException wde) {
            log.debug(wde);
        } catch (WebDriverException wde) {
            log.debug(wde);
        }
        return new ArrayList<WebElement>();
    }

    private void scanHelper(WebDriverWrapper driver, String attackVector, String url) throws DomAlertException {
        if (this.isStop()) {
            return;
        }

        try {
            getHelper(driver, url);
        } catch (UnhandledAlertException uae) {
            Stats.incCounter("domxss.vulns.get1");
            throw new DomAlertException(url, attackVector);
        }

        List<WebElement> inputElements;

        try {
            inputElements = findHelper(driver, By.tagName("input"));
        } catch (UnhandledAlertException uae) {
            Stats.incCounter("domxss.vulns.input1");
            throw new DomAlertException(url, attackVector);
        }

        for (int i = 0; i < inputElements.size(); i++) {
            if (this.isStop()) {
                return;
            }
            WebElement element = inputElements.get(i);
            String tagName = null;
            String attributeId = null;
            String attributeName = null;

            try {
                // Save for the evidence
                tagName = element.getTagName();
                attributeId = element.getAttribute("id");
                attributeName = element.getAttribute("name");

                element.sendKeys(attackVector);
                element.click();
            } catch (UnhandledAlertException uae) {
                Stats.incCounter("domxss.vulns.input2");
                throw new DomAlertException(url, attackVector, tagName, attributeId, attributeName);
            } catch (WebDriverException wde) {
                log.debug(wde);
            }
            try {
                getHelper(driver, url);
            } catch (UnhandledAlertException uae) {
                Stats.incCounter("domxss.vulns.get2");
                throw new DomAlertException(url, attackVector, tagName, attributeId, attributeName);
            }
            try {
                inputElements = findHelper(driver, By.tagName("input"));
            } catch (UnhandledAlertException uae) {
                Stats.incCounter("domxss.vulns.input3");
                throw new DomAlertException(url, attackVector, tagName, attributeId, attributeName);
            }
        }
        List<WebElement> allElements;
        try {
            allElements = findHelper(driver, By.tagName("div"));
        } catch (UnhandledAlertException uae) {
            Stats.incCounter("domxss.vulns.div1");
            throw new DomAlertException(url, attackVector);
        }
        for (int i = 0; i < allElements.size(); i++) {
            if (this.isStop()) {
                return;
            }
            WebElement element = allElements.get(i);
            String tagName = null;
            String attributeId = null;
            String attributeName = null;
            try {
                // Save for the evidence
                tagName = element.getTagName();
                attributeId = element.getAttribute("id");
                attributeName = element.getAttribute("name");

                element.click();
                getHelper(driver, url);
                allElements = findHelper(driver, By.tagName("div"));

            } catch (UnhandledAlertException uae) {
                Stats.incCounter("domxss.vulns.div2");
                throw new DomAlertException(url, attackVector, tagName, attributeId, attributeName);
            } catch (SessionNotFoundException enve) {
                log.debug(enve);
                // replaceDriver(driver);
            } catch (ElementNotVisibleException enve) {
                log.debug(enve);
            } catch (TimeoutException wde) {
                log.debug(wde);
            } catch (WebDriverException wde) {
                log.debug(wde);
            }
        }
    }

    @Override
    public void scan() {
        Stats.incCounter("domxss.scan.count");
        ArrayList<String> attackVectors = new ArrayList<String>();
        int numberOfAttackStrings;

        switch (this.getAttackStrength()) {
        case LOW:
            numberOfAttackStrings = 2;
            break;
        case MEDIUM:
        default:
            numberOfAttackStrings = 4;
            break;
        case HIGH:
            numberOfAttackStrings = 6;
            break;
        case INSANE:
            numberOfAttackStrings = ATTACK_STRINGS.length;
            break;
        }

        for (int i = 0; i < numberOfAttackStrings; i++) {
            attackVectors.add(ATTACK_STRINGS[i]);
        }

        ArrayList<WebDriverWrapper> drivers = new ArrayList<WebDriverWrapper>();

        WebDriverWrapper fxDriver = this.getFirefoxDriver();
        drivers.add(fxDriver);

        try {
            for (String attackVector : attackVectors) {
                if (scan(drivers, attackVector)) {
                    if (!Plugin.AlertThreshold.LOW.equals(this.getAlertThreshold())) {
                        // Only report one issue per URL unless 
                        // Alert threshold is LOW
                        break;
                    }
                }
            }
        } finally {
            this.returnFirefoxDriver(fxDriver);
        }
    }

    public boolean scan(ArrayList<WebDriverWrapper> drivers, String attackVector) {
        HttpMessage msg = getBaseMsg();
        String url = msg.getRequestHeader().getURI().toString();
        String currURL = url + attackVector;

        for (WebDriverWrapper driver : drivers) {
            try {
                scanHelper(driver, attackVector, currURL);
            } catch (DomAlertException e) {
                String tagName = e.getTagName();
                String otherInfo = "";
                if (tagName != null) {
                    otherInfo = "Tag name: " + tagName + " Att name: " + e.getAttributeName() + " Att id: "
                            + e.getAttributeId();
                }

                bingo(Alert.RISK_HIGH, Alert.CONFIDENCE_MEDIUM, e.getUrl(), null, e.getAttack(), otherInfo, null,
                        msg);
                Stats.incCounter("domxss.attack." + attackVector);
                return true;
            }
        }
        return false;
    }

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

    @Override
    public int getCweId() {
        return 79;
    }

    @Override
    public int getWascId() {
        return 8;
    }

    @Override
    public AttackStrength[] getAttackStrengthsSupported() {
        return new AttackStrength[] { AttackStrength.LOW, AttackStrength.MEDIUM, AttackStrength.HIGH,
                AttackStrength.INSANE };
    }

    @Override
    public AlertThreshold[] getAlertThresholdsSupported() {
        return new AlertThreshold[] { AlertThreshold.LOW, AlertThreshold.MEDIUM };
    }

}