com.redcanari.ui.WebKitBrowser.java Source code

Java tutorial

Introduction

Here is the source code for com.redcanari.ui.WebKitBrowser.java

Source

/*
 * BurpKit - WebKit-based penetration testing plugin for BurpSuite
 * Copyright (C) 2015  Red Canari, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.redcanari.ui;

import burp.BurpExtender;
import burp.IMessageEditorController;
import com.dlsc.trafficbrowser.beans.Traffic;
import com.dlsc.trafficbrowser.scene.control.TrafficBrowser;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.redcanari.js.BurpExtenderCallbacksBridge;
import com.redcanari.js.BurpKitBridge;
import com.redcanari.ui.font.FontAwesome;
import com.redcanari.util.ResourceUtils;
import com.sun.javafx.scene.web.Debugger;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebErrorEvent;
import javafx.scene.web.WebEvent;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.util.Callback;
import netscape.javascript.JSObject;

import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by ndouba on 2014-06-01.
 */
public class WebKitBrowser extends JFXPanel {

    //    private LocalJSObject locals;
    //    private LocalJSObject globals;
    private static final SimpleBooleanProperty isProxyingEnabled = new SimpleBooleanProperty(false);
    static {
        isProxyingEnabled.addListener((observable, oldValue, newValue) -> {
            if (newValue) {
                System.setProperty("http.proxySet", "true");
                System.setProperty("http.proxyHost", "127.0.0.1");
                System.setProperty("http.proxyPort", "8080");
                System.setProperty("https.proxySet", "true");
                System.setProperty("https.proxyHost", "127.0.0.1");
                System.setProperty("https.proxyPort", "8080");
            } else {
                System.clearProperty("http.proxySet");
                System.clearProperty("http.proxyHost");
                System.clearProperty("http.proxyPort");
                System.clearProperty("https.proxySet");
                System.clearProperty("https.proxyHost");
                System.clearProperty("https.proxyPort");
            }
        });
    }

    private WebEngine webEngine;
    private WebView webView;
    private Scene scene;
    private IMessageEditorController controller;
    private CollapsibleSplitPane masterDetailPane;
    private ToolBar toolBar;
    private StatusBar statusBar;
    private Button firebugButton;
    private ToggleButton consoleToggleButton;
    private ToggleButton showAlertsToggleButton;
    private ToggleButton proxyRequestsToggleButton;
    private AnchorPane webViewAnchorPane;
    private Button screenShotButton;
    private BorderPane masterPane;
    private String originalUserAgent;
    private WebURLField urlTextField;
    private TrafficBrowser trafficBrowser;
    private TabPane detailPane;
    private boolean enabled = false;
    private BurpKitBridge javaScriptHelpers;

    private PageResourcesTab pageResourcesTab;
    private JavaScriptConsoleTab javaScriptConsoleTab;
    private CrossSiteScriptingTrackerTab crossSiteScriptingTrackerTab;

    private final StringProperty numberOfAlerts = new SimpleStringProperty("0");
    private final SimpleBooleanProperty showAlerts = new SimpleBooleanProperty(false);

    private final BooleanProperty isDetailNodeVisible = new SimpleBooleanProperty(true);
    private final List<EventHandler<WebEvent<String>>> alertListeners = new ArrayList<>();
    private final List<EventHandler<WebErrorEvent>> errorListeners = new ArrayList<>();

    private final String selectionScript;
    private final String firebugScript;
    private JSInputDialog promptDialog;

    public WebKitBrowser() {
        selectionScript = ResourceUtils.getResourceContentsAsString("/scripts/selectHTML.js");
        firebugScript = ResourceUtils.getResourceContentsAsString("/scripts/launchFirebug.js");
        init();
    }

    public WebKitBrowser(boolean enabled) {
        this.enabled = enabled;
        selectionScript = ResourceUtils.getResourceContentsAsString("/scripts/selectHTML.js");
        firebugScript = ResourceUtils.getResourceContentsAsString("/scripts/launchFirebug.js");
        init();
    }

    public WebKitBrowser(IMessageEditorController controller) {
        this();
        this.controller = controller;
    }

    public WebKitBrowser(IMessageEditorController controller, boolean enabled) {
        this(enabled);
        this.controller = controller;
    }

    private void init() {
        Platform.setImplicitExit(false);
        Platform.runLater(this::createScene);
    }

    //    private void addTab(TabPane tabPane, String title, Node node) {
    //        Tab tab = new Tab(title);
    //        tab.setContent(node);
    //        tab.setClosable(false);
    //        tabPane.getTabs().add(tab);
    //    }

