Java tutorial
/* * 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()); // } // }); // } }