From c948668af512148710a9f7bc3afd9f2627bfc1c5 Mon Sep 17 00:00:00 2001 From: bytebutcher Date: Thu, 9 Jan 2020 20:59:18 +0100 Subject: [PATCH] Added default context menu entries: decoder++, droopescan, mooscan, wpscan, bfac, gobuster, nikto, wfuzz, sqlmap, sslscan, sslyze, testssl Added additional placeholders: host, port, protocol, url, path, query, method, cookies, request/response, body Enhanced logging --- BappDescription.html | 23 +- BappManifest.bmf | 4 +- README.md | 12 +- burp-send-to-extension/build.gradle | 2 +- .../src/main/java/burp/BurpExtender.java | 166 +++++---- .../gui/SendToAddDialog.form | 326 +++++++++--------- .../gui/SendToAddDialog.java | 10 + .../gui/SendToContextMenu.java | 281 +++++++++++++-- .../burpsendtoextension/gui/SendToTab.java | 7 +- .../gui/SendToTableListener.java | 2 +- .../burpsendtoextension/models/Config.java | 59 +++- .../burpsendtoextension/models/Cookie.java | 225 ++++++++++++ 12 files changed, 845 insertions(+), 272 deletions(-) create mode 100644 burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Cookie.java diff --git a/BappDescription.html b/BappDescription.html index 1fb4325..8243da9 100644 --- a/BappDescription.html +++ b/BappDescription.html @@ -13,9 +13,30 @@ +
  • %F: will be replaced with the path to a temporary file containing the selected text
  • + +
  • %R: will be replaced with the path to a temporary file containing the content of the focused request/response
  • + +
  • %B: will be replaced with the path to a temporary file containing the body of the focused request/response
  • +
  • Run in terminal: defines whether a terminal-window should appear in which the configured command is executed. By default "xterm" is used as terminal-emulator. You can change the terminal-emulator in the "Miscellaneous Options" to your liking.
  • diff --git a/BappManifest.bmf b/BappManifest.bmf index cadfd67..a823d0f 100644 --- a/BappManifest.bmf +++ b/BappManifest.bmf @@ -2,11 +2,11 @@ Uuid: f089f1ad056545489139cb9f32900f8e ExtensionType: 1 Name: Custom Send To RepoName: custom-send-to -ScreenVersion: 0.94 +ScreenVersion: 0.98 SerialVersion: 1 MinPlatformVersion: 0 ProOnly: False Author: Thomas Engel ShortDescription: Add a customizable "Send to..." menu to the context menu -EntryPoint: burp-send-to-extension/build/libs/burp-send-to-extension-0.93.jar +EntryPoint: burp-send-to-extension/build/libs/burp-send-to-extension-0.98.jar BuildCommand: cd burp-send-to-extension; ./gradlew fatJar diff --git a/README.md b/README.md index 880dd1b..a088b04 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,18 @@ After loading the extension the "Send to"-Tab contains all necessary options to New context-menu-entries can be added using the "Add"-button. Each entry consists of following fields: * **Name:** the name of the context-menu-entry. * **Command:** the command to be executed. You can use following placeholders: + * **%H:** will be replaced with the host + * **%P:** will be replaced with the port + * **%T:** will be replaced with the protocol + * **%U:** will be replaced with the url + * **%A:** will be replaced with the url path + * **%Q:** will be replaced with the url query + * **%C:** will be replaced with the cookies + * **%M:** will be replaced with the HTTP-method * **%S:** will be replaced with the selected text - * **%F:** will be replaced with the path to a temporary file which contains the selected text + * **%F:** will be replaced with the path to a temporary file containing the selected text + * **%R:** will be replaced with the path to a temporary file containing the content of the focused request/response + * **%B:** will be replaced with the path to a temporary file containing the body of the focused request/response * **Group:** the name of the sub-menu in which this entry will be shown. Can be left blank. * **Run in terminal:** defines whether a terminal-window should appear in which the configured command is executed. By default "xterm" is used as terminal-emulator. You can change the terminal-emulator in the "Miscellaneous Options" to your liking. * **Show preview:** gives you the chance to preview and change the command before executing it. diff --git a/burp-send-to-extension/build.gradle b/burp-send-to-extension/build.gradle index 52fbc2d..ccb3905 100644 --- a/burp-send-to-extension/build.gradle +++ b/burp-send-to-extension/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'java' group 'net.bytebutcher' -version '0.95' +version '0.98' sourceCompatibility = 1.8 diff --git a/burp-send-to-extension/src/main/java/burp/BurpExtender.java b/burp-send-to-extension/src/main/java/burp/BurpExtender.java index 7ddc9bd..b68e499 100644 --- a/burp-send-to-extension/src/main/java/burp/BurpExtender.java +++ b/burp-send-to-extension/src/main/java/burp/BurpExtender.java @@ -1,73 +1,93 @@ -package burp; - -import net.bytebutcher.burpsendtoextension.gui.SendToContextMenu; -import net.bytebutcher.burpsendtoextension.gui.SendToTab; -import net.bytebutcher.burpsendtoextension.gui.SendToTable; -import net.bytebutcher.burpsendtoextension.models.Config; -import net.bytebutcher.burpsendtoextension.utils.OsUtils; - -import javax.swing.*; -import java.awt.*; -import java.io.File; -import java.io.PrintWriter; - -public class BurpExtender implements IBurpExtender, ITab { - - private JPanel tab = null; - private SendToTab sendToTab = null; - private IBurpExtenderCallbacks callbacks; - private SendToContextMenu sendToContextMenu; - private Config config; - private SendToTable sendToTable; - private PrintWriter stderr; - - @Override - public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { - this.callbacks = callbacks; - this.callbacks.setExtensionName("Send to"); - this.stderr = new PrintWriter(callbacks.getStderr(), true); - this.config = new Config(this); - this.sendToTab = new SendToTab(this); - this.sendToContextMenu = new SendToContextMenu(this, this.sendToTab.getSendToTableListener()); - this.callbacks.registerContextMenuFactory(sendToContextMenu); - this.tab = sendToTab.getRootPanel(); - this.sendToTable = this.sendToTab.getSendToTable(); - this.sendToTable.addCommandObjects(this.config.getSendToTableData()); - callbacks.addSuiteTab(this); - } - - @Override - public String getTabCaption() { - return "Send to"; - } - - @Override - public Component getUiComponent() { - return this.tab; - } - - public ImageIcon createImageIcon(String path, String description, int width, int height) { - java.net.URL imgURL = getClass().getResource(path); - if (imgURL != null) { - ImageIcon icon = new ImageIcon(imgURL); - Image image = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH); - return new ImageIcon(image, description); - } else { - stderr.println("Couldn't find file: " + path); - return null; - } - } - - public Config getConfig() { - return this.config; - } - - public JFrame getParent() { - return this.sendToTab.getParent(); - } - - public IBurpExtenderCallbacks getCallbacks() { - return this.callbacks; - } - -} +package burp; + +import net.bytebutcher.burpsendtoextension.gui.SendToContextMenu; +import net.bytebutcher.burpsendtoextension.gui.SendToTab; +import net.bytebutcher.burpsendtoextension.gui.SendToTable; +import net.bytebutcher.burpsendtoextension.models.Config; +import net.bytebutcher.burpsendtoextension.utils.OsUtils; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.io.PrintWriter; + +public class BurpExtender implements IBurpExtender, ITab { + + private JPanel tab = null; + private SendToTab sendToTab = null; + private IBurpExtenderCallbacks callbacks; + private SendToContextMenu sendToContextMenu; + private Config config; + private SendToTable sendToTable; + + private static PrintWriter stdout; + private static PrintWriter stderr; + + @Override + public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { + this.callbacks = callbacks; + initLogHandler(callbacks); + BurpExtender.printOut("Initializing Send to Extension..."); + this.callbacks.setExtensionName("Send to"); + BurpExtender.printOut("Initializing config..."); + this.config = new Config(this); + BurpExtender.printOut("Initializing tab..."); + this.sendToTab = new SendToTab(this); + BurpExtender.printOut("Registering context menu..."); + this.sendToContextMenu = new SendToContextMenu(this, this.sendToTab.getSendToTableListener()); + this.callbacks.registerContextMenuFactory(sendToContextMenu); + this.tab = sendToTab.getRootPanel(); + BurpExtender.printOut("Loading table data..."); + this.sendToTable = this.sendToTab.getSendToTable(); + this.sendToTable.addCommandObjects(this.config.getSendToTableData()); + callbacks.addSuiteTab(this); + BurpExtender.printOut("----------------------------------------------------------------------"); + } + + private void initLogHandler(IBurpExtenderCallbacks callbacks) { + this.stderr = new PrintWriter(callbacks.getStderr(), true); + this.stdout = new PrintWriter(callbacks.getStdout(), true); + } + + @Override + public String getTabCaption() { + return "Send to"; + } + + @Override + public Component getUiComponent() { + return this.tab; + } + + public ImageIcon createImageIcon(String path, String description, int width, int height) { + java.net.URL imgURL = getClass().getResource(path); + if (imgURL != null) { + ImageIcon icon = new ImageIcon(imgURL); + Image image = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH); + return new ImageIcon(image, description); + } else { + BurpExtender.printErr("Couldn't find file: " + path); + return null; + } + } + + public Config getConfig() { + return this.config; + } + + public JFrame getParent() { + return this.sendToTab.getParent(); + } + + public IBurpExtenderCallbacks getCallbacks() { + return this.callbacks; + } + + public static void printOut(String s) { + stdout.println(s); + } + + public static void printErr(String s) { + stderr.println(s); + } +} diff --git a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.form b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.form index 99447e4..559c308 100644 --- a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.form +++ b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.form @@ -1,163 +1,163 @@ - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.java b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.java index f9c5bd7..f67a80f 100644 --- a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.java +++ b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToAddDialog.java @@ -102,8 +102,18 @@ public void actionPerformed(ActionEvent e) { btnCancel.addActionListener(onCancelActionListener); btnCommandHelp.addActionListener(new ToolTipActionListener(btnCommandHelp, "" + "" + + "

    %H = Host

    " + + "

    %P = Port

    " + + "

    %T = Protocol

    " + + "

    %U = URL

    " + + "

    %A = Path

    " + + "

    %Q = Query

    " + + "

    %C = Cookies

    " + + "

    %M = Method

    " + "

    %S = Selected text

    " + "

    %F = Path to file containing selected text

    " + + "

    %R = Path to file containing focused request/response

    " + + "

    %B = Path to file containing body of focused request/response

    " + "") ); } diff --git a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToContextMenu.java b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToContextMenu.java index a83dd73..ad664ca 100644 --- a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToContextMenu.java +++ b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToContextMenu.java @@ -4,13 +4,17 @@ import com.google.common.collect.*; import net.bytebutcher.burpsendtoextension.gui.util.DialogUtil; import net.bytebutcher.burpsendtoextension.models.CommandObject; +import net.bytebutcher.burpsendtoextension.models.Cookie; import net.bytebutcher.burpsendtoextension.utils.OsUtils; import net.bytebutcher.burpsendtoextension.utils.StringUtils; import javax.swing.*; import java.awt.event.ActionEvent; import java.io.*; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; public class SendToContextMenu implements IContextMenuFactory { @@ -44,35 +48,211 @@ private IRequestInfo getRequestInfo(IHttpRequestResponse req) { return burpExtender.getCallbacks().getHelpers().analyzeRequest(req.getHttpService(), req.getRequest()); } + private IResponseInfo getResponseInfo(IHttpRequestResponse req) { + return burpExtender.getCallbacks().getHelpers().analyzeResponse(req.getResponse()); + } + + private String getUrl() { + String url = ""; + try { + IRequestInfo iRequestInfo = this.burpExtender.getCallbacks().getHelpers().analyzeRequest(this.invocation.getSelectedMessages()[0]); + url = iRequestInfo.getUrl().toString(); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing URL!"); + } + return url; + } + + private String getHost() { + String host = ""; + try { + host = this.invocation.getSelectedMessages()[0].getHttpService().getHost(); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing host!"); + } + return host; + } + + private String getPort() { + String port = ""; + try { + port = String.valueOf(this.invocation.getSelectedMessages()[0].getHttpService().getPort()); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing port!"); + } + return port; + } + private String getSelectedText() { - String selectedText = null; - int[] selectionBounds = this.invocation.getSelectionBounds(); - IHttpRequestResponse[] selectedMessages = invocation.getSelectedMessages(); - byte iContext = invocation.getInvocationContext(); - if (selectionBounds != null) { - IHttpRequestResponse iHttpRequestResponse = invocation.getSelectedMessages()[0]; - if (iContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST - || iContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) { - selectedText = new String(iHttpRequestResponse.getRequest()).substring(selectionBounds[0], selectionBounds[1]); - } else if (iContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_RESPONSE - || iContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_RESPONSE) { - selectedText = new String(iHttpRequestResponse.getResponse()).substring(selectionBounds[0], selectionBounds[1]); + String selectedText = ""; + try { + int[] selectionBounds = this.invocation.getSelectionBounds(); + IHttpRequestResponse[] selectedMessages = invocation.getSelectedMessages(); + if (selectionBounds != null && selectedMessages != null) { + byte iContext = invocation.getInvocationContext(); + byte[] requestResponse = getRequestResponse(iContext, invocation.getSelectedMessages()[0]); + if (requestResponse != null) { + selectedText = new String(requestResponse).substring(selectionBounds[0], selectionBounds[1]); + } else { + BurpExtender.printErr("Error parsing selected text! No request/response found!"); + } + } else { + BurpExtender.printErr("Error parsing selected text! No selected message and/or selection bounds!"); } - } else if (selectedMessages != null) { - selectedText = getRequestInfo(selectedMessages[0]).getUrl().toString(); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing selected text!"); } return selectedText; } + private String getRequestResponse() { + String requestResponseString = ""; + try { + byte iContext = invocation.getInvocationContext(); + byte[] requestResponse = getRequestResponse(iContext, invocation.getSelectedMessages()[0]); + requestResponseString = (requestResponse == null) ? "" : new String(requestResponse); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing focused request/response!"); + } + return requestResponseString; + } + + private byte[] getRequestResponse(byte iContext, IHttpRequestResponse message) { + if (iContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST + || iContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) { + // HTTP-Request + return message.getRequest(); + } else if (iContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_RESPONSE + || iContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_RESPONSE) { + // HTTP-Response + return message.getResponse(); + } else { + // Unknown + return null; + } + } + + private String getMethod() { + String method = ""; + try { + IRequestInfo iRequestInfo = this.burpExtender.getCallbacks().getHelpers().analyzeRequest(this.invocation.getSelectedMessages()[0]); + method = iRequestInfo.getMethod(); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing method!"); + } + return method; + } + + private String getCookies() { + String cookies = ""; + try { + List cookieList = Lists.newArrayList(); + String cookieHeaderPrefix = "cookie: "; + IHttpRequestResponse[] selectedMessages = invocation.getSelectedMessages(); + if (selectedMessages != null) { + IHttpRequestResponse message = selectedMessages[0]; + List cookieHeaders = getRequestInfo(message).getHeaders().stream().filter(s -> s.toLowerCase().startsWith(cookieHeaderPrefix)).collect(Collectors.toList()); + boolean hasCookieHeader = !cookieHeaders.isEmpty(); + cookieList = Lists.newArrayList(); + if (hasCookieHeader) { + for (String cookieHeader : cookieHeaders) { + cookieList.addAll(Cookie.parseRequestCookies(cookieHeader.substring(cookieHeaderPrefix.length() - 1))); + } + } + } + cookies = cookieList.stream().map(iCookie -> iCookie.getName() + "=" + iCookie.getValue()).collect(Collectors.joining(",")); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing cookies!"); + } + return cookies; + } + + private String getUrlPath() { + String urlPath = ""; + try { + IRequestInfo iRequestInfo = this.burpExtender.getCallbacks().getHelpers().analyzeRequest(this.invocation.getSelectedMessages()[0]); + if (iRequestInfo.getUrl().getPath() != null) { + urlPath = iRequestInfo.getUrl().getPath(); + } + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing url path!"); + } + return urlPath; + } + + private String getUrlQuery() { + String urlQuery = ""; + try { + IRequestInfo iRequestInfo = this.burpExtender.getCallbacks().getHelpers().analyzeRequest(this.invocation.getSelectedMessages()[0]); + if (iRequestInfo.getUrl().getQuery() != null) { + urlQuery = iRequestInfo.getUrl().getQuery(); + } + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing url query!"); + } + return urlQuery; + } + + private String getProtocol() { + String protocol = ""; + try { + protocol = String.valueOf(this.invocation.getSelectedMessages()[0].getHttpService().getProtocol()); + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing protocol!"); + } + return protocol; + } + + public String getBody() { + String body = ""; + try { + byte iContext = invocation.getInvocationContext(); + IHttpRequestResponse[] selectedMessages = invocation.getSelectedMessages(); + if (selectedMessages != null) { + IHttpRequestResponse message = selectedMessages[0]; + int bodyOffset = getBodyOffset(iContext, message); + if (bodyOffset != -1) { + byte[] requestResponse = getRequestResponse(iContext, message); + if (requestResponse != null) { + body = new String(Arrays.copyOfRange(requestResponse, bodyOffset, requestResponse.length)); + } else { + BurpExtender.printErr("Error parsing body! No request/response found!"); + } + } else { + BurpExtender.printErr("Error parsing body! Parsing body offset failed!"); + } + } else { + BurpExtender.printErr("Error parsing body! No selected message!"); + } + } catch (RuntimeException e) { + BurpExtender.printErr("Error parsing body!"); + } + return body; + } + + private int getBodyOffset(byte iContext, IHttpRequestResponse message) { + int bodyOffset = -1; + if (iContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST + || iContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) { + // HTTP-Request + bodyOffset = getRequestInfo(message).getBodyOffset(); + } else if (iContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_RESPONSE + || iContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_RESPONSE) { + // HTTP-Response + bodyOffset = getResponseInfo(message).getBodyOffset(); + } + return bodyOffset; + } + private void replaceSelectedText(String replaceText) { - if(invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST || invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) { + if (invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST || invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) { int[] bounds = invocation.getSelectionBounds(); byte[] message = invocation.getSelectedMessages()[0].getRequest(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { outputStream.write(Arrays.copyOfRange(message, 0, bounds[0])); outputStream.write(replaceText.getBytes()); - outputStream.write(Arrays.copyOfRange(message, bounds[1],message.length)); + outputStream.write(Arrays.copyOfRange(message, bounds[1], message.length)); outputStream.flush(); invocation.getSelectedMessages()[0].setRequest(outputStream.toByteArray()); } catch (IOException e) { @@ -168,11 +348,19 @@ private String runCommandObject(CommandObject commandObject) { "

    There was an unknown error during command execution!

    " + "

    For more information check out the \"Send to\" extension error log!

    " ); - burpExtender.getCallbacks().printError("Error during command execution: " + e); + BurpExtender.printErr("Error during command execution: " + e); + BurpExtender.printErr(stackTraceToString(e)); return null; } } + private String stackTraceToString(Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + private String[] getCommandToBeExecuted(CommandObject commandObject) throws IOException { return formatCommand(commandObject); } @@ -214,21 +402,60 @@ private String[] formatCommandRunningOnOperatingSystem(String command) { } private String formatCommandPattern(String command) throws IOException { - if (command.contains("%F")) { - File tmp = writeTextToTemporaryFile(command); - command = command.replace("%F", tmp.getAbsolutePath()); - } if (command.contains("%S")) { command = command.replace("%S", "'" + StringUtils.shellEscape(this.getSelectedText()) + "'"); } + if (command.contains("%H")) { + command = command.replace("%H", "'" + StringUtils.shellEscape(getHost()) + "'"); + } + if (command.contains("%P")) { + command = command.replace("%P", getPort()); + } + if (command.contains("%T")) { + command = command.replace("%T", "'" + StringUtils.shellEscape(getProtocol()) + "'"); + } + if (command.contains("%U")) { + command = command.replace("%U", "'" + StringUtils.shellEscape(getUrl()) + "'"); + } + if (command.contains("%A")) { + command = command.replace("%A", "'" + StringUtils.shellEscape(getUrlPath()) + "'"); + } + if (command.contains("%Q")) { + command = command.replace("%Q", "'" + StringUtils.shellEscape(getUrlQuery()) + "'"); + } + if (command.contains("%C")) { + command = command.replace("%C", "'" + StringUtils.shellEscape(getCookies()) + "'"); + } + if (command.contains("%M")) { + command = command.replace("%M", "'" + StringUtils.shellEscape(getMethod()) + "'"); + } + if (command.contains("%B")) { + File tmp = writeTextToTemporaryFile(getBody()); + command = command.replace("%B", tmp.getAbsolutePath()); + } + if (command.contains("%R")) { + File tmp = writeTextToTemporaryFile(getRequestResponse()); + command = command.replace("%R", tmp.getAbsolutePath()); + } + if (command.contains("%F")) { + File tmp = writeTextToTemporaryFile(getSelectedText()); + command = command.replace("%F", tmp.getAbsolutePath()); + } return command; } - private File writeTextToTemporaryFile(String command) throws IOException { + private File writeTextToTemporaryFile(String input) throws IOException { + if (input == null) { + input = ""; + } File tmp = File.createTempFile("burp_", ".snd"); - PrintWriter out = new PrintWriter(tmp.getPath()); - out.write(this.getSelectedText()); - out.flush(); + try { + PrintWriter out = new PrintWriter(tmp.getPath()); + out.write(input); + out.flush(); + } catch (RuntimeException e) { + BurpExtender.printErr("Error writing to temporary file!"); + } return tmp; } @@ -245,6 +472,8 @@ private String[] formatCommandForRunningInTerminal(String command) throws IOExce private void logCommandToBeExecuted(String[] commandToBeExecuted) { String commandToBeExecutedWithoutControlCharacters = String.join(" ", commandToBeExecuted).replaceAll("[\u0000-\u001f]", ""); - burpExtender.getCallbacks().printOutput("CommandObject: " + commandToBeExecutedWithoutControlCharacters); + String dateTime = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("uuuu/MM/dd HH:mm:ss")); + BurpExtender.printOut("[" + dateTime + "] " + commandToBeExecutedWithoutControlCharacters); + BurpExtender.printOut("----------------------------------------------------------------------"); } } diff --git a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTab.java b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTab.java index acf6e44..788052d 100644 --- a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTab.java +++ b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTab.java @@ -136,10 +136,15 @@ public void save() { } public void resetOptions() { - sendToTable.clearTable(); + resetSendToTableData(); resetRunInTerminalOption(); } + private void resetSendToTableData() { + sendToTable.clearTable(); + sendToTable.addCommandObjects(this.burpExtender.getConfig().getDefaultSendToTableData()); + } + private void resetRunInTerminalOption() { this.burpExtender.getConfig().resetRunInTerminalCommand(); txtRunInTerminal.setText(this.burpExtender.getConfig().getRunInTerminalCommand()); diff --git a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTableListener.java b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTableListener.java index 96a6024..f94c179 100644 --- a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTableListener.java +++ b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/gui/SendToTableListener.java @@ -33,7 +33,7 @@ public void tableChanged(TableModelEvent e) { for (CommandsChangeListener commandsChangeListener : commandsChangeListeners) { commandsChangeListener.commandsChanged(commandObjects); } - this.burpExtender.getConfig().saveSendToTableData(new Gson().toJson(sendToTable.getCommandObjects())); + this.burpExtender.getConfig().saveSendToTableData(sendToTable.getCommandObjects()); } void onAddButtonClick(ActionEvent e, CommandObject commandObject) { diff --git a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Config.java b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Config.java index fb2a162..29bd8eb 100644 --- a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Config.java +++ b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Config.java @@ -2,6 +2,7 @@ import burp.BurpExtender; import burp.IBurpExtenderCallbacks; +import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import net.bytebutcher.burpsendtoextension.utils.OsUtils; @@ -19,23 +20,75 @@ public Config(BurpExtender burpExtender) { this.callbacks = burpExtender.getCallbacks(); } - public void saveSendToTableData(String jsonData) { - this.callbacks.saveExtensionSetting("SendToTableData", jsonData); + public void saveSendToTableData(List sendToTableData) { + this.callbacks.saveExtensionSetting("SendToTableData", new Gson().toJson(sendToTableData)); } public List getSendToTableData() { List commandObjectList = new ArrayList<>(); try { String sendToTableData = this.callbacks.loadExtensionSetting("SendToTableData"); - if (sendToTableData == null || sendToTableData.isEmpty()) { + if (sendToTableData == null || sendToTableData.isEmpty() || "[]".equals(sendToTableData)) { + if (isFirstStart()) { + BurpExtender.printOut("Initializing default table data..."); + commandObjectList = initializeDefaultSendToTableData(); + } return commandObjectList; } return new Gson().fromJson(sendToTableData, new TypeToken>() {}.getType()); } catch (Exception e) { + BurpExtender.printErr("Error retrieving table data!"); + BurpExtender.printErr(e.toString()); return commandObjectList; } } + private List initializeDefaultSendToTableData() { + List commandObjectList = getDefaultSendToTableData(); + saveSendToTableData(commandObjectList); + unsetFirstStart(); + return commandObjectList; + } + + public List getDefaultSendToTableData() { + String groupFuzz = "fuzz"; + String groupCMS = "cms"; + String groupSQL = "sql"; + String groupSSL = "ssl"; + return Lists.newArrayList( + new CommandObject("decoder++", "dpp --dialog -f %F", "", false, false, true), + // cms + new CommandObject("droopescan", "droopescan scan drupal -u %U -t 10", groupCMS, true, true, false), + new CommandObject("mooscan", "mooscan -v --url %U", groupCMS, true, true, false), + new CommandObject("wpscan", "wpscan --url %U --threads 10", groupCMS, true, true, false), + // fuzz + new CommandObject("bfac", "bfac --url %U", groupFuzz, true, true, false), + new CommandObject("gobuster", "gobuster -u %U -s 403,404 -w /usr/share/wfuzz/wordlist/general/common.txt", groupFuzz, true, true, false), + new CommandObject("nikto", "nikto %U", groupFuzz, true, true, false), + new CommandObject("wfuzz", "wfuzz -c -w /usr/share/wfuzz/wordlist/general/common.txt --hc 404,403 %U", groupFuzz, true, true, false), + // sql + new CommandObject("sqlmap (GET)", "sqlmap -o -u %U --level=5 --risk=3", groupSQL, true, true, false), + new CommandObject("sqlmap (POST)", "sqlmap -r %R --level=5 --risk=3", groupSQL, true, true, false), + // ssl + new CommandObject("sslscan", "sslscan %H:%P", groupSSL, true, true, false), + new CommandObject("sslyze", "sslyze --regular %H:%P", groupSSL, true, true, false), + new CommandObject("testssl", "testssl.sh %H:%P", groupSSL, true, true, false) + ); + } + + private boolean isFirstStart() { + String isFirstStart = this.callbacks.loadExtensionSetting("isFirstStart"); + return isFirstStart == null || "true".equals(isFirstStart); + } + + private void setFirstStart() { + this.callbacks.saveExtensionSetting("isFirstStart", null); + } + + private void unsetFirstStart() { + this.callbacks.saveExtensionSetting("isFirstStart", "false"); + } + public void resetRunInTerminalCommand() { this.callbacks.saveExtensionSetting("runInTerminalSettingWindows", "cmd /c start cmd /K %C"); this.callbacks.saveExtensionSetting("runInTerminalSettingUnix", "xterm -hold -e %C"); diff --git a/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Cookie.java b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Cookie.java new file mode 100644 index 0000000..8f6ddf8 --- /dev/null +++ b/burp-send-to-extension/src/main/java/net/bytebutcher/burpsendtoextension/models/Cookie.java @@ -0,0 +1,225 @@ +package net.bytebutcher.burpsendtoextension.models; + +import burp.BurpExtender; +import burp.ICookie; +import com.google.common.collect.Lists; + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Implements the ICookie interface which holds details about an HTTP cookie. + * @author Thomas Engel + * @author August Detlefsen [augustd at codemagi dot com] + */ +public class Cookie implements ICookie { + + private String name; + private String value; + private String domain; + private String path; + private Date expiration; + private Long maxAge; + private Boolean secure = false; + private Boolean httpOnly = false; + + public Cookie(String name, String value) { + this.name = name; + this.value = value; + } + + public Cookie(ICookie cookie) { + this.name = cookie.getName(); + this.value = cookie.getValue(); + this.domain = cookie.getDomain(); + this.path = cookie.getPath(); + this.expiration = cookie.getExpiration(); + } + + /** + * Parses a cookie from a String containing the raw HTTP response header + * _value_ (Minus "Set-Cookie:"). + * + * @param rawCookie A String containing the raw cookie + * @return A Cookie object parsed from the raw cookie string + */ + public static Optional parseResponseCookie(String rawCookie) { + String[] rawCookieParams = rawCookie.split(";"); + + //get the cookie name, check for valid cookie + String[] rawCookieNameAndValue = rawCookieParams[0].split("="); + String cookieName = rawCookieNameAndValue[0].trim(); + if (cookieName.isEmpty()) { + BurpExtender.printErr("Invalid cookie: missing name"); + return Optional.empty(); + } + + //get the cookie value + String cookieValue = rawCookieNameAndValue[1].trim(); + + //construct output + Cookie output = new Cookie(cookieName, cookieValue); + + //parse other cookie params + for (int i = 1; i < rawCookieParams.length; i++) { + String[] rawCookieParam = rawCookieParams[i].trim().split("="); + + String paramName = rawCookieParam[0].trim(); + + if ("secure".equalsIgnoreCase(paramName)) { + output.setSecure(true); + + } else if ("HttpOnly".equalsIgnoreCase(paramName)) { + output.setHttpOnly(true); + + } else { + if (rawCookieParam.length != 2) { + //attribute not a flag or missing value + continue; + } + String paramValue = rawCookieParam[1].trim(); + + if ("expires".equalsIgnoreCase(paramName)) { + try { + SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz"); + Date expiryDate = format.parse(paramValue); + output.setExpiration(expiryDate); + } catch (Exception e) { + //couldn't parse date, ignore + BurpExtender.printErr("WARNING: unable to parse cookie expiration: " + paramValue); + } + } else if ("max-age".equalsIgnoreCase(paramName)) { + long maxAge = Long.parseLong(paramValue); + output.setMaxAge(maxAge); + } else if ("domain".equalsIgnoreCase(paramName)) { + output.setDomain(paramValue); + } else if ("path".equalsIgnoreCase(paramName)) { + output.setPath(paramValue); + } + } + } + + return Optional.of(output); + } + + /** + * Parses a cookie from a String containing the raw HTTP request header + * _value_ (Minus "Cookie:"). + * + * @param rawCookie A String containing the raw cookie + * @return A list of Cookie objects parsed from the raw cookie string + */ + public static List parseRequestCookies(String rawCookie) { + List cookies = Lists.newArrayList(); + String[] rawCookieParams = rawCookie.split(";"); + for (String rawCookieParam : rawCookieParams) { + //get the cookie name, check for valid cookie + String[] rawCookieNameAndValue = rawCookieParam.split("="); + String cookieName = rawCookieNameAndValue[0].trim(); + if (cookieName.isEmpty() || !rawCookieParam.contains("=")) { + BurpExtender.printErr("Invalid cookie: missing name"); + continue; + } + + //get the cookie value + String cookieValue = ""; + if (rawCookieNameAndValue.length != 1) { + cookieValue = rawCookieNameAndValue[1].trim(); + } + + //construct output + cookies.add(new Cookie(cookieName, cookieValue)); + } + + return cookies; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + @Override + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + public Long getMaxAge() { + return maxAge; + } + + public void setMaxAge(Long maxAge) { + this.maxAge = maxAge; + } + + public Boolean getSecure() { + return secure; + } + + public void setSecure(Boolean secure) { + this.secure = secure; + } + + public Boolean getHttpOnly() { + return httpOnly; + } + + public void setHttpOnly(Boolean httpOnly) { + this.httpOnly = httpOnly; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Cookie cookie = (Cookie) o; + return Objects.equals(name, cookie.name) && + Objects.equals(value, cookie.value) && + Objects.equals(domain, cookie.domain) && + Objects.equals(path, cookie.path) && + Objects.equals(expiration, cookie.expiration) && + Objects.equals(maxAge, cookie.maxAge) && + Objects.equals(secure, cookie.secure) && + Objects.equals(httpOnly, cookie.httpOnly); + } + + @Override + public int hashCode() { + return Objects.hash(name, value, domain, path, expiration, maxAge, secure, httpOnly); + } +} \ No newline at end of file