org.zaproxy.zap.extension.quickstart.ExtensionQuickStart.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.quickstart.ExtensionQuickStart.java

Source

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

import java.awt.Container;
import java.io.BufferedWriter;
import java.io.File;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Vector;
import javax.swing.ComboBoxModel;
import javax.swing.ImageIcon;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.control.Control.Mode;
import org.parosproxy.paros.extension.CommandLineArgument;
import org.parosproxy.paros.extension.CommandLineListener;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.OptionsChangedListener;
import org.parosproxy.paros.extension.SessionChangedListener;
import org.parosproxy.paros.extension.report.ReportLastScan;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.model.OptionsParam;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpSender;
import org.parosproxy.paros.network.HttpStatusCode;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.Version;
import org.zaproxy.zap.extension.ext.ExtensionExtension;
import org.zaproxy.zap.extension.help.ExtensionHelp;
import org.zaproxy.zap.utils.DisplayUtils;
import org.zaproxy.zap.utils.ZapXmlConfiguration;

public class ExtensionQuickStart extends ExtensionAdaptor
        implements SessionChangedListener, CommandLineListener, OptionsChangedListener {

    public static final String NAME = "ExtensionQuickStart";
    public static final String RESOURCES = "/org/zaproxy/zap/extension/quickstart/resources";
    public static ImageIcon ZAP_ICON = DisplayUtils
            .getScaledIcon(new ImageIcon(QuickStartSubPanel.class.getResource(RESOURCES + "/zap64x64.png")));
    public static ImageIcon HUD_ICON = DisplayUtils
            .getScaledIcon(new ImageIcon(QuickStartSubPanel.class.getResource(RESOURCES + "/hud_logo_64px.png")));
    public static ImageIcon HELP_ICON = DisplayUtils
            .getScaledIcon(new ImageIcon(QuickStartSubPanel.class.getResource(RESOURCES + "/help.png")));
    public static ImageIcon ONLINE_DOC_ICON = DisplayUtils
            .getScaledIcon(new ImageIcon(QuickStartSubPanel.class.getResource(RESOURCES + "/document-globe.png")));
    public static ImageIcon PDF_DOC_ICON = DisplayUtils.getScaledIcon(
            new ImageIcon(QuickStartSubPanel.class.getResource(RESOURCES + "/document-pdf-text.png")));

    protected static final String SCRIPT_CONSOLE_HOME_PAGE = Constant.ZAP_HOMEPAGE;

    private static final String DEFAULT_NEWS_PAGE_URL_PREFIX = "https://bit.ly/owaspzap-news-";
    private static final String DEV_NEWS_PAGE = "dev";

    private static final Logger LOGGER = Logger.getLogger(ExtensionQuickStart.class);

    private QuickStartPanel quickStartPanel = null;
    private AttackThread attackThread = null;
    private PlugableSpider plugableSpider;
    private PlugableHud hudProvider;
    private QuickStartParam quickStartParam;

    private CommandLineArgument[] arguments = new CommandLineArgument[3];
    private static final int ARG_QUICK_URL_IDX = 0;
    private static final int ARG_QUICK_OUT_IDX = 1;
    private static final int ARG_QUICK_PROGRESS_IDX = 2;
    private static final String SPIN_CHRS = "|/-\\|/-\\";

    private boolean runningFromCmdLine = false;
    private boolean showProgress = false;
    private int spinner = 0;

    public ExtensionQuickStart() {
        super(NAME);
    }

    @Override
    public boolean supportsDb(String type) {
        return true;
    }

    @Override
    public void hook(ExtensionHook extensionHook) {
        super.hook(extensionHook);

        extensionHook.addOptionsChangedListener(this);
        extensionHook.addOptionsParamSet(getQuickStartParam());

        if (getView() != null) {
            extensionHook.getHookView().addWorkPanel(getQuickStartPanel());

            ExtensionHelp.enableHelpKey(getQuickStartPanel(), "quickstart");
        }
        extensionHook.addSessionListener(this);

        extensionHook.addCommandLine(getCommandLineArguments());
    }

    @Override
    public boolean canUnload() {
        return true;
    }

    @Override
    public void optionsLoaded() {
        super.optionsLoaded();
        if (View.isInitialised()) {
            getQuickStartPanel().optionsLoaded(this.getQuickStartParam());
        }

        // Check for silent mode - only available in 2.8.0 so must use reflection for now
        try {
            Method isSilentMethod = Constant.class.getMethod("isSilent");
            Object res = isSilentMethod.invoke(null);
            if (res instanceof Boolean) {
                if ((Boolean) res) {
                    LOGGER.info("Shh! No check-for-news - silent mode enabled");
                    return;
                }
            }
        } catch (Exception e) {
            // Ignore
        }

        new Thread("ZAP-NewsFetcher") {
            @Override
            public void run() {
                // Try to read the news page
                HttpMessage msg;
                String newsPageUrl = getNewsPageURL();
                try {
                    HttpSender httpSender = new HttpSender(
                            Model.getSingleton().getOptionsParam().getConnectionParam(), true,
                            HttpSender.CHECK_FOR_UPDATES_INITIATOR);
                    httpSender.setFollowRedirect(true);
                    msg = new HttpMessage(new URI(newsPageUrl, true),
                            Model.getSingleton().getOptionsParam().getConnectionParam());
                    httpSender.sendAndReceive(msg, true);
                    if (msg.getResponseHeader().getStatusCode() == HttpStatusCode.OK) {
                        String zapLocale = Constant.getLocale().toString();

                        // Safely parse the XML
                        ZapXmlConfiguration xmlNews = new ZapXmlConfiguration();
                        xmlNews.load(new StringReader(msg.getResponseBody().toString()));

                        ConfigurationNode newsNode = getFirstChildNode(xmlNews.getRoot(), "news");
                        if (newsNode != null) {
                            String id = getFirstChildNodeString(newsNode, "id");
                            ConfigurationNode localeNode = getFirstChildNode(newsNode, zapLocale);
                            if (localeNode == null) {
                                localeNode = getFirstChildNode(newsNode, "default");
                            }
                            if (localeNode != null) {
                                String itemText = getFirstChildNodeString(localeNode, "item");

                                if (itemText != null && itemText.length() > 0) {
                                    announceNews(new NewsItem(id, itemText,
                                            new URI(getFirstChildNodeString(localeNode, "link"), true)));
                                }
                            }
                        }

                    } else {
                        LOGGER.debug(
                                "Response from " + newsPageUrl + " : " + msg.getResponseHeader().getStatusCode());
                    }
                } catch (Exception e) {
                    LOGGER.debug("Failed to read from " + newsPageUrl + " : " + e.getMessage(), e);
                }
            }
        }.start();
    }

    private ConfigurationNode getFirstChildNode(ConfigurationNode node, String childName) {
        List<ConfigurationNode> list = node.getChildren(childName);
        if (list.size() > 0) {
            return list.get(0);
        }
        return null;
    }

    private String getFirstChildNodeString(ConfigurationNode node, String childName) {
        ConfigurationNode child = this.getFirstChildNode(node, childName);
        if (child != null) {
            return child.getValue().toString();
        }
        return null;
    }

    private String getNewsPageURL() {
        String page = DEV_NEWS_PAGE;
        if (!Constant.isDevBuild() && !Constant.isDailyBuild()) {
            // Converts the ZAP version to something like 2-8
            try {
                Version zapVersion = new Version(Constant.PROGRAM_VERSION);
                page = zapVersion.getMajorVersion() + "-" + zapVersion.getMinorVersion();
            } catch (IllegalArgumentException e) {
                LOGGER.error("Failed to parse ZAP version " + Constant.PROGRAM_VERSION, e);
            }
        }

        return DEFAULT_NEWS_PAGE_URL_PREFIX + page;
    }

    private void announceNews(NewsItem newsItem) {
        if (View.isInitialised()) {
            if (!this.getQuickStartParam().getClearedNewsItem().equals(newsItem.getId())) {
                getQuickStartPanel().announceNews(newsItem);
            }
        }
    }

    public QuickStartParam getQuickStartParam() {
        if (quickStartParam == null) {
            quickStartParam = new QuickStartParam();
        }
        return quickStartParam;
    }

    public void setLaunchPanel(QuickStartSubPanel panel) {
        if (quickStartPanel != null) {
            quickStartPanel.setExplorePanel(panel);
        }
    }

    public void addPlugableSpider(PlugableSpider pe) {
        this.plugableSpider = pe;
        if (quickStartPanel != null) {
            quickStartPanel.addPlugableSpider(pe);
        }
    }

    public void removePlugableSpider(PlugableSpider pe) {
        this.plugableSpider = pe;
        if (quickStartPanel != null) {
            quickStartPanel.removePlugableSpider(pe);
        }
    }

    public QuickStartPanel getQuickStartPanel() {
        if (quickStartPanel == null) {
            quickStartPanel = new QuickStartPanel(this);
            quickStartPanel.setName(Constant.messages.getString("quickstart.panel.title"));
            // Force it to be the first one
            quickStartPanel.setTabIndex(0);
            if (this.plugableSpider != null) {
                quickStartPanel.addPlugableSpider(this.plugableSpider);
            }
        }
        return quickStartPanel;
    }

    @Override
    public String getAuthor() {
        return Constant.ZAP_TEAM;
    }

    @Override
    public String getDescription() {
        return Constant.messages.getString("quickstart.desc");
    }

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

    @Override
    public URL getURL() {
        try {
            return new URL(Constant.ZAP_HOMEPAGE);
        } catch (MalformedURLException e) {
            return null;
        }
    }

    public void attack(URL url, boolean useStdSpider) {
        if (attackThread != null && attackThread.isAlive()) {
            return;
        }
        attackThread = new AttackThread(this, useStdSpider);
        attackThread.setURL(url);
        attackThread.setPlugableSpider(plugableSpider);
        attackThread.start();
    }

    public void notifyProgress(AttackThread.Progress progress) {
        this.notifyProgress(progress, (String) null);
    }

    public void notifyProgress(AttackThread.Progress progress, String msg) {
        if (View.isInitialised()) {
            this.getQuickStartPanel().getAttackPanel().notifyProgress(progress, msg);
        } else if (this.runningFromCmdLine && this.showProgress) {
            if (msg != null) {
                System.out.println(msg);
            } else {
                System.out.println(Constant.messages.getString("quickstart.cmdline.progress." + progress.name()));
            }
        }
        switch (progress) {
        case notstarted:
        case started:
        case spider:
        case ajaxspider:
        case ascan:
            this.runningFromCmdLine = true;
            break;
        case failed:
        case complete:
        case stopped:
            this.runningFromCmdLine = false;
            break;
        }
    }

    public void notifyProgress(AttackThread.Progress progress, int percent) {
        if (this.runningFromCmdLine && this.showProgress) {
            int scale = 5; // 20 chrs seems about right..
            System.out.print("[");
            for (int i = 0; i < 100 / scale; i++) {
                if (i + 1 <= percent / scale) {
                    System.out.print("=");
                } else {
                    System.out.print(" ");
                }
            }
            System.out.print("] " + percent + "% ");
            if (percent < 100) {
                // Print out a v simple spinner so its obvious something is still happening
                System.out.print(SPIN_CHRS.charAt(this.spinner % SPIN_CHRS.length()) + "\r");
                this.spinner++;
            } else {
                System.out.print("\n");
                this.spinner = 0;
            }
        }
    }

    public void stopAttack() {
        if (attackThread != null) {
            attackThread.stopAttack();
            attackThread = null;
        }
    }

    public void showOnStart(boolean showOnStart) {
        if (!showOnStart) {
            // Remove the tab right away
            Container parent = this.getQuickStartPanel().getParent();
            parent.remove(this.getQuickStartPanel());
        }

        // Save in configs
        ExtensionExtension extExt = (ExtensionExtension) Control.getSingleton().getExtensionLoader()
                .getExtension(ExtensionExtension.NAME);
        if (extExt != null) {
            extExt.enableExtension(NAME, showOnStart);
        }
    }

    @Override
    public void sessionAboutToChange(Session arg0) {
        stopAttack();
    }

    @Override
    public void sessionChanged(Session arg0) {
        // Ignore
    }

    @Override
    public void sessionModeChanged(Mode mode) {
        if (getView() != null) {
            this.getQuickStartPanel().getAttackPanel().setMode(mode);
        }
    }

    @Override
    public void sessionScopeChanged(Session arg0) {
        // Ignore
    }

    @Override
    public void execute(CommandLineArgument[] args) {
        if (arguments[ARG_QUICK_URL_IDX].isEnabled()) {
            Vector<String> params = arguments[ARG_QUICK_URL_IDX].getArguments();
            if (params.size() == 1) {
                QuickAttacker quickAttacker;
                if (View.isInitialised()) {
                    quickAttacker = new UIQuickAttacker();
                } else {
                    quickAttacker = new HeadlessQuickAttacker();
                }

                if (!quickAttacker.attack(params.get(0))) {
                    return;
                }

                this.runningFromCmdLine = true;

                if (arguments[ARG_QUICK_PROGRESS_IDX].isEnabled()) {
                    this.showProgress = true;
                }

                while (this.runningFromCmdLine) {
                    // Loop until the attack thread completes
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ignore) {
                    }
                }

                if (arguments[ARG_QUICK_OUT_IDX].isEnabled()) {
                    quickAttacker.saveReport(Paths.get(arguments[ARG_QUICK_OUT_IDX].getArguments().get(0)));
                } else {
                    quickAttacker.handleNoSavedReport();
                }
            }
        } else {
            return;
        }
    }

    private void saveReportTo(Path file) throws Exception {
        try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
            writer.write(getScanReport());
        }
    }

    private String getScanReport() throws Exception {
        ReportLastScan report = new ReportLastScan();
        StringBuilder rpt = new StringBuilder();
        report.generate(rpt, getModel());
        return rpt.toString();
    }

    private CommandLineArgument[] getCommandLineArguments() {
        arguments[ARG_QUICK_URL_IDX] = new CommandLineArgument("-quickurl", 1, null, "",
                "-quickurl [target url]: " + Constant.messages.getString("quickstart.cmdline.url.help"));
        arguments[ARG_QUICK_OUT_IDX] = new CommandLineArgument("-quickout", 1, null, "",
                "-quickout [output filename]: " + Constant.messages.getString("quickstart.cmdline.out.help"));
        arguments[ARG_QUICK_PROGRESS_IDX] = new CommandLineArgument("-quickprogress", 0, null, "",
                "-quickprogress: " + Constant.messages.getString("quickstart.cmdline.progress.help"));
        return arguments;
    }

    @Override
    public List<String> getHandledExtensions() {
        return null;
    }

    @Override
    public boolean handleFile(File file) {
        // Not supported
        return false;
    }

    private abstract static class QuickAttacker {

        public abstract boolean attack(String url);

        protected final boolean isValid(Path file) {
            if (Files.notExists(file)) {
                if (file.getParent() == null || !Files.isWritable(file.getParent())) {
                    reportError(Constant.messages.getString("quickstart.cmdline.quickout.error.dirNotWritable",
                            file.getParent() == null ? file.toAbsolutePath()
                                    : file.getParent().toAbsolutePath().normalize()));
                    return false;
                }
            } else if (!Files.isRegularFile(file)) {
                reportError(Constant.messages.getString("quickstart.cmdline.quickout.error.notAFile",
                        file.toAbsolutePath().normalize()));
                return false;
            } else if (!Files.isWritable(file)) {
                reportError(Constant.messages.getString("quickstart.cmdline.quickout.error.fileNotWritable",
                        file.toAbsolutePath().normalize()));
                return false;
            }

            return true;
        }

        protected abstract void reportError(String error);

        public abstract void saveReport(Path file);

        public abstract void handleNoSavedReport();
    }

    private class UIQuickAttacker extends QuickAttacker {

        @Override
        public boolean attack(String url) {
            getQuickStartPanel().getAttackPanel().setAttackUrl(url);
            return getQuickStartPanel().getAttackPanel().attackUrl();
        }

        @Override
        protected void reportError(String error) {
            View.getSingleton().showWarningDialog(error);
        }

        @Override
        public void saveReport(Path file) {
            if (!isValid(file)) {
                return;
            }
            try {
                saveReportTo(file);
                View.getSingleton().showMessageDialog(Constant.messages.getString(
                        "quickstart.cmdline.quickout.save.report.successful", file.toAbsolutePath().normalize()));
            } catch (Exception e) {
                reportError(Constant.messages.getString("quickstart.cmdline.quickout.error.save.report"));
                LOGGER.error("Failed to generate report: ", e);
            }
        }

        @Override
        public void handleNoSavedReport() {
            // Do nothing, the user has the UI to generate the report if (s)he wants to.
        }
    }

    private class HeadlessQuickAttacker extends QuickAttacker {

        @Override
        public boolean attack(String url) {
            URL targetURL;
            try {
                targetURL = new URL(url);
                // Validate the actual request-uri of the HTTP message accessed.
                new URI(url, true);
            } catch (MalformedURLException | URIException e) {
                reportError(Constant.messages.getString("quickstart.cmdline.quickurl.error.invalidUrl"));
                e.printStackTrace();
                return false;
            }

            ExtensionQuickStart.this.attack(targetURL, true);
            return true;
        }

        @Override
        protected void reportError(String error) {
            System.out.println(error);
        }

        @Override
        public void saveReport(Path file) {
            System.out.println(
                    Constant.messages.getString("quickstart.cmdline.outputto", file.toAbsolutePath().toString()));

            if (!isValid(file)) {
                return;
            }

            try {
                saveReportTo(file);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void handleNoSavedReport() {
            try {
                // Just output to stdout
                System.out.println(getScanReport());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void backToMainPanel() {
        this.getQuickStartPanel().backToMainPanel();
    }

    public void setHudProvider(PlugableHud hp) {
        this.hudProvider = hp;
    }

    public PlugableHud getHudProvider() {
        return this.hudProvider;
    }

    @Override
    public void optionsChanged(OptionsParam optionsParam) {
        this.getQuickStartPanel().optionsChanged(optionsParam);
    }

    public ComboBoxModel<String> getUrlModel() {
        return this.getQuickStartPanel().getUrlModel();
    }
}