    private void createScene() {

        // Fixes issue with blank BurpKitty tabs
        if (Thread.currentThread().getContextClassLoader() == null) {
            System.err.println("Warning: context class loader for JFX thread returned null.");
            Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
        }

        createMasterPane();
        createDetailPane();
        masterDetailPane = new CollapsibleSplitPane(masterPane, detailPane);
        masterDetailPane.setOrientation(Orientation.VERTICAL);
        masterDetailPane.expandedProperty().bind(isDetailNodeVisible);

        scene = new Scene(masterDetailPane);

        setScene(scene);
    }

    private void createDetailPane() {
        detailPane = new TabPane();

        javaScriptConsoleTab = new JavaScriptConsoleTab(webEngine);
        addErrorListener(javaScriptConsoleTab::handleError);
        addAlertListener(javaScriptConsoleTab::handleAlert);

        crossSiteScriptingTrackerTab = new CrossSiteScriptingTrackerTab(webEngine);
        addAlertListener(crossSiteScriptingTrackerTab::handleAlert);

        pageResourcesTab = new PageResourcesTab(webEngine);

        promptDialog = new JSInputDialog();

        Tab javaScriptEditorTab = new Tab("BurpScript IDE");
        javaScriptEditorTab.selectedProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue)
                masterDetailPane.setDividerPositions(0.5);
        });
        JavaScriptEditor javaScriptEditor = new JavaScriptEditor(webEngine, controller, false);
        javaScriptEditor.setJavaScriptConsoleTab(javaScriptConsoleTab);
        javaScriptEditorTab.setContent(javaScriptEditor);

        Tab trafficBrowserTab = new Tab("Network");
        trafficBrowser = new TrafficBrowser();
        trafficBrowserTab.setContent(trafficBrowser);

        Debugger debugger = webEngine.impl_getDebugger();
        debugger.setEnabled(true);
        debugger.sendMessage("{\"id\": 1, \"method\":\"Network.enable\"}");
        debugger.setMessageCallback(new Callback<String, Void>() {

            ConcurrentHashMap<String, Traffic> trafficState = new ConcurrentHashMap<>();

            @Override
            public Void call(String param) {
                JsonParser parser = new JsonParser();
                JsonObject object = parser.parse(param).getAsJsonObject();

                String method = object.get("method").getAsString();
                JsonObject params = object.getAsJsonObject("params");
                JsonObject request = params.getAsJsonObject("request");
                JsonObject response = params.getAsJsonObject("response");
                String requestId = params.get("requestId").getAsString();

                Instant timeStamp;
                JsonElement epochObject = params.get("timestamp");
                if (epochObject != null) {
                    double epoch = epochObject.getAsDouble();
                    timeStamp = Instant.ofEpochSecond((long) Math.floor(epoch),
                            (long) (epoch * 1000000000 % 1000000000));
                } else {
                    timeStamp = Instant.now();
                }

                Traffic traffic = null;

                switch (method) {
                case "Network.requestWillBeSent":
                    URL url = null;
                    String urlString = request.get("url").getAsString();

                    try {
                        url = new URL(urlString);
                    } catch (MalformedURLException e) {
                        //                            e.printStackTrace();
                    }
                    trafficState.put(requestId,
                            new Traffic((url == null) ? urlString : url.getFile(), timeStamp,
                                    (url == null) ? "" : url.getHost(), request.get("method").getAsString(),
                                    params.get("documentURL").getAsString()));
                    break;
                case "Network.responseReceived":
                    traffic = trafficState.get(requestId);
                    JsonObject headers = response.getAsJsonObject("headers");
                    JsonElement contentType = headers.get("Content-Type");
                    JsonElement contentLength = headers.get("Content-Length");
                    traffic.setType((contentType == null) ? "" : contentType.getAsString());
                    JsonElement requestLine = headers.get("");
                    if (requestLine != null) {
                        String[] requestLineParts = requestLine.getAsString().split(" ", 3);
                        traffic.setStatusCode(new Integer(requestLineParts[1]));
                        traffic.setStatusText(requestLineParts[2]);
                        traffic.setSize((contentLength == null) ? "0" : contentLength.getAsString());
                    } else {
                        traffic.setStatusCode(200);
                        traffic.setStatusText("OK");
                        traffic.setSize("0");
                    }
                    break;
                case "Network.loadingFinished":
                    traffic = trafficState.get(requestId);
                    traffic.setEndTime(timeStamp);
                    trafficBrowser.getTraffic().add(traffic);
                    trafficState.remove(requestId);
                    if (traffic.getEndTime().isAfter(trafficBrowser.getEndTime())) {
                        trafficBrowser.setEndTime(traffic.getEndTime());
                    }
                }
                return null;
            }
        });

        detailPane.getTabs().addAll(javaScriptConsoleTab, crossSiteScriptingTrackerTab, pageResourcesTab,
                trafficBrowserTab, javaScriptEditorTab
        //                new ImagesTab(webEngine)
        );

        detailPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
    }

    private void createMasterPane() {
        webView = new WebView();

        webViewAnchorPane = new AnchorPane(webView);
        AnchorPane.setBottomAnchor(webView, 0.0);
        AnchorPane.setTopAnchor(webView, 0.0);
        AnchorPane.setLeftAnchor(webView, 0.0);
        AnchorPane.setRightAnchor(webView, 0.0);

        webEngine = webView.getEngine();

        //        dialog = Dialogs.create()
        //                .lightweight()
        //                .modal()
        //                .owner(webView);

        //        locals = new LocalJSObject(webEngine);
        //        globals = GlobalJSObject.getGlobalJSObject(webEngine);
        javaScriptHelpers = new BurpKitBridge(webEngine);
        originalUserAgent = webEngine.getUserAgent();
        webEngine.setJavaScriptEnabled(true);
        webEngine.setOnAlert(this::handleAlert);
        webEngine.setOnError(this::handleError);
        webEngine.setPromptHandler(promptData -> {
            if (showAlerts.getValue())
                return promptDialog.prompt(promptData);
            return "Prompt disabled (toggle alert button to see prompts).";
        });
        webEngine.setConfirmHandler(param -> true);
        webEngine.getLoadWorker().stateProperty().addListener(this::workerStateChanged);

        createToolBar();

        createStatusBar();

        webEngine.load("about:blank");

        masterPane = new BorderPane();
        masterPane.setTop(toolBar);
        masterPane.setCenter(webViewAnchorPane);
        masterPane.setBottom(statusBar);

    }

    private void createStatusBar() {
        statusBar = new StatusBar();
        statusBar.setText("Alerts");

        Button alertsButton = new Button();
        alertsButton.textProperty().bind(numberOfAlerts);
        alertsButton
                .setBackground(new Background(new BackgroundFill(Color.ORANGE, new CornerRadii(2), new Insets(4))));
        alertsButton.setOnAction(event -> {
            isDetailNodeVisible.setValue(true);
            detailPane.getSelectionModel().select(0);
        });

        statusBar.getLeftItems().add(alertsButton);
        statusBar.progressProperty().bind(webEngine.getLoadWorker().progressProperty());
    }

    private void createToolBar() {
        createUrlTextField();
        createShowConsoleButton();
        createShowAlertsButton();
        createFirebugButton();
        createScreenShotButton();
        createProxyRequestsButton();

        toolBar = new ToolBar();

        toolBar.getItems().addAll(urlTextField, consoleToggleButton, showAlertsToggleButton, firebugButton,
                screenShotButton, proxyRequestsToggleButton);
    }

    private void createUrlTextField() {
        urlTextField = new WebURLField(webEngine);
        urlTextField.setEditable(enabled);
        HBox.setHgrow(urlTextField, Priority.ALWAYS);
    }

    private void createShowConsoleButton() {
        consoleToggleButton = new ToggleButton(FontAwesome.ICON_TERMINAL);
        consoleToggleButton.setFont(Font.font("FontAwesome", 14));
        consoleToggleButton.setTextFill(Color.DARKBLUE);
        consoleToggleButton.setTooltip(new Tooltip("Show/Hide Console."));
        consoleToggleButton.selectedProperty().bindBidirectional(isDetailNodeVisible);
    }

    private void createProxyRequestsButton() {
        proxyRequestsToggleButton = new ToggleButton(FontAwesome.ICON_EXCHANGE);
        proxyRequestsToggleButton.setFont(Font.font("FontAwesome", 14));
        proxyRequestsToggleButton.setTextFill(Color.BLACK);
        proxyRequestsToggleButton.setTooltip(new Tooltip("Enable/Disable Proxying via Burp"));
        proxyRequestsToggleButton.selectedProperty().bindBidirectional(isProxyingEnabled);
    }

    private void createShowAlertsButton() {
        showAlertsToggleButton = new ToggleButton(FontAwesome.ICON_WARNING);
        showAlertsToggleButton.setFont(Font.font("FontAwesome", 14));
        showAlertsToggleButton.setTextFill(Color.DARKGOLDENROD);
        showAlertsToggleButton.setTooltip(new Tooltip("Show/Hide Alerts."));
        showAlertsToggleButton.selectedProperty().bindBidirectional(showAlerts);
    }

    private void createScreenShotButton() {
        screenShotButton = new Button(FontAwesome.ICON_CAMERA);
        screenShotButton.setFont(Font.font("FontAwesome", 14));
        screenShotButton.setTooltip(new Tooltip("Take Screen Shot."));
        //        screenShotButton.disableProperty().bind(webEngine.getLoadWorker().runningProperty());
        screenShotButton.setOnAction(observable -> {
            WritableImage image = masterPane.snapshot(null, null);
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss'.png'");

            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Save Screen Shot...");
            fileChooser.setInitialFileName(simpleDateFormat.format(Date.from(Instant.now())));
            File imageFile = fileChooser.showSaveDialog(null);

            if (imageFile != null) {
                try {
                    ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", imageFile);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void createFirebugButton() {
        firebugButton = new Button(FontAwesome.ICON_BUG);
        firebugButton.setFont(Font.font("FontAwesome", 14));
        firebugButton.setTextFill(Color.RED);
        firebugButton.setTooltip(new Tooltip("Launch Firebug."));
        firebugButton.disableProperty().bind(webEngine.getLoadWorker().runningProperty());
        firebugButton.setOnAction(observable -> webEngine.executeScript(firebugScript));
    }

    private void handleError(WebErrorEvent event) {
        for (EventHandler<WebErrorEvent> handler : errorListeners)
            handler.handle(event);
    }

    public void addAlertListener(EventHandler<WebEvent<String>> handler) {
        if (!alertListeners.contains(handler))
            alertListeners.add(handler);
    }

    public void removeAlertListener(EventHandler<WebEvent<String>> handler) {
        if (alertListeners.contains(handler))
            alertListeners.remove(handler);
    }

    public void addErrorListener(EventHandler<WebErrorEvent> handler) {
        if (!errorListeners.contains(handler))
            errorListeners.add(handler);
    }

    public void removeErrorListener(EventHandler<WebErrorEvent> handler) {
        if (errorListeners.contains(handler))
            errorListeners.remove(handler);
    }

    private void handleAlert(WebEvent<String> event) {
        numberOfAlerts.setValue(Integer.toString(Integer.valueOf(numberOfAlerts.getValue()) + 1));
        String message = event.getData();

        /*
         * Handle all the external onAlert event handlers first.
         */
        for (EventHandler<WebEvent<String>> handler : alertListeners)
            handler.handle(event);

        /*
         * Finally display an alert box if the operator demands it.
         */
        if (showAlerts.getValue()) {
            new JSAlertDialog(webView).alert(message);
            //            resetParents();
        }

    }

    /**
     * Used to get rid of LightweightDialog parent container which causes ugly GUI glitches.
     * Called after every time a dialog window is closed.
     */
    //    private void resetParents() {
    //        Parent webViewParent = webView.getParent();
    //        webViewAnchorPane.getChildren().remove(webViewParent);
    //        webViewAnchorPane.getChildren().add(webView);
    //    }

    public void loadUrl(final String url) {
        Platform.runLater(() -> webEngine.load(url));
    }

    public void loadUrl(final String url, final String userAgent) {
        Platform.runLater(() -> {
            webEngine.setUserAgent(originalUserAgent + userAgent);
            webEngine.load(url);
            webEngine.setUserAgent(originalUserAgent);
        });
    }

    public void loadContent(final String content) {
        Platform.runLater(() -> webEngine.loadContent(content));
    }

    public void workerStateChanged(ObservableValue<? extends Worker.State> observable, Worker.State oldValue,
            Worker.State newValue) {
        if (newValue == Worker.State.READY || newValue == Worker.State.SCHEDULED) {
            if (trafficBrowser != null) {
                trafficBrowser.setStartTime(Instant.now());
                trafficBrowser.getTraffic().clear();
            }
            numberOfAlerts.setValue("0");
        } else if (newValue == Worker.State.SUCCEEDED) {
            JSObject result = (JSObject) webEngine.executeScript("window");

            result.setMember("burpCallbacks",
                    new BurpExtenderCallbacksBridge(webEngine, BurpExtender.getBurpExtenderCallbacks()));

            result.setMember("burpKit", javaScriptHelpers);

            //            result.setMember("locals", locals);
            //
            //            result.setMember("globals", globals);

            if (controller != null) {
                result.setMember("burpController", controller);
            }
        } else if (newValue == Worker.State.FAILED) {
            JSAlertDialog alert = new JSAlertDialog(webView);
            alert.setHeaderText("Navigation Failed");
            alert.alert(webEngine.getLoadWorker().getException().getMessage());
        }

    }

    //    public void logRequest(final URL url) {
    //        Platform.runLater(new Runnable() {
    //            @Override
    //            public void run() {
    //                networkRequestsListView.getItems().add(url.toString());
    //            }
    //        });
    //    }

}