├── .gitignore ├── AutoRepeater.jar ├── BappDescription.html ├── BappManifest.bmf ├── LICENSE ├── README.md ├── ar.png ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── burp ├── AutoRepeater.java ├── BurpExtender.java ├── Conditions ├── Condition.java ├── ConditionTableModel.java └── Conditions.java ├── Filter ├── Filter.java ├── FilterTableModel.java └── Filters.java ├── Highlighter ├── Highlighter.java ├── HighlighterTableModel.java ├── HighlighterUITableModel.java └── Highlighters.java ├── Logs ├── LogEntry.java ├── LogEntryMenu.java ├── LogManager.java └── LogTableModel.java ├── Replacements ├── Replacement.java ├── ReplacementTableModel.java └── Replacements.java └── Utils ├── AutoRepeaterMenu.java ├── DiffViewerPane.java ├── HttpComparer.java ├── MessageEditorController.java ├── ResponseStore.java ├── Utils.java └── diff_match_patch.java /.gitignore: -------------------------------------------------------------------------------- 1 | out/** 2 | .gradle/** 3 | .idea/** 4 | build/** 5 | -------------------------------------------------------------------------------- /AutoRepeater.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nccgroup/AutoRepeater/f22b01723ac46605ed08ec42b0e10e674b7d0f90/AutoRepeater.jar -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

This extension automatically repeats requests, with replacement rules and response diffing. It provides a general-purpose solution for streamlining authorization testing within web applications.

2 | 3 |

AutoRepeater provides the following features:

4 | 5 | 20 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: f89f2837c22c4ab4b772f31522647ed8 2 | ExtensionType: 1 3 | Name: AutoRepeater 4 | RepoName: auto-repeater 5 | ScreenVersion: 1.0 6 | SerialVersion: 2 7 | MinPlatformVersion: 0 8 | ProOnly: False 9 | Author: Justin Moore, NCC Group 10 | ShortDescription: Automatically repeat requests, with replacement rules and response diffing. 11 | EntryPoint: build/libs/AutoRepeater-all.jar 12 | BuildCommand: gradle shadowJar 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 NCC Group Plc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoRepeater: Automated HTTP Request Repeating With Burp Suite 2 | 3 | ## tl;dr 4 | Within extender import AutoRepeater.jar 5 | 6 | ## Some Brief Instructions 7 | AutoRepeater will only resend requests which are changed by a defined replacement. When AutoRepeater receives a request that matches the conditions set for a given tab, AutoRepeater will first apply every defined base replacement to the request, then will copy the request with the base replacements performed for each defined replacement and apply the given replacement to the request. 8 | 9 | ## Introduction 10 | Burp Suite is an intercepting HTTP Proxy, and it is the defacto tool for performing web application security testing. While Burp Suite is a very useful tool, using it to perform authorization testing is often a tedious effort involving a "change request and resend" loop, which can miss vulnerabilities and slow down testing. AutoRepeater, an open source Burp Suite extension, was developed to alleviate this effort. AutoRepeater automates and streamlines web application authorization testing, and provides security researchers with an easy-to-use tool for automatically duplicating, modifying, and resending requests within Burp Suite while quickly evaluating the differences in responses. 11 | 12 | ![AutoRepeater](./ar.png) 13 | 14 | ## AutoRepeater 15 | Without AutoRepeater, the basic Burp Suite web application testing flow is as follows: 16 | 17 | 1. User noodles around a web application until they find an interesting request 18 | 2. User sends the request to Burp Suite's "Repeater" tool 19 | 3. User modifies the request within "Repeater" and resends it to the server 20 | 4. Repeat step 3 until a sweet vulnerability is found 21 | 5. Start again from step 1, until the user runs out of testing time or can retire from bug bounty earnings 22 | 23 | While this testing flow works, it is particularly tedious for testing issues that could exist within any request. For example, changing email addresses, account identities, roles, URLs, and CSRF tokens can all lead to vulnerabilities. Currently, Burp Suite does not quickly test for these types of vulnerabilities within a web application. 24 | 25 | There are some existing Burp Suite plugins (AuthMatrix, Authz, and Autorize) which exist to make authorization testing easier but each has issues that limit their usefulness. AuthMatrix and Authz require users to send specific requests to the plugins and set up rules for how the authorization testing is performed, which introduces the risk of missing important requests and slows down testing. Autorize does not provide the users with the ability to perform general-purpose text replacements and has a confusing user interface. AutoRepeater takes all the best ideas from these plugins, along with the Burp Suite's familiar user interface, and combines them to create the most streamlined authorization testing plugin. 26 | 27 | AutoRepeater provides a general-purpose solution for streamlining authorization testing within web applications. AutoRepeater provides the following features: 28 | 29 | + Automatically duplicate, modify, and resend any request 30 | + Conditional replacements 31 | + Quick header, cookie, and parameter value replacements 32 | + Split request/response viewer 33 | + Original vs. modified request/response diff viewer 34 | + Base replacements for values that break requests like CSRF tokens and session cookies 35 | + Renamable tabs 36 | + Logging 37 | + Exporting 38 | + Toggled activation 39 | + "Send to AutoRepeater" from other Burp Suite tools 40 | 41 | ## Sample Usage 42 | Following are some common use cases for AutoRepeater. Some helpful tips when using the tool are: 43 | 44 | + Don't activate autorepeater until you're ready to start browsing. 45 | + Ensure **Extender** is not using cookies from Burp's cookie jar (**Project Options > Session**). 46 | + Check early to ensure your replacements are working as expected. 47 | + Tabs and configuration are preserved after a restart, but data is lost. 48 | 49 | ### Testing Unauthenticated User Access 50 | To test whether an unauthenticated user can access the application, configure one rule under Base Replacements to **Remove Header By Name** and then match "Cookie". 51 | 52 | ### Testing Authenticated User Access 53 | To test access between authenticated users (e.g. low privilege to higher privilege), you'll need to define replacements for each of the session cookies used. 54 | 55 | 1. Make note of the cookie names and values for the lower-privileged session. 56 | 2. Configure a rule under Base Replacements for each cookie to **Match Cookie Name, Replace Value**. Match the cookie name, replace with the lower-privileged user's cookie. 57 | 3. Repeat for as many roles as you'd like to test. 58 | 4. Browse the application as the highest-privileged user. 59 | 5. Review the results. 60 | 61 | ### Reviewing User Access Results 62 | To review the results of access testing, first ensure you're using the latest version of the tool (Git, not BApp store). 63 | 64 | 1. Sort by **URL**, then by **Resp. Len. Diff.**. Items with a difference of 0 and identical status codes are strong indicators of successful access. 65 | 2. Using **Logs > Log Filter** configure exclusions for irrelevant data (e.g. File Extension = (png|gif|css|ico), Modified Status Code = (403|404)). 66 | 3. Review the results and manually investigate anything that looks out of place. 67 | 68 | ## References 69 | 70 | + [BSides Rochester 2018 - AutoRepeater: Automated HTTP Request Repeating With Burp Suite](https://www.youtube.com/watch?v=IYFLp_4ccrw) 71 | + [AutoRepeater: Automated HTTP Request Repeating With Burp Suite](https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2018/january/autorepeater-automated-http-request-repeating-with-burp-suite/) 72 | -------------------------------------------------------------------------------- /ar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nccgroup/AutoRepeater/f22b01723ac46605ed08ec42b0e10e674b7d0f90/ar.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.github.jengelman.gradle.plugins:shadow:+' 8 | } 9 | } 10 | 11 | apply plugin: 'java' 12 | apply plugin: 'idea' 13 | apply plugin: 'com.github.johnrengelman.shadow' 14 | 15 | sourceCompatibility = 1.8 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | compileJava.options.encoding = 'UTF-8' 22 | 23 | dependencies { 24 | compile group: 'com.google.code.gson', name: 'gson', version: '2.7' 25 | compile 'net.portswigger.burp.extender:burp-extender-api:1.7.13' 26 | compile 'com.google.guava:guava:23.4-jre' 27 | } 28 | 29 | //noinspection GroovyAssignabilityCheck 30 | sourceSets { 31 | main { 32 | java { 33 | srcDirs = ["./src"] 34 | } 35 | } 36 | } 37 | 38 | defaultTasks 'shadowJar' 39 | 40 | //noinspection GroovyAssignabilityCheck 41 | jar { 42 | manifest { 43 | attributes ( 44 | "Manifest-Version": "1.0", 45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nccgroup/AutoRepeater/f22b01723ac46605ed08ec42b0e10e674b7d0f90/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin, switch paths to Windows format before running java 129 | if $cygwin ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'AutoRepeater' 2 | -------------------------------------------------------------------------------- /src/burp/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | /** 4 | * Created by j on 8/7/17. 5 | */ 6 | 7 | import burp.Utils.AutoRepeaterMenu; 8 | import burp.Utils.Utils; 9 | import com.google.gson.*; 10 | 11 | import java.awt.*; 12 | import java.awt.event.*; 13 | import java.util.*; 14 | import java.util.List; 15 | import java.util.concurrent.ExecutorService; 16 | import java.util.concurrent.Executors; 17 | import javax.swing.*; 18 | import javax.swing.Timer; 19 | import javax.swing.event.DocumentEvent; 20 | import javax.swing.event.DocumentListener; 21 | 22 | public class BurpExtender implements IBurpExtender, ITab, IHttpListener, IContextMenuFactory { 23 | 24 | // burp stuff 25 | private static IBurpExtenderCallbacks callbacks; 26 | private static IExtensionHelpers helpers; 27 | private static Gson gson; 28 | 29 | private static JTabbedPane mainTabbedPane; 30 | private static JTabbedPane parentTabbedPane; 31 | private static JPanel newTabButton; 32 | private static AutoRepeaterMenu autoRepeaterMenu; 33 | private ExecutorService executor; 34 | //private static ResponseStore responseStore; 35 | 36 | // Global state variables 37 | private static int tabCounter = 0; 38 | private static boolean tabChangeListenerLock = false; 39 | private static ArrayList autoRepeaters; 40 | 41 | @Override 42 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) { 43 | // keep a reference to our callbacks object 44 | BurpExtender.callbacks = callbacks; 45 | // obtain an extension helpers object 46 | BurpExtender.helpers = callbacks.getHelpers(); 47 | // Gson for serialization 48 | BurpExtender.gson = new Gson(); 49 | //BurpExtender.gson = new GsonBuilder().setPrettyPrinting().create(); 50 | autoRepeaters = new ArrayList<>(); 51 | executor = Executors.newFixedThreadPool(25); 52 | // create our UI 53 | SwingUtilities.invokeLater(() -> { 54 | mainTabbedPane = new JTabbedPane(); 55 | newTabButton = new JPanel(); 56 | newTabButton.setName("..."); 57 | mainTabbedPane = new JTabbedPane(); 58 | mainTabbedPane.add(newTabButton); 59 | // If there is a saved extensionSetting load it. 60 | String b64ConfigurationJson = callbacks.loadExtensionSetting(getTabCaption()); 61 | if (b64ConfigurationJson != null) { 62 | initializeFromSave(b64ConfigurationJson, true); 63 | } else { 64 | addNewTab(); 65 | } 66 | mainTabbedPane.addChangeListener(e -> { 67 | // Make all tabname not editable whenever the tab is changed 68 | if (!tabChangeListenerLock) { 69 | if (mainTabbedPane.getSelectedIndex() == mainTabbedPane.getTabCount() - 1) { 70 | if (!tabChangeListenerLock) { 71 | addNewTab(); 72 | } 73 | } 74 | } 75 | for (int i = 0; i < mainTabbedPane.getTabCount() - 1; i++) { 76 | AutoRepeaterTabHandle arth = (AutoRepeaterTabHandle) mainTabbedPane.getTabComponentAt(i); 77 | arth.tabName.setEditable(false); 78 | } 79 | }); 80 | 81 | // Set Extension Name 82 | callbacks.setExtensionName("AutoRepeater"); 83 | // register As An HTTP Listener 84 | callbacks.registerHttpListener(BurpExtender.this); 85 | // Add To Right Click Menu 86 | callbacks.registerContextMenuFactory(BurpExtender.this); 87 | // Add response store 88 | //Save State 89 | callbacks.registerExtensionStateListener( 90 | () -> callbacks.saveExtensionSetting(getTabCaption(), exportSave()) 91 | ); 92 | // Add A Custom Tab To Burp 93 | callbacks.addSuiteTab(BurpExtender.this); 94 | // set parent component 95 | parentTabbedPane = (JTabbedPane) getUiComponent().getParent(); 96 | autoRepeaterMenu = new AutoRepeaterMenu(parentTabbedPane.getRootPane()); 97 | SwingUtilities.invokeLater(autoRepeaterMenu); 98 | }); 99 | } 100 | 101 | public static AutoRepeaterMenu getAutoRepeaterMenu() { 102 | return autoRepeaterMenu; 103 | } 104 | 105 | public static JTabbedPane getParentTabbedPane() { 106 | return parentTabbedPane; 107 | } 108 | 109 | public static String exportSave() { 110 | JsonArray BurpExtenderJson = new JsonArray(); 111 | // Don't count the "..." tab 112 | for (int i = 0; i < mainTabbedPane.getTabCount() - 1; i++) { 113 | AutoRepeaterTabHandle autoRepeaterTabHandle 114 | = (AutoRepeaterTabHandle) mainTabbedPane.getTabComponentAt(i); 115 | AutoRepeater ar = autoRepeaterTabHandle.autoRepeater; 116 | JsonObject AutoRepeaterJson = ar.toJson(); 117 | AutoRepeaterJson.addProperty("tabName", autoRepeaterTabHandle.tabName.getText()); 118 | BurpExtenderJson.add(AutoRepeaterJson); 119 | } 120 | return BurpExtenderJson.toString(); 121 | } 122 | 123 | public static String exportSave(AutoRepeater ar) { 124 | return exportSaveAsJson(ar).toString(); 125 | } 126 | 127 | public static JsonArray exportSaveAsJson(AutoRepeater ar) { 128 | JsonArray BurpExtenderJson = new JsonArray(); 129 | // Don't count the "..." tab 130 | for (int i = 0; i < mainTabbedPane.getTabCount() - 1; i++) { 131 | AutoRepeaterTabHandle autoRepeaterTabHandle 132 | = (AutoRepeaterTabHandle) mainTabbedPane.getTabComponentAt(i); 133 | AutoRepeater tempAR = autoRepeaterTabHandle.autoRepeater; 134 | if (ar.equals(tempAR)) { 135 | JsonObject AutoRepeaterJson = tempAR.toJson(); 136 | AutoRepeaterJson.addProperty("tabName", autoRepeaterTabHandle.tabName.getText()); 137 | BurpExtenderJson.add(AutoRepeaterJson); 138 | } 139 | } 140 | return BurpExtenderJson; 141 | } 142 | 143 | public static void initializeFromSave(String configuration, boolean replaceTabs) { 144 | getCallbacks().printOutput("Loading Stored AutoRepeater Configuration"); 145 | String configurationJson; 146 | // Check if the configuration is B64 encoded for legacy. 147 | try { 148 | configurationJson = new String(Base64.getDecoder().decode(configuration)); 149 | } catch (IllegalArgumentException e) { 150 | configurationJson = configuration; 151 | } 152 | JsonParser jsonParser = new JsonParser(); 153 | JsonArray tabConfigurations = jsonParser.parse(configurationJson).getAsJsonArray(); 154 | if (replaceTabs) { 155 | closeAllTabs(); 156 | } 157 | for (JsonElement tabConfiguration : tabConfigurations) { 158 | addNewTab(tabConfiguration.getAsJsonObject()); 159 | } 160 | } 161 | 162 | public static void addNewTab(JsonObject tabContents) { 163 | String tabName = tabContents.get("tabName").getAsString(); 164 | tabChangeListenerLock = true; 165 | tabCounter += 1; 166 | AutoRepeater autoRepeater = new AutoRepeater(tabContents); 167 | autoRepeaters.add(autoRepeater); 168 | mainTabbedPane.add(autoRepeater.getUI()); 169 | AutoRepeaterTabHandle autoRepeaterTabHandle = new AutoRepeaterTabHandle(tabName, autoRepeater); 170 | mainTabbedPane.setTabComponentAt(mainTabbedPane.indexOfComponent(autoRepeater.getUI()), 171 | autoRepeaterTabHandle); 172 | // Hack to steal and remove focus 173 | mainTabbedPane.remove(newTabButton); 174 | mainTabbedPane.add(newTabButton); 175 | tabChangeListenerLock = false; 176 | } 177 | 178 | private static void addNewTab() { 179 | tabChangeListenerLock = true; 180 | tabCounter += 1; 181 | AutoRepeater autoRepeater = new AutoRepeater(); 182 | autoRepeaters.add(autoRepeater); 183 | mainTabbedPane.add(autoRepeater.getUI()); 184 | AutoRepeaterTabHandle autoRepeaterTabHandle = new AutoRepeaterTabHandle( 185 | Integer.toString(tabCounter), autoRepeater); 186 | mainTabbedPane.setTabComponentAt(mainTabbedPane.indexOfComponent(autoRepeater.getUI()), 187 | autoRepeaterTabHandle); 188 | // Hack to steal and remove focus 189 | mainTabbedPane.remove(newTabButton); 190 | mainTabbedPane.add(newTabButton); 191 | tabChangeListenerLock = false; 192 | } 193 | 194 | public static void highlightTab() { 195 | if (parentTabbedPane != null) { 196 | for (int i = 0; i < parentTabbedPane.getTabCount(); i++) { 197 | if (parentTabbedPane.getComponentAt(i).equals(mainTabbedPane)) { 198 | parentTabbedPane.setBackgroundAt(i, Utils.getBurpOrange()); 199 | Timer timer = new Timer(3000, e -> { 200 | for (int j = 0; j < parentTabbedPane.getTabCount(); j++) { 201 | if (parentTabbedPane.getComponentAt(j).equals(mainTabbedPane)) { 202 | parentTabbedPane.setBackgroundAt(j, Color.BLACK); 203 | break; 204 | } 205 | } 206 | }); 207 | timer.setRepeats(false); 208 | timer.start(); 209 | break; 210 | } 211 | } 212 | } 213 | } 214 | 215 | // implement ITab 216 | @Override 217 | public String getTabCaption() { 218 | return "AutoRepeater"; 219 | } 220 | 221 | @Override 222 | public Component getUiComponent() { 223 | return mainTabbedPane; 224 | } 225 | 226 | // implement IHttpListener 227 | @Override 228 | public void processHttpMessage( 229 | int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { 230 | if (!messageIsRequest) { 231 | for (AutoRepeater autoRepeater : autoRepeaters) { 232 | executor.submit( 233 | () -> autoRepeater.modifyAndSendRequestAndLog( 234 | toolFlag, 235 | messageInfo) 236 | ); 237 | } 238 | } 239 | } 240 | 241 | public static void closeAllTabs() { 242 | tabChangeListenerLock = true; 243 | int tabCount = mainTabbedPane.getTabCount() - 1; 244 | for (int i = 0; i < tabCount; i++) { 245 | if (mainTabbedPane.getTabComponentAt(0).getClass().equals(AutoRepeaterTabHandle.class)) { 246 | try { 247 | AutoRepeaterTabHandle arth = (AutoRepeaterTabHandle) mainTabbedPane.getTabComponentAt(0); 248 | autoRepeaters.remove(arth.autoRepeater); 249 | mainTabbedPane.remove(0); 250 | } catch (Exception e) { 251 | getCallbacks().printOutput(e.getMessage()); 252 | } 253 | } 254 | } 255 | tabChangeListenerLock = false; 256 | } 257 | 258 | private static class AutoRepeaterTabHandle extends JPanel { 259 | 260 | AutoRepeater autoRepeater; 261 | JTextField tabName; 262 | 263 | public AutoRepeaterTabHandle(String title, AutoRepeater autoRepeater) { 264 | this.autoRepeater = autoRepeater; 265 | this.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); 266 | this.setOpaque(false); 267 | JLabel label = new JLabel(title); 268 | label.setBorder(BorderFactory.createEmptyBorder(1, 2, 1, 2)); 269 | tabName = new JTextField(title); 270 | tabName.setOpaque(false); 271 | tabName.setBorder(null); 272 | tabName.setBackground(new Color(0, 0, 0, 0)); 273 | tabName.setEditable(false); 274 | tabName.setCaretColor(Color.BLACK); 275 | 276 | this.add(tabName); 277 | JButton closeButton = new JButton("✕"); 278 | closeButton.setFont(new Font("monospaced", Font.PLAIN, 10)); 279 | closeButton.setBorder(BorderFactory.createEmptyBorder(1, 2, 1, 2)); 280 | closeButton.setForeground(Color.GRAY); 281 | 282 | closeButton.setBorderPainted(false); 283 | closeButton.setContentAreaFilled(false); 284 | closeButton.setOpaque(false); 285 | 286 | // Fix tabname redraw lag. 287 | JPanel parent = this; 288 | tabName.getDocument().addDocumentListener( 289 | new DocumentListener() { 290 | @Override 291 | public void insertUpdate(DocumentEvent e) { 292 | tabName.repaint(); 293 | parent.validate(); 294 | } 295 | 296 | @Override 297 | public void removeUpdate(DocumentEvent e) { 298 | tabName.repaint(); 299 | parent.validate(); 300 | } 301 | 302 | @Override 303 | public void changedUpdate(DocumentEvent e) { 304 | tabName.repaint(); 305 | parent.validate(); 306 | } 307 | }); 308 | 309 | tabName.addMouseListener(new MouseAdapter() { 310 | @Override 311 | public void mouseClicked(MouseEvent e) { 312 | if (!mainTabbedPane.getSelectedComponent().equals(autoRepeater.getUI())) { 313 | mainTabbedPane.setSelectedComponent(autoRepeater.getUI()); 314 | for (int i = 0; i < mainTabbedPane.getTabCount() - 2; i++) { 315 | if (!mainTabbedPane.getComponentAt(i).equals(autoRepeater.getUI())) { 316 | AutoRepeaterTabHandle autoRepeaterTabHandle = 317 | (AutoRepeaterTabHandle) mainTabbedPane.getTabComponentAt(i); 318 | autoRepeaterTabHandle.tabName.setEditable(false); 319 | } 320 | } 321 | } else { 322 | mainTabbedPane.setSelectedComponent(autoRepeater.getUI()); 323 | tabName.setEditable(true); 324 | } 325 | } 326 | 327 | @Override 328 | public void mousePressed(MouseEvent e) { 329 | if (!mainTabbedPane.getSelectedComponent().equals(autoRepeater.getUI())) { 330 | mainTabbedPane.setSelectedComponent(autoRepeater.getUI()); 331 | for (int i = 0; i < mainTabbedPane.getTabCount() - 2; i++) { 332 | if (!mainTabbedPane.getComponentAt(i).equals(autoRepeater.getUI())) { 333 | AutoRepeaterTabHandle arth = (AutoRepeaterTabHandle) mainTabbedPane 334 | .getTabComponentAt(i); 335 | arth.tabName.setEditable(false); 336 | } 337 | } 338 | } else { 339 | mainTabbedPane.setSelectedComponent(autoRepeater.getUI()); 340 | } 341 | } 342 | }); 343 | 344 | closeButton.addActionListener(e -> { 345 | tabChangeListenerLock = true; 346 | if (mainTabbedPane.getSelectedComponent().equals(autoRepeater.getUI())) { 347 | if (mainTabbedPane.getTabCount() == 2) { 348 | mainTabbedPane.remove(autoRepeater.getUI()); 349 | autoRepeaters.remove(autoRepeater); 350 | addNewTab(); 351 | tabChangeListenerLock = true; 352 | } else if (mainTabbedPane.getTabCount() > 2) { 353 | mainTabbedPane.remove(autoRepeater.getUI()); 354 | autoRepeaters.remove(autoRepeater); 355 | } 356 | if (mainTabbedPane.getSelectedIndex() == mainTabbedPane.getTabCount() - 1) { 357 | mainTabbedPane.setSelectedIndex(mainTabbedPane.getTabCount() - 2); 358 | } 359 | } else { 360 | mainTabbedPane.setSelectedComponent(autoRepeater.getUI()); 361 | } 362 | tabChangeListenerLock = false; 363 | }); 364 | this.add(closeButton); 365 | } 366 | 367 | } 368 | 369 | public static AutoRepeater getSelectedAutoRepeater() { 370 | AutoRepeaterTabHandle autoRepeaterTabHandle = (AutoRepeaterTabHandle) mainTabbedPane.getTabComponentAt(mainTabbedPane.getSelectedIndex()); 371 | return autoRepeaterTabHandle.autoRepeater; 372 | } 373 | 374 | public static IBurpExtenderCallbacks getCallbacks() { 375 | return callbacks; 376 | } 377 | 378 | public static IExtensionHelpers getHelpers() { 379 | return helpers; 380 | } 381 | 382 | public static ArrayList getAutoRepeaters() { 383 | return autoRepeaters; 384 | } 385 | 386 | public static Gson getGson() { 387 | return gson; 388 | } 389 | 390 | @Override 391 | public List createMenuItems(IContextMenuInvocation invocation) { 392 | ArrayList menu = new ArrayList<>(); 393 | ActionListener listener; 394 | final int toolFlag = invocation.getToolFlag(); 395 | IHttpRequestResponse[] requestResponses = invocation.getSelectedMessages(); 396 | 397 | listener = event -> new Thread(() -> { 398 | if (toolFlag != -1) { 399 | BurpExtender.highlightTab(); 400 | for (IHttpRequestResponse requestResponse : requestResponses) { 401 | final IHttpRequestResponse tempRequestResponse; 402 | if (requestResponse.getResponse() == null) { 403 | tempRequestResponse = BurpExtender.getCallbacks().makeHttpRequest( 404 | requestResponse.getHttpService(), requestResponse.getRequest()); 405 | } else { 406 | tempRequestResponse = requestResponse; 407 | } 408 | executor.submit(() -> { 409 | for (AutoRepeater autoRepeater : autoRepeaters) { 410 | autoRepeater.modifyAndSendRequestAndLog( 411 | toolFlag, 412 | tempRequestResponse); 413 | } 414 | }); 415 | } 416 | } 417 | }).start(); 418 | 419 | JMenuItem item = new JMenuItem("Send to AutoRepeater", null); 420 | item.addActionListener(listener); 421 | menu.add(item); 422 | return menu; 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /src/burp/Conditions/Condition.java: -------------------------------------------------------------------------------- 1 | package burp.Conditions; 2 | 3 | import burp.BurpExtender; 4 | import burp.IHttpRequestResponse; 5 | import burp.IParameter; 6 | import burp.IRequestInfo; 7 | import burp.IResponseInfo; 8 | import com.google.common.io.Files; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.regex.Pattern; 13 | import java.util.stream.Collectors; 14 | 15 | public class Condition { 16 | 17 | private String booleanOperator; 18 | private String matchType; 19 | private String matchRelationship; 20 | private String matchCondition; 21 | private boolean isEnabled; 22 | 23 | public Condition( 24 | String booleanOperator, 25 | String matchType, 26 | String matchRelationship, 27 | String matchCondition) { 28 | this(booleanOperator, 29 | matchType, 30 | matchRelationship, 31 | matchCondition, 32 | true); 33 | } 34 | 35 | public Condition( 36 | String booleanOperator, 37 | String matchType, 38 | String matchRelationship, 39 | String matchCondition, 40 | boolean isEnabled) { 41 | setEnabled(isEnabled); 42 | setBooleanOperator(booleanOperator); 43 | setMatchType(matchType); 44 | setMatchRelationship(matchRelationship); 45 | setMatchCondition(matchCondition); 46 | } 47 | 48 | public Condition(Condition condition) { 49 | this(condition.getBooleanOperator(), 50 | condition.getMatchType(), 51 | condition.getMatchRelationship(), 52 | condition.getMatchCondition(), 53 | condition.isEnabled()); 54 | if (getBooleanOperator().equals("")) { 55 | setBooleanOperator("And"); 56 | } 57 | } 58 | 59 | public static final String[] BOOLEAN_OPERATOR_OPTIONS = { 60 | "And", 61 | "Or" 62 | }; 63 | 64 | public static final String[] MATCH_TYPE_OPTIONS = { 65 | "String In Request", 66 | "String In Response", 67 | "Request Length", 68 | "Response Length", 69 | "URL", 70 | "Status Code", 71 | "Domain Name", 72 | //"IP Address", 73 | "Protocol", 74 | "HTTP Method", 75 | "File Extension", 76 | "Request", 77 | "Cookie Name", 78 | "Cookie Value", 79 | "Any Header", 80 | "Request Body", 81 | "Param Name", 82 | "Param Value", 83 | "Sent From Tool", 84 | "Listener Port" 85 | }; 86 | 87 | public boolean checkCondition(int toolFlag, IHttpRequestResponse messageInfo) { 88 | switch (this.matchType) { 89 | case "Domain Name": return checkDomainName(messageInfo); 90 | case "Protocol": return checkProtocol(messageInfo); 91 | case "HTTP Method": return checkHttpMethod(messageInfo); 92 | case "URL": return checkUrl(messageInfo); 93 | case "File Extension": return checkFileExtension(messageInfo); 94 | case "Request": return checkRequest(messageInfo); 95 | case "Cookie Name": return checkCookieName(messageInfo); 96 | case "Cookie Value": return checkCookieValue(messageInfo); 97 | case "Any Header": return checkAnyHeader(messageInfo); 98 | case "Request Body": return checkRequestBody(messageInfo); 99 | case "Param Name": return checkParamName(messageInfo); 100 | case "Param Value": return checkParamValue(messageInfo); 101 | case "Sent From Tool": return checkSentFromTool(toolFlag); 102 | case "Listener Port": return checkListenerPort(messageInfo); 103 | case "Status Code": return checkStatusCode(messageInfo); 104 | case "String In Request": return checkStringInRequest(messageInfo); 105 | case "String In Response": return checkStringInResponse(messageInfo); 106 | default: throw new IllegalStateException("checkCondition() not defined for the input."); 107 | } 108 | } 109 | 110 | 111 | public static String[] getMatchRelationshipOptions(String inputString) { 112 | switch (inputString) { 113 | case "Domain Name": 114 | return new String[]{"Matches", "Does Not Match"}; 115 | case "IP Address": 116 | return new String[]{"Is In Range", "Is Not In Range"}; 117 | case "Protocol": 118 | return new String[]{"Is HTTP", "Is Not HTTP"}; 119 | case "HTTP Method": 120 | return new String[]{"Matches", "Does Not Match"}; 121 | case "URL": 122 | return new String[]{"Matches", "Does Not Match", "Is In Scope"}; 123 | case "File Extension": 124 | return new String[]{"Matches", "Does Not Match"}; 125 | case "Request": 126 | return new String[]{"Contains Parameters", "Does Not Contain Parameters"}; 127 | case "Status Code": 128 | return new String[]{"Is Greater Than", "Is Less Than", "Equals", "Does Not Equal"}; 129 | case "Cookie Name": 130 | return new String[]{"Matches", "Does Not Match"}; 131 | case "Cookie Value": 132 | return new String[]{"Matches", "Does Not Match"}; 133 | case "Any Header": 134 | return new String[]{"Matches", "Does Not Match"}; 135 | case "Request Body": 136 | return new String[]{"Matches", "Does Not Match"}; 137 | case "Param Name": 138 | return new String[]{"Matches", "Does Not Match"}; 139 | case "Param Value": 140 | return new String[]{"Matches", "Does Not Match"}; 141 | case "Sent From Tool": 142 | return new String[]{ 143 | "Burp", 144 | "Proxy", 145 | "Repeater", 146 | "Spider", 147 | "Intruder", 148 | "Scanner" 149 | }; 150 | case "Listener Port": 151 | return new String[]{"Matches", "Does Not Match"}; 152 | case "String In Request": 153 | return new String[]{"Matches", "Does Not Match", "Matches Regex", "Does Not Match Regex"}; 154 | case "String In Response": 155 | return new String[]{"Matches", "Does Not Match", "Matches Regex", "Does Not Match Regex"}; 156 | case "Request Length": 157 | return new String[]{"Is Greater Than", "Is Less Than", "Equals"}; 158 | case "Response Length": 159 | return new String[]{"Is Greater Than", "Is Less Than", "Equals"}; 160 | case "Mime Type": 161 | return new String[]{"Is Text", "Is Not Text", "Is Media", "Is Not Media"}; 162 | default: 163 | throw new IllegalStateException("getMatchRelationshipOptions() not defined for "+inputString); 164 | } 165 | } 166 | 167 | public static boolean matchConditionIsEditable(String inputString) { 168 | switch (inputString) { 169 | case "Domain Name": 170 | return true; 171 | case "IP Address": 172 | return true; 173 | case "Protocol": 174 | return false; 175 | case "HTTP Method": 176 | return true; 177 | case "URL": 178 | return true; 179 | case "File Extension": 180 | return true; 181 | case "Request": 182 | return false; 183 | case "Cookie Name": 184 | return true; 185 | case "Cookie Value": 186 | return true; 187 | case "Any Header": 188 | return true; 189 | case "Request Body": 190 | return true; 191 | case "Param Name": 192 | return true; 193 | case "Param Value": 194 | return true; 195 | case "Sent From Tool": 196 | return false; 197 | case "Listener Port": 198 | return true; 199 | case "Status Code": 200 | return true; 201 | case "String In Request": 202 | return true; 203 | case "String In Response": 204 | return true; 205 | case "Request Length": 206 | return true; 207 | case "Response Length": 208 | return true; 209 | default: 210 | throw new IllegalStateException("matchConditionIsEditable() not defined for input "+inputString); 211 | } 212 | } 213 | 214 | private boolean checkRequestLength(IHttpRequestResponse messageInfo) { 215 | try { 216 | int matchInt = Integer.parseInt(this.matchCondition); 217 | switch (this.matchRelationship) { 218 | case "Is Greater Than": 219 | return messageInfo.getRequest().length > matchInt; 220 | case "Is Less Than": 221 | return messageInfo.getRequest().length < matchInt; 222 | default: 223 | return messageInfo.getRequest().length == matchInt; 224 | } 225 | } catch(Exception NumberFormatException) { 226 | return false; 227 | } 228 | } 229 | 230 | private boolean checkResponseLength(IHttpRequestResponse messageInfo) { 231 | try { 232 | int matchInt = Integer.parseInt(this.matchCondition); 233 | switch (this.matchRelationship) { 234 | case "Is Greater Than": 235 | return messageInfo.getResponse().length > matchInt; 236 | case "Is Less Than": 237 | return messageInfo.getResponse().length < matchInt; 238 | default: 239 | return messageInfo.getResponse().length == matchInt; 240 | } 241 | } catch(Exception NumberFormatException) { 242 | return false; 243 | } 244 | } 245 | 246 | private boolean checkStringInRequest(IHttpRequestResponse messageInfo) { 247 | switch (this.matchRelationship) { 248 | case "Matches": 249 | return new String(messageInfo.getRequest()).contains(this.matchCondition); 250 | case "Does Not Match": 251 | return !(new String(messageInfo.getRequest()).contains(this.matchCondition)); 252 | case "Matches Regex": 253 | return Pattern.compile(this.matchCondition).matcher(new String(messageInfo.getRequest())).find(); 254 | default: 255 | return !(Pattern.compile(this.matchCondition).matcher(new String(messageInfo.getRequest())).find()); 256 | } 257 | } 258 | 259 | private boolean checkStringInResponse(IHttpRequestResponse messageInfo) { 260 | switch (this.matchRelationship) { 261 | case "Matches": 262 | return new String(messageInfo.getResponse()).contains(this.matchCondition); 263 | case "Does Not Match": 264 | return !(new String(messageInfo.getResponse()).contains(this.matchCondition)); 265 | case "Matches Regex": 266 | return Pattern.compile(this.matchCondition).matcher(new String(messageInfo.getResponse())).find(); 267 | default: 268 | return !(Pattern.compile(this.matchCondition).matcher(new String(messageInfo.getResponse())).find()); 269 | } 270 | } 271 | 272 | private boolean checkStatusCode(IHttpRequestResponse messageInfo) { 273 | IResponseInfo analyzedResponse = BurpExtender.getHelpers().analyzeResponse(messageInfo.getResponse()); 274 | try { 275 | short responseCodeAsShort = Short.parseShort(this.matchCondition); 276 | switch (this.matchRelationship) { 277 | case "Is Greater Than": 278 | return analyzedResponse.getStatusCode() > responseCodeAsShort; 279 | case "Is Less Than": 280 | return analyzedResponse.getStatusCode() < responseCodeAsShort; 281 | case "Equals": 282 | return (analyzedResponse.getStatusCode() == responseCodeAsShort); 283 | default: 284 | return !(analyzedResponse.getStatusCode() == responseCodeAsShort); 285 | } 286 | } catch (NumberFormatException e) { 287 | return false; 288 | } 289 | } 290 | 291 | private boolean checkDomainName(IHttpRequestResponse messageInfo) { 292 | switch (this.matchRelationship) { 293 | case "Matches": 294 | return messageInfo.getHttpService().getHost().equals(this.matchCondition); 295 | default: 296 | return !messageInfo.getHttpService().getHost().equals(this.matchCondition); 297 | } 298 | } 299 | 300 | private boolean checkProtocol(IHttpRequestResponse messageInfo) { 301 | String protocol = messageInfo.getHttpService().getProtocol(); 302 | switch (this.matchRelationship) { 303 | case "Is HTTP": 304 | return protocol.equals("http"); 305 | default: 306 | return !protocol.equals("http"); 307 | } 308 | } 309 | 310 | private boolean checkHttpMethod(IHttpRequestResponse messageInfo) { 311 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 312 | switch (this.matchRelationship) { 313 | case "Matches": 314 | return analyzedRequest.getMethod().matches(this.matchCondition); 315 | default: 316 | return !analyzedRequest.getMethod().matches(this.matchCondition); 317 | } 318 | } 319 | 320 | private boolean checkUrl(IHttpRequestResponse messageInfo) { 321 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 322 | switch (this.matchRelationship) { 323 | case "Is In Scope": 324 | return BurpExtender.getCallbacks().isInScope(analyzedRequest.getUrl()); 325 | case "Matches": 326 | return analyzedRequest.getUrl().toString().matches(this.matchCondition); 327 | default: 328 | return !analyzedRequest.getUrl().toString().matches(this.matchCondition); 329 | } 330 | } 331 | 332 | private boolean checkFileExtension(IHttpRequestResponse messageInfo) { 333 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 334 | String fileExtension = Files.getFileExtension(analyzedRequest.getUrl().getPath().toString()); 335 | switch (this.matchRelationship) { 336 | case "Matches": 337 | return fileExtension.matches(this.matchCondition); 338 | default: 339 | return !fileExtension.matches(this.matchCondition); 340 | } 341 | } 342 | 343 | private boolean checkRequest(IHttpRequestResponse messageInfo) { 344 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 345 | long parameterCount = analyzedRequest.getParameters() 346 | .stream() 347 | .filter( 348 | p -> p.getType() == IParameter.PARAM_URL || p.getType() == IParameter.PARAM_BODY) 349 | .count(); 350 | switch (this.matchRelationship) { 351 | case "Contains Parameters": 352 | return parameterCount > 0; 353 | default: 354 | return !(parameterCount > 0); 355 | } 356 | } 357 | 358 | private boolean checkCookieName(IHttpRequestResponse messageInfo) { 359 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 360 | List cookiesByName = analyzedRequest.getParameters() 361 | .stream() 362 | .filter(p -> p.getType() == IParameter.PARAM_COOKIE) 363 | .filter(p -> p.getName().matches(this.matchCondition)) 364 | .collect(Collectors.toList()); 365 | switch (this.matchRelationship) { 366 | case "Matches": 367 | return cookiesByName.size() > 0; 368 | default: 369 | return !(cookiesByName.size() > 0); 370 | } 371 | } 372 | 373 | private boolean checkCookieValue(IHttpRequestResponse messageInfo) { 374 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 375 | List cookiesByName = analyzedRequest.getParameters() 376 | .stream() 377 | .filter(p -> p.getType() == IParameter.PARAM_COOKIE) 378 | .filter(p -> p.getName().matches(this.matchCondition)) 379 | .collect(Collectors.toList()); 380 | switch (this.matchRelationship) { 381 | case "Matches": 382 | return cookiesByName.size() > 0; 383 | default: 384 | return !(cookiesByName.size() > 0); 385 | } 386 | } 387 | 388 | private boolean checkAnyHeader(IHttpRequestResponse messageInfo) { 389 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 390 | List matchingHeaders = analyzedRequest.getHeaders() 391 | .stream() 392 | .filter(h -> h.matches(this.matchCondition)) 393 | .collect(Collectors.toList()); 394 | switch (this.matchRelationship) { 395 | case "Matches": 396 | return matchingHeaders.size() > 0; 397 | default: 398 | return !(matchingHeaders.size() > 0); 399 | } 400 | } 401 | 402 | private boolean checkRequestBody(IHttpRequestResponse messageInfo) { 403 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 404 | byte[] request = messageInfo.getRequest(); 405 | String bodyString = new String( 406 | Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length)); 407 | switch (this.matchRelationship) { 408 | case ("Matches"): 409 | return bodyString.matches(this.matchCondition); 410 | default: 411 | return !bodyString.matches(this.matchCondition); 412 | } 413 | } 414 | 415 | private boolean checkParamName(IHttpRequestResponse messageInfo) { 416 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 417 | List parametersByName = analyzedRequest.getParameters() 418 | .stream() 419 | .filter(p -> p.getName().matches(this.matchCondition)) 420 | .collect(Collectors.toList()); 421 | switch (this.matchRelationship) { 422 | case "Matches": 423 | return parametersByName.size() > 0; 424 | default: 425 | return !(parametersByName.size() > 0); 426 | } 427 | } 428 | 429 | private boolean checkParamValue(IHttpRequestResponse messageInfo) { 430 | IRequestInfo analyzedRequest = BurpExtender.getHelpers().analyzeRequest(messageInfo); 431 | List parametersByValue = analyzedRequest.getParameters() 432 | .stream() 433 | .filter(p -> p.getValue().matches(this.matchCondition)) 434 | .collect(Collectors.toList()); 435 | switch (this.matchRelationship) { 436 | case "Matches": 437 | return parametersByValue.size() > 0; 438 | default: 439 | return !(parametersByValue.size() > 0); 440 | } 441 | } 442 | 443 | private boolean checkSentFromTool(int toolFlag) { 444 | switch (this.matchRelationship) { 445 | case "Burp": 446 | return toolFlag != BurpExtender.getCallbacks().TOOL_EXTENDER && 447 | toolFlag != BurpExtender.getCallbacks().TOOL_SCANNER; 448 | case "Proxy": 449 | return toolFlag == BurpExtender.getCallbacks().TOOL_PROXY; 450 | case "Repeater": 451 | return toolFlag == BurpExtender.getCallbacks().TOOL_REPEATER; 452 | case "Spider": 453 | return toolFlag == BurpExtender.getCallbacks().TOOL_SPIDER; 454 | case "Intruder": 455 | return toolFlag == BurpExtender.getCallbacks().TOOL_INTRUDER; 456 | default: 457 | return toolFlag == BurpExtender.getCallbacks().TOOL_SCANNER; 458 | } 459 | } 460 | 461 | private boolean checkListenerPort(IHttpRequestResponse messageInfo) { 462 | if (this.matchType.equals("Matches")) { 463 | return messageInfo.getHttpService().getPort() == Integer.parseInt(this.matchCondition); 464 | } else { 465 | return !(messageInfo.getHttpService().getPort() == Integer.parseInt(this.matchCondition)); 466 | } 467 | } 468 | 469 | public String getBooleanOperator() { 470 | return booleanOperator; 471 | } 472 | 473 | public void setBooleanOperator(String booleanOperator) { 474 | if(Arrays.stream(BOOLEAN_OPERATOR_OPTIONS).anyMatch(s -> s.equals(booleanOperator))) { 475 | this.booleanOperator = booleanOperator; 476 | } else if (booleanOperator.equals("")) { 477 | this.booleanOperator = booleanOperator; 478 | } else { 479 | this.booleanOperator = BOOLEAN_OPERATOR_OPTIONS[0]; 480 | } 481 | } 482 | 483 | public String getMatchType() { 484 | return matchType; 485 | } 486 | 487 | public void setMatchType(String matchType) { 488 | if(Arrays.stream(MATCH_TYPE_OPTIONS).anyMatch(s -> s.equals(matchType))) { 489 | this.matchType = matchType; 490 | } else { 491 | this.matchType = MATCH_TYPE_OPTIONS[0]; 492 | } 493 | } 494 | 495 | public String getMatchRelationship() { 496 | return matchRelationship; 497 | } 498 | 499 | public void setMatchRelationship(String matchRelationship) { 500 | this.matchRelationship = matchRelationship; 501 | } 502 | 503 | public String getMatchCondition() { 504 | return matchCondition; 505 | } 506 | 507 | public void setMatchCondition(String matchCondition) { 508 | this.matchCondition = matchCondition; 509 | } 510 | 511 | public boolean isEnabled() { 512 | return isEnabled; 513 | } 514 | 515 | public void setEnabled(boolean enabled) { 516 | isEnabled = enabled; 517 | } 518 | 519 | } 520 | -------------------------------------------------------------------------------- /src/burp/Conditions/ConditionTableModel.java: -------------------------------------------------------------------------------- 1 | package burp.Conditions; 2 | 3 | import burp.IHttpRequestResponse; 4 | import javax.swing.table.AbstractTableModel; 5 | import java.util.ArrayList; 6 | 7 | public class ConditionTableModel extends AbstractTableModel { 8 | 9 | private final static String[] columnNames = { 10 | "Enabled", 11 | "Boolean Operator", 12 | "Match Type", 13 | "Match Relationship", 14 | "Match Condition" 15 | }; 16 | 17 | private ArrayList conditions; 18 | 19 | // Setting default conditions 20 | public ConditionTableModel() { 21 | conditions = new ArrayList<>(); 22 | } 23 | 24 | public void add(Condition condition) { conditions.add(condition); } 25 | 26 | public void update(int index, Condition condition) { 27 | if (index == 0) { 28 | condition.setBooleanOperator(""); 29 | } 30 | conditions.set(index, condition); 31 | } 32 | 33 | public boolean check(int toolFlag, IHttpRequestResponse messageInfo) { 34 | boolean meetsConditions = false; 35 | if (getConditions().size() == 0) { 36 | meetsConditions = false; 37 | } else { 38 | if (getConditions() 39 | .stream() 40 | .filter(Condition::isEnabled) 41 | .filter(c -> c.getBooleanOperator().equals("Or")) 42 | .anyMatch(c -> c.checkCondition(toolFlag, messageInfo))) { 43 | meetsConditions = true; 44 | } 45 | if (getConditions() 46 | .stream() 47 | .filter(Condition::isEnabled) 48 | .filter( 49 | c -> c.getBooleanOperator().equals("And") || c.getBooleanOperator().equals("")) 50 | .allMatch(c -> c.checkCondition(toolFlag, messageInfo))) { 51 | meetsConditions = true; 52 | } 53 | } 54 | return meetsConditions; 55 | } 56 | 57 | public ArrayList getConditions() { 58 | return conditions; 59 | } 60 | 61 | public Condition get(int conditionIndex) { 62 | return conditions.get(conditionIndex); 63 | } 64 | 65 | public void remove(int index) { 66 | if (index != 0) { conditions.remove(index); } 67 | } 68 | 69 | public void clear() { 70 | conditions.clear(); 71 | } 72 | 73 | @Override 74 | public int getColumnCount() { 75 | return columnNames.length; 76 | } 77 | 78 | @Override 79 | public int getRowCount() { 80 | return conditions.size(); 81 | } 82 | 83 | @Override 84 | public String getColumnName(int col) { 85 | return columnNames[col]; 86 | } 87 | 88 | @Override 89 | public Object getValueAt(int row, int col) { 90 | Condition tempCondition = conditions.get(row); 91 | switch (col) { 92 | case 0: 93 | return tempCondition.isEnabled(); 94 | case 1: 95 | return tempCondition.getBooleanOperator(); 96 | case 2: 97 | return tempCondition.getMatchType(); 98 | case 3: 99 | return tempCondition.getMatchRelationship(); 100 | case 4: 101 | return tempCondition.getMatchCondition(); 102 | default: 103 | throw new IllegalStateException("getValueAt not defined for "+Integer.toString(col)); 104 | } 105 | } 106 | 107 | @Override 108 | public Class getColumnClass(int column) { 109 | return (getValueAt(0, column).getClass()); 110 | } 111 | 112 | @Override 113 | public boolean isCellEditable(int row, int column) { 114 | return (getColumnName(column).equals("Enabled") && row != 0); 115 | } 116 | 117 | @Override 118 | public void setValueAt(Object value, int row, int col) { 119 | Condition tempCondition = conditions.get(row); 120 | switch (col) { 121 | case 0: 122 | tempCondition.setEnabled((Boolean) value); 123 | break; 124 | case 1: 125 | tempCondition.setBooleanOperator((String) value); 126 | break; 127 | case 2: 128 | tempCondition.setMatchType((String) value); 129 | break; 130 | case 3: 131 | tempCondition.setMatchRelationship((String) value); 132 | break; 133 | default: 134 | tempCondition.setMatchCondition((String) value); 135 | break; 136 | } 137 | conditions.set(row, tempCondition); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/burp/Conditions/Conditions.java: -------------------------------------------------------------------------------- 1 | package burp.Conditions; 2 | 3 | import burp.AutoRepeater; 4 | import burp.BurpExtender; 5 | import java.awt.Color; 6 | import java.awt.GridBagConstraints; 7 | import java.awt.GridBagLayout; 8 | import javax.swing.DefaultComboBoxModel; 9 | import javax.swing.JButton; 10 | import javax.swing.JComboBox; 11 | import javax.swing.JLabel; 12 | import javax.swing.JOptionPane; 13 | import javax.swing.JPanel; 14 | import javax.swing.JScrollPane; 15 | import javax.swing.JTable; 16 | import javax.swing.JTextField; 17 | 18 | public class Conditions { 19 | // Conditions UI 20 | JPanel conditionsPanel; 21 | private JPanel conditionPanel; 22 | private JScrollPane conditionScrollPane; 23 | private JTable conditionTable; 24 | private JPanel conditionsButtonPanel; 25 | private JButton addConditionButton; 26 | private JButton editConditionButton; 27 | private JButton deleteConditionButton; 28 | private JButton duplicateConditionButton; 29 | 30 | // Conditions Popup UI 31 | private JComboBox booleanOperatorComboBox; 32 | private JComboBox matchTypeComboBox; 33 | private JComboBox matchRelationshipComboBox; 34 | private JTextField matchConditionTextField; 35 | 36 | private JLabel booleanOperatorLabel; 37 | private JLabel matchTypeLabel; 38 | private JLabel matchRelationshipLabel; 39 | private JLabel matchConditionLabel; 40 | 41 | private ConditionTableModel conditionTableModel; 42 | 43 | public Conditions() { 44 | conditionTableModel = new ConditionTableModel(); 45 | createUI(); 46 | } 47 | 48 | public ConditionTableModel getConditionTableModel() { return conditionTableModel; } 49 | public JPanel getUI() { return conditionsPanel; } 50 | 51 | private void resetConditionDialog() { 52 | booleanOperatorComboBox.setSelectedIndex(0); 53 | matchTypeComboBox.setSelectedIndex(0); 54 | matchRelationshipComboBox.setSelectedIndex(0); 55 | matchConditionTextField.setText(""); 56 | } 57 | 58 | private void createUI() { 59 | GridBagConstraints c; 60 | //Condition Dialog 61 | c = new GridBagConstraints(); 62 | conditionsPanel = new JPanel(); 63 | conditionPanel = new JPanel(); 64 | conditionPanel.setLayout(new GridBagLayout()); 65 | conditionPanel.setPreferredSize(AutoRepeater.dialogDimension); 66 | 67 | booleanOperatorComboBox = new JComboBox<>(Condition.BOOLEAN_OPERATOR_OPTIONS); 68 | matchTypeComboBox = new JComboBox<>(Condition.MATCH_TYPE_OPTIONS); 69 | matchRelationshipComboBox = new JComboBox<>(Condition.getMatchRelationshipOptions( 70 | Condition.MATCH_TYPE_OPTIONS[0])); 71 | matchConditionTextField = new JTextField(); 72 | 73 | booleanOperatorComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 74 | matchTypeComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 75 | matchRelationshipComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 76 | matchConditionTextField.setPreferredSize(AutoRepeater.textFieldDimension); 77 | 78 | matchTypeComboBox.addActionListener(e -> { 79 | matchRelationshipComboBox 80 | .setModel(new DefaultComboBoxModel<>(Condition.getMatchRelationshipOptions( 81 | (String) matchTypeComboBox.getSelectedItem()))); 82 | matchConditionTextField.setEnabled(Condition.matchConditionIsEditable( 83 | (String) matchTypeComboBox.getSelectedItem())); 84 | }); 85 | 86 | booleanOperatorLabel = new JLabel("Boolean Operator: "); 87 | matchTypeLabel = new JLabel("Match Type: "); 88 | matchRelationshipLabel = new JLabel("Match Relationship: "); 89 | matchConditionLabel = new JLabel("Match Condition: "); 90 | 91 | c.gridx = 0; 92 | c.gridy = 0; 93 | c.anchor = GridBagConstraints.WEST; 94 | conditionPanel.add(booleanOperatorLabel, c); 95 | c.gridy = 1; 96 | conditionPanel.add(matchTypeLabel, c); 97 | c.gridy = 2; 98 | conditionPanel.add(matchRelationshipLabel, c); 99 | c.gridy = 3; 100 | conditionPanel.add(matchConditionLabel, c); 101 | 102 | c.anchor = GridBagConstraints.EAST; 103 | c.fill = GridBagConstraints.HORIZONTAL; 104 | c.gridx = 1; 105 | c.gridy = 0; 106 | conditionPanel.add(booleanOperatorComboBox, c); 107 | c.gridy = 1; 108 | conditionPanel.add(matchTypeComboBox, c); 109 | c.gridy = 2; 110 | conditionPanel.add(matchRelationshipComboBox, c); 111 | c.gridy = 3; 112 | conditionPanel.add(matchConditionTextField, c); 113 | 114 | // Condition Buttons 115 | addConditionButton = new JButton("Add"); 116 | addConditionButton.setPreferredSize(AutoRepeater.buttonDimension); 117 | addConditionButton.setMinimumSize(AutoRepeater.buttonDimension); 118 | addConditionButton.setMaximumSize(AutoRepeater.buttonDimension); 119 | 120 | addConditionButton.addActionListener(e -> { 121 | int result = JOptionPane.showConfirmDialog( 122 | BurpExtender.getParentTabbedPane(), 123 | conditionPanel, 124 | "Add Condition", 125 | JOptionPane.OK_CANCEL_OPTION, 126 | JOptionPane.PLAIN_MESSAGE); 127 | if (result == JOptionPane.OK_OPTION) { 128 | Condition newCondition = new Condition( 129 | (String) booleanOperatorComboBox.getSelectedItem(), 130 | (String) matchTypeComboBox.getSelectedItem(), 131 | (String) matchRelationshipComboBox.getSelectedItem(), 132 | matchConditionTextField.getText() 133 | ); 134 | conditionTableModel.add(newCondition); 135 | conditionTableModel.fireTableDataChanged(); 136 | } 137 | resetConditionDialog(); 138 | }); 139 | 140 | editConditionButton = new JButton("Edit"); 141 | editConditionButton.setPreferredSize(AutoRepeater.buttonDimension); 142 | editConditionButton.setMinimumSize(AutoRepeater.buttonDimension); 143 | editConditionButton.setMaximumSize(AutoRepeater.buttonDimension); 144 | 145 | editConditionButton.addActionListener(e -> { 146 | if (conditionTable.getSelectedRow() != -1) { 147 | int selectedRow = conditionTable.getSelectedRow(); 148 | Condition tempCondition = conditionTableModel.get(selectedRow); 149 | 150 | booleanOperatorComboBox.setSelectedItem(tempCondition.getBooleanOperator()); 151 | matchTypeComboBox.setSelectedItem(tempCondition.getMatchType()); 152 | matchRelationshipComboBox.setSelectedItem(tempCondition.getMatchRelationship()); 153 | matchConditionTextField.setText(tempCondition.getMatchCondition()); 154 | 155 | int result = JOptionPane.showConfirmDialog( 156 | BurpExtender.getParentTabbedPane(), 157 | conditionPanel, 158 | "Edit Condition", 159 | JOptionPane.OK_CANCEL_OPTION, 160 | JOptionPane.PLAIN_MESSAGE); 161 | if (result == JOptionPane.OK_OPTION) { 162 | Condition newCondition = new Condition( 163 | (String) booleanOperatorComboBox.getSelectedItem(), 164 | (String) matchTypeComboBox.getSelectedItem(), 165 | (String) matchRelationshipComboBox.getSelectedItem(), 166 | matchConditionTextField.getText() 167 | ); 168 | newCondition.setEnabled(tempCondition.isEnabled()); 169 | conditionTableModel.update(selectedRow, newCondition); 170 | conditionTableModel.fireTableDataChanged(); 171 | } 172 | resetConditionDialog(); 173 | } 174 | }); 175 | 176 | deleteConditionButton = new JButton("Remove"); 177 | deleteConditionButton.setPreferredSize(AutoRepeater.buttonDimension); 178 | deleteConditionButton.setMinimumSize(AutoRepeater.buttonDimension); 179 | deleteConditionButton.setMaximumSize(AutoRepeater.buttonDimension); 180 | 181 | deleteConditionButton.addActionListener(e -> { 182 | int selectedRow = conditionTable.getSelectedRow(); 183 | if (selectedRow != -1) { 184 | conditionTableModel.remove(selectedRow); 185 | conditionTableModel.fireTableDataChanged(); 186 | } 187 | }); 188 | 189 | 190 | // Duplicate Condition 191 | duplicateConditionButton = new JButton("Duplicate"); 192 | duplicateConditionButton.setPreferredSize(AutoRepeater.buttonDimension); 193 | duplicateConditionButton.setMinimumSize(AutoRepeater.buttonDimension); 194 | duplicateConditionButton.setMaximumSize(AutoRepeater.buttonDimension); 195 | 196 | duplicateConditionButton.addActionListener(e -> { 197 | int selectedRow = conditionTable.getSelectedRow(); 198 | if (conditionTable.getSelectedRow() != -1 199 | && selectedRow < getConditionTableModel().getConditions().size()) { 200 | conditionTableModel.add(new Condition(getConditionTableModel().get(selectedRow))); 201 | conditionTableModel.fireTableDataChanged(); 202 | } 203 | }); 204 | 205 | conditionsButtonPanel = new JPanel(); 206 | conditionsButtonPanel.setLayout(new GridBagLayout()); 207 | conditionsButtonPanel.setPreferredSize(AutoRepeater.buttonPanelDimension); 208 | conditionsButtonPanel.setMaximumSize(AutoRepeater.buttonPanelDimension); 209 | conditionsButtonPanel.setPreferredSize(AutoRepeater.buttonPanelDimension); 210 | 211 | c = new GridBagConstraints(); 212 | c.anchor = GridBagConstraints.FIRST_LINE_END; 213 | c.gridx = 0; 214 | c.weightx = 1; 215 | 216 | conditionsButtonPanel.add(addConditionButton, c); 217 | conditionsButtonPanel.add(editConditionButton, c); 218 | conditionsButtonPanel.add(deleteConditionButton, c); 219 | conditionsButtonPanel.add(duplicateConditionButton, c); 220 | 221 | conditionTableModel = new ConditionTableModel(); 222 | conditionTable = new JTable(conditionTableModel); 223 | conditionTable.getColumnModel().getColumn(0).setMaxWidth(55); 224 | conditionTable.getColumnModel().getColumn(0).setMinWidth(55); 225 | conditionScrollPane = new JScrollPane(conditionTable); 226 | 227 | // Panel containing condition options 228 | conditionsPanel.setLayout(new GridBagLayout()); 229 | c = new GridBagConstraints(); 230 | c.ipady = 0; 231 | c.anchor = GridBagConstraints.PAGE_START; 232 | c.gridx = 0; 233 | conditionsPanel.add(conditionsButtonPanel, c); 234 | c.fill = GridBagConstraints.BOTH; 235 | c.weightx = 1; 236 | c.weighty = 1; 237 | c.gridx = 1; 238 | conditionsPanel.add(conditionScrollPane, c); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/burp/Filter/Filter.java: -------------------------------------------------------------------------------- 1 | package burp.Filter; 2 | 3 | import burp.Conditions.Condition; 4 | import burp.Logs.LogEntry; 5 | 6 | public class Filter extends Condition { 7 | private String originalOrModified; 8 | public static final String[] ORIGINAL_OR_MODIFIED= {"Original", "Modified"}; 9 | 10 | public Filter( 11 | String booleanOperator, 12 | String originalOrModified, 13 | String matchType, 14 | String matchRelationship, 15 | String matchCondition, 16 | boolean isEnabled) { 17 | super(booleanOperator, matchType, matchRelationship, matchCondition, isEnabled); 18 | setOriginalOrModified(originalOrModified); 19 | } 20 | 21 | public Filter( 22 | String booleanOperator, 23 | String originalOrModified, 24 | String matchType, 25 | String matchRelationship, 26 | String matchCondition) { 27 | this(booleanOperator, originalOrModified, matchType, matchRelationship, matchCondition, true); 28 | } 29 | 30 | public Filter(Filter filter) { 31 | this(filter.getBooleanOperator(), 32 | filter.getOriginalOrModified(), 33 | filter.getMatchType(), 34 | filter.getMatchRelationship(), 35 | filter.getMatchCondition(), 36 | filter.isEnabled()); 37 | if(getBooleanOperator().equals("")) { 38 | setBooleanOperator("And"); 39 | } 40 | } 41 | 42 | public boolean checkCondition(LogEntry logEntry) { 43 | if (getOriginalOrModified().equals("Original")) { 44 | return checkCondition(logEntry.getToolFlag(), logEntry.getOriginalRequestResponse()); 45 | } else { 46 | return checkCondition(logEntry.getToolFlag(), logEntry.getModifiedRequestResponse()); 47 | } 48 | } 49 | 50 | public String getOriginalOrModified() { 51 | return originalOrModified; 52 | } 53 | 54 | public void setOriginalOrModified(String originalOrModified) { 55 | if (originalOrModified.equals("Original")) { 56 | this.originalOrModified = "Original"; 57 | } else { 58 | this.originalOrModified = "Modified"; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/burp/Filter/FilterTableModel.java: -------------------------------------------------------------------------------- 1 | package burp.Filter; 2 | 3 | import burp.Conditions.ConditionTableModel; 4 | import burp.Logs.LogEntry; 5 | import java.util.ArrayList; 6 | import java.util.stream.Collectors; 7 | 8 | public class FilterTableModel extends ConditionTableModel { 9 | 10 | private final static String[] columnNames = { 11 | "Enabled", 12 | "Boolean Operator", 13 | "Original Or Modified", 14 | "Match Type", 15 | "Match Relationship", 16 | "Match Condition" 17 | }; 18 | 19 | public boolean check(LogEntry logEntry) { 20 | boolean meetsFilters = false; 21 | if (getFilters().isEmpty()) { 22 | meetsFilters = true; 23 | } else { 24 | if (getFilters() 25 | .stream() 26 | .filter(Filter::isEnabled) 27 | .filter(f -> f.getBooleanOperator().equals("Or")) 28 | .anyMatch(f -> f.checkCondition(logEntry))) { 29 | meetsFilters = true; 30 | } 31 | if (getFilters() 32 | .stream() 33 | .filter(Filter::isEnabled) 34 | .filter(f -> f.getBooleanOperator().equals("And") || f.getBooleanOperator().equals("")) 35 | .allMatch(f -> f.checkCondition(logEntry))) { 36 | meetsFilters = true; 37 | } 38 | } 39 | return meetsFilters; 40 | } 41 | 42 | public ArrayList getFilters() { 43 | return getConditions().stream() 44 | .map(x -> (Filter)x) 45 | .collect(Collectors.toCollection(ArrayList::new)); 46 | } 47 | 48 | public Filter get(int index) { return (Filter)super.get(index); } 49 | 50 | @Override 51 | public String getColumnName(int col) { 52 | return columnNames[col]; 53 | } 54 | 55 | @Override 56 | public int getColumnCount() { 57 | return columnNames.length; 58 | } 59 | 60 | @Override 61 | public Object getValueAt(int row, int col) { 62 | Filter filter = get(row); 63 | switch (col) { 64 | case 0: 65 | return filter.isEnabled(); 66 | case 1: 67 | return filter.getBooleanOperator(); 68 | case 2: 69 | return filter.getOriginalOrModified(); 70 | case 3: 71 | return filter.getMatchType(); 72 | case 4: 73 | return filter.getMatchRelationship(); 74 | case 5: 75 | return filter.getMatchCondition(); 76 | default: 77 | throw new IllegalStateException("getValueAt not defined for "+Integer.toString(col)); 78 | } 79 | } 80 | 81 | @Override 82 | public void setValueAt(Object value, int row, int col) { 83 | Filter filter = get(row); 84 | switch (col) { 85 | case 0: 86 | filter.setEnabled((Boolean) value); 87 | break; 88 | case 1: 89 | filter.setBooleanOperator((String) value); 90 | break; 91 | case 2: 92 | filter.setOriginalOrModified((String) value); 93 | break; 94 | case 3: 95 | filter.setMatchType((String) value); 96 | break; 97 | case 4: 98 | filter.setMatchRelationship((String) value); 99 | break; 100 | case 5: 101 | filter.setMatchCondition((String) value); 102 | break; 103 | default: 104 | throw new IllegalStateException("setValueAt not defined for "+Integer.toString(col)); 105 | } 106 | update(row, filter); 107 | fireTableCellUpdated(row, col); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/burp/Filter/Filters.java: -------------------------------------------------------------------------------- 1 | package burp.Filter; 2 | 3 | import burp.AutoRepeater; 4 | import burp.BurpExtender; 5 | import burp.Logs.LogEntry; 6 | import burp.Logs.LogManager; 7 | import java.awt.GridBagConstraints; 8 | import java.awt.GridBagLayout; 9 | import javax.swing.ButtonGroup; 10 | import javax.swing.DefaultComboBoxModel; 11 | import javax.swing.JButton; 12 | import javax.swing.JComboBox; 13 | import javax.swing.JLabel; 14 | import javax.swing.JOptionPane; 15 | import javax.swing.JPanel; 16 | import javax.swing.JRadioButton; 17 | import javax.swing.JScrollPane; 18 | import javax.swing.JTable; 19 | import javax.swing.JTextField; 20 | 21 | public class Filters { 22 | 23 | // Filters UI 24 | private JPanel filtersPanel; 25 | private JPanel filterPanel; 26 | private JScrollPane filterScrollPane; 27 | private JRadioButton whitelistFilterRadioButton; 28 | private JRadioButton blacklistFilterRadioButton; 29 | private ButtonGroup buttonGroup; 30 | private JTable filterTable; 31 | private JButton addFilterButton; 32 | private JPanel filtersButtonPanel; 33 | private JButton editFilterButton; 34 | private JButton deleteFilterButton; 35 | private JButton duplicateFilterButton; 36 | 37 | // Filters Popup UI 38 | private JComboBox booleanOperatorComboBox; 39 | private JComboBox originalOrModifiedComboBox; 40 | private JComboBox matchTypeComboBox; 41 | private JComboBox matchRelationshipComboBox; 42 | private JTextField matchFilterTextField; 43 | 44 | private JLabel booleanOperatorLabel; 45 | private JLabel originalOrModifiedLabel; 46 | private JLabel matchTypeLabel; 47 | private JLabel matchRelationshipLabel; 48 | private JLabel matchFilterLabel; 49 | 50 | private FilterTableModel filterTableModel; 51 | private LogManager logManager; 52 | private boolean isWhitelist = true; 53 | 54 | public Filters(LogManager logManager) { 55 | filterTableModel = new FilterTableModel(); 56 | this.logManager = logManager; 57 | createUI(); 58 | } 59 | 60 | public FilterTableModel getFilterTableModel() { return filterTableModel; } 61 | public JPanel getUI() { return filtersPanel; } 62 | 63 | private void resetFilterDialog() { 64 | booleanOperatorComboBox.setSelectedIndex(0); 65 | originalOrModifiedComboBox.setSelectedIndex(0); 66 | matchTypeComboBox.setSelectedIndex(0); 67 | matchRelationshipComboBox.setSelectedIndex(0); 68 | matchFilterTextField.setText(""); 69 | } 70 | 71 | public boolean filter(LogEntry logEntry) { 72 | if (isWhitelist) { 73 | return filterTableModel.check(logEntry); 74 | } else { 75 | return !filterTableModel.check(logEntry); 76 | } 77 | } 78 | 79 | public boolean isWhitelist() { return isWhitelist; } 80 | 81 | private void createUI() { 82 | GridBagConstraints c; 83 | //Filter Dialog 84 | c = new GridBagConstraints(); 85 | filtersPanel = new JPanel(); 86 | filterPanel = new JPanel(); 87 | filterPanel.setLayout(new GridBagLayout()); 88 | filterPanel.setPreferredSize(AutoRepeater.dialogDimension); 89 | 90 | whitelistFilterRadioButton = new JRadioButton("Whitelist"); 91 | whitelistFilterRadioButton.setSelected(true); 92 | blacklistFilterRadioButton = new JRadioButton("Blacklist"); 93 | buttonGroup = new ButtonGroup(); 94 | buttonGroup.add(whitelistFilterRadioButton); 95 | buttonGroup.add(blacklistFilterRadioButton); 96 | 97 | booleanOperatorComboBox = new JComboBox<>(Filter.BOOLEAN_OPERATOR_OPTIONS); 98 | originalOrModifiedComboBox = new JComboBox<>(Filter.ORIGINAL_OR_MODIFIED); 99 | matchTypeComboBox = new JComboBox<>(Filter.MATCH_TYPE_OPTIONS); 100 | matchRelationshipComboBox = new JComboBox<>(Filter.getMatchRelationshipOptions( 101 | Filter.MATCH_TYPE_OPTIONS[0])); 102 | matchFilterTextField = new JTextField(); 103 | 104 | booleanOperatorComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 105 | originalOrModifiedComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 106 | matchTypeComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 107 | matchRelationshipComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 108 | matchFilterTextField.setPreferredSize(AutoRepeater.textFieldDimension); 109 | 110 | matchTypeComboBox.addActionListener(e -> { 111 | matchRelationshipComboBox 112 | .setModel(new DefaultComboBoxModel<>(Filter.getMatchRelationshipOptions( 113 | (String) matchTypeComboBox.getSelectedItem()))); 114 | matchFilterTextField.setEnabled(Filter.matchConditionIsEditable( 115 | (String) matchTypeComboBox.getSelectedItem())); 116 | }); 117 | 118 | booleanOperatorLabel = new JLabel("Boolean Operator: "); 119 | originalOrModifiedLabel = new JLabel("Match Original Or Modified: "); 120 | matchTypeLabel = new JLabel("Match Type: "); 121 | matchRelationshipLabel = new JLabel("Match Relationship: "); 122 | matchFilterLabel = new JLabel("Match Condition: "); 123 | 124 | c.gridx = 0; 125 | c.gridy = 0; 126 | c.anchor = GridBagConstraints.WEST; 127 | filterPanel.add(booleanOperatorLabel, c); 128 | c.gridy = 1; 129 | filterPanel.add(originalOrModifiedLabel, c); 130 | c.gridy = 2; 131 | filterPanel.add(matchTypeLabel, c); 132 | c.gridy = 3; 133 | filterPanel.add(matchRelationshipLabel, c); 134 | c.gridy = 4; 135 | filterPanel.add(matchFilterLabel, c); 136 | 137 | c.anchor = GridBagConstraints.EAST; 138 | c.fill = GridBagConstraints.HORIZONTAL; 139 | c.gridx = 1; 140 | c.gridy = 0; 141 | filterPanel.add(booleanOperatorComboBox, c); 142 | c.gridy = 1; 143 | filterPanel.add(originalOrModifiedComboBox, c); 144 | c.gridy = 2; 145 | filterPanel.add(matchTypeComboBox, c); 146 | c.gridy = 3; 147 | filterPanel.add(matchRelationshipComboBox, c); 148 | c.gridy = 4; 149 | filterPanel.add(matchFilterTextField, c); 150 | 151 | // Filter Buttons 152 | addFilterButton = new JButton("Add"); 153 | addFilterButton.setPreferredSize(AutoRepeater.buttonDimension); 154 | addFilterButton.setMinimumSize(AutoRepeater.buttonDimension); 155 | addFilterButton.setMaximumSize(AutoRepeater.buttonDimension); 156 | 157 | addFilterButton.addActionListener(e -> { 158 | int result = JOptionPane.showConfirmDialog( 159 | BurpExtender.getParentTabbedPane(), 160 | filterPanel, 161 | "Add Filter", 162 | JOptionPane.OK_CANCEL_OPTION, 163 | JOptionPane.PLAIN_MESSAGE); 164 | if (result == JOptionPane.OK_OPTION) { 165 | Filter newFilter = new Filter( 166 | (String) booleanOperatorComboBox.getSelectedItem(), 167 | (String) originalOrModifiedComboBox.getSelectedItem(), 168 | (String) matchTypeComboBox.getSelectedItem(), 169 | (String) matchRelationshipComboBox.getSelectedItem(), 170 | matchFilterTextField.getText() 171 | ); 172 | filterTableModel.add(newFilter); 173 | filterTableModel.fireTableDataChanged(); 174 | } 175 | resetFilterDialog(); 176 | }); 177 | 178 | editFilterButton = new JButton("Edit"); 179 | editFilterButton.setPreferredSize(AutoRepeater.buttonDimension); 180 | editFilterButton.setMinimumSize(AutoRepeater.buttonDimension); 181 | editFilterButton.setMaximumSize(AutoRepeater.buttonDimension); 182 | 183 | editFilterButton.addActionListener(e -> { 184 | int selectedRow = filterTable.getSelectedRow(); 185 | if (selectedRow != -1) { 186 | Filter tempFilter = filterTableModel.get(selectedRow); 187 | 188 | booleanOperatorComboBox.setSelectedItem(tempFilter.getBooleanOperator()); 189 | originalOrModifiedComboBox.setSelectedItem(tempFilter.getOriginalOrModified()); 190 | matchTypeComboBox.setSelectedItem(tempFilter.getMatchType()); 191 | matchRelationshipComboBox.setSelectedItem(tempFilter.getMatchRelationship()); 192 | matchFilterTextField.setText(tempFilter.getMatchCondition()); 193 | 194 | int result = JOptionPane.showConfirmDialog( 195 | BurpExtender.getParentTabbedPane(), 196 | filterPanel, 197 | "Edit Filter", 198 | JOptionPane.OK_CANCEL_OPTION, 199 | JOptionPane.PLAIN_MESSAGE); 200 | if (result == JOptionPane.OK_OPTION) { 201 | Filter newFilter = new Filter( 202 | (String) booleanOperatorComboBox.getSelectedItem(), 203 | (String) originalOrModifiedComboBox.getSelectedItem(), 204 | (String) matchTypeComboBox.getSelectedItem(), 205 | (String) matchRelationshipComboBox.getSelectedItem(), 206 | matchFilterTextField.getText() 207 | ); 208 | newFilter.setEnabled(tempFilter.isEnabled()); 209 | 210 | filterTableModel.update(selectedRow, newFilter); 211 | filterTableModel.fireTableDataChanged(); 212 | } 213 | resetFilterDialog(); 214 | } 215 | }); 216 | 217 | deleteFilterButton = new JButton("Remove"); 218 | deleteFilterButton.setPreferredSize(AutoRepeater.buttonDimension); 219 | deleteFilterButton.setMinimumSize(AutoRepeater.buttonDimension); 220 | deleteFilterButton.setMaximumSize(AutoRepeater.buttonDimension); 221 | 222 | deleteFilterButton.addActionListener(e -> { 223 | int selectedRow = filterTable.getSelectedRow(); 224 | if (selectedRow != -1) { 225 | filterTableModel.remove(selectedRow); 226 | filterTableModel.fireTableDataChanged(); 227 | } 228 | }); 229 | 230 | filtersButtonPanel = new JPanel(); 231 | filtersButtonPanel.setLayout(new GridBagLayout()); 232 | 233 | duplicateFilterButton = new JButton("Duplicate"); 234 | duplicateFilterButton.setPreferredSize(AutoRepeater.buttonDimension); 235 | duplicateFilterButton.setMinimumSize(AutoRepeater.buttonDimension); 236 | duplicateFilterButton.setMaximumSize(AutoRepeater.buttonDimension); 237 | 238 | duplicateFilterButton.addActionListener(e -> { 239 | int selectedRow = filterTable.getSelectedRow(); 240 | if (selectedRow != -1 && selectedRow < filterTableModel.getConditions().size()) { 241 | filterTableModel.add(new Filter(filterTableModel.getFilters().get(selectedRow))); 242 | filterTableModel.fireTableDataChanged(); 243 | } 244 | }); 245 | 246 | c = new GridBagConstraints(); 247 | c.anchor = GridBagConstraints.FIRST_LINE_START; 248 | c.gridx = 0; 249 | c.weightx = 1; 250 | 251 | filtersButtonPanel.add(addFilterButton, c); 252 | filtersButtonPanel.add(editFilterButton, c); 253 | filtersButtonPanel.add(deleteFilterButton, c); 254 | filtersButtonPanel.add(duplicateFilterButton, c); 255 | filtersButtonPanel.add(whitelistFilterRadioButton, c); 256 | filtersButtonPanel.add(blacklistFilterRadioButton, c); 257 | 258 | filterTableModel = new FilterTableModel(); 259 | filterTable = new JTable(filterTableModel); 260 | filterTable.getColumnModel().getColumn(0).setMinWidth(55); 261 | filterTable.getColumnModel().getColumn(0).setMaxWidth(55); 262 | 263 | filterTable.setPreferredSize(AutoRepeater.tableDimension); 264 | filterTable.setMaximumSize(AutoRepeater.tableDimension); 265 | filterTable.setMinimumSize(AutoRepeater.tableDimension); 266 | 267 | filterScrollPane = new JScrollPane(filterTable); 268 | 269 | // Panel containing filter options 270 | filtersPanel.setLayout(new GridBagLayout()); 271 | c = new GridBagConstraints(); 272 | c.ipady = 0; 273 | c.anchor = GridBagConstraints.PAGE_START; 274 | c.gridx = 0; 275 | filtersPanel.add(filtersButtonPanel, c); 276 | c.fill = GridBagConstraints.BOTH; 277 | c.weightx = 1; 278 | c.weighty = 1; 279 | c.gridx = 1; 280 | filtersPanel.add(filterScrollPane, c); 281 | // Refilter the logs whenever anything is touched. For whatever reason click the enabled 282 | 283 | whitelistFilterRadioButton.addActionListener(e -> { 284 | setWhitelist(whitelistFilterRadioButton.isSelected()); 285 | logManager.setFilter(this); 286 | }); 287 | blacklistFilterRadioButton.addActionListener(e -> { 288 | setWhitelist(!blacklistFilterRadioButton.isSelected()); 289 | logManager.setFilter(this); 290 | }); 291 | filterTableModel.addTableModelListener(e -> logManager.setFilter(this)); 292 | } 293 | 294 | public void setWhitelist(boolean whitelist) { 295 | isWhitelist = whitelist; 296 | if(isWhitelist) { 297 | whitelistFilterRadioButton.setSelected(true); 298 | } else { 299 | blacklistFilterRadioButton.setSelected(true); 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/burp/Highlighter/Highlighter.java: -------------------------------------------------------------------------------- 1 | package burp.Highlighter; 2 | 3 | import burp.Filter.Filter; 4 | import java.awt.Color; 5 | 6 | public class Highlighter extends Filter { 7 | public final static Color[] COLORS = { 8 | new Color(0xFFFFFF), 9 | new Color(0xFB6063), 10 | new Color(0xFFC562), 11 | new Color(0xFDFF5F), 12 | new Color(0x60FE62), 13 | new Color(0x64FFFF), 14 | new Color(0x6262FF), 15 | new Color(0xFFC6CC), 16 | new Color(0xFE63FD) , 17 | new Color(0xB3B5B2), 18 | }; 19 | 20 | public final static Color[] SELECTED_COLORS = { 21 | new Color(0xFFC498), 22 | new Color(0xDF4444), 23 | new Color(0xDFa844), 24 | new Color(0xDFDF44), 25 | new Color(0x44DF44), 26 | new Color(0x44DFDF), 27 | new Color(0x4444DF), 28 | new Color(0xDFA8A8), 29 | new Color(0xDF44DF), 30 | new Color(0x949494), 31 | }; 32 | 33 | public final static String[] COLOR_NAMES = { 34 | "WHITE", 35 | "RED", 36 | "ORANGE", 37 | "YELLOW", 38 | "GREEN", 39 | "CYAN", 40 | "PURPLE", 41 | "PINK", 42 | "MAGENTA", 43 | "GRAY" 44 | }; 45 | 46 | 47 | public static Color getColorFromColorName(String colorName) { 48 | for(int i = 0; i < Highlighter.COLOR_NAMES.length; i++) { 49 | if (Highlighter.COLOR_NAMES[i].equals(colorName)) { 50 | return Highlighter.COLORS[i]; 51 | } 52 | } 53 | return Highlighter.COLORS[0]; 54 | } 55 | 56 | public static Color getSelectedColorFromColorName(String colorName) { 57 | for(int i = 0; i < Highlighter.COLOR_NAMES.length; i++) { 58 | if (Highlighter.COLOR_NAMES[i].equals(colorName)) { 59 | return Highlighter.SELECTED_COLORS[i]; 60 | } 61 | } 62 | return Highlighter.SELECTED_COLORS[0]; 63 | } 64 | 65 | public Highlighter( 66 | String booleanOperator, 67 | String originalOrModified, 68 | String matchType, 69 | String matchRelationship, 70 | String matchCondition, 71 | boolean isEnabled) { 72 | super(booleanOperator, originalOrModified, matchType, matchRelationship, matchCondition, isEnabled); 73 | } 74 | 75 | public Highlighter( 76 | String booleanOperator, 77 | String originalOrModified, 78 | String matchType, 79 | String matchRelationship, 80 | String matchCondition) { 81 | this(booleanOperator, originalOrModified, matchType, matchRelationship, matchCondition, true); 82 | } 83 | 84 | public Highlighter(Highlighter highlighter) { 85 | this(highlighter.getBooleanOperator(), 86 | highlighter.getOriginalOrModified(), 87 | highlighter.getMatchType(), 88 | highlighter.getMatchRelationship(), 89 | highlighter.getMatchCondition(), 90 | highlighter.isEnabled()); 91 | if (this.getBooleanOperator().equals("")) { 92 | setBooleanOperator("And"); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/burp/Highlighter/HighlighterTableModel.java: -------------------------------------------------------------------------------- 1 | package burp.Highlighter; 2 | 3 | import burp.Filter.FilterTableModel; 4 | import java.awt.Color; 5 | import java.util.ArrayList; 6 | import java.util.stream.Collectors; 7 | 8 | public class HighlighterTableModel extends FilterTableModel{ 9 | private String colorName; 10 | private boolean isEnabled; 11 | private Color backgroundColor; 12 | private Color selectedBackgroundColor; 13 | private String comment; 14 | 15 | public HighlighterTableModel() { 16 | super(); 17 | backgroundColor = Highlighter.COLORS[0]; 18 | selectedBackgroundColor = Highlighter.SELECTED_COLORS[0]; 19 | setColorName(Highlighter.COLOR_NAMES[0]); 20 | } 21 | 22 | public void setComment(String comment) { 23 | this.comment = comment; 24 | fireTableDataChanged(); 25 | } 26 | 27 | public String getComment() {return this.comment;} 28 | 29 | public Color getColor() { 30 | return backgroundColor; 31 | } 32 | 33 | public Color getSelectedColor() { 34 | return selectedBackgroundColor; 35 | } 36 | 37 | public void add(Highlighter highlighter) { 38 | super.add(highlighter); 39 | // Clear out the boolean if it's the first entry 40 | if (getConditions().get(0).equals(highlighter)) { 41 | getConditions().get(0).setBooleanOperator(""); 42 | } 43 | fireTableDataChanged(); 44 | } 45 | 46 | @Override 47 | public void remove(int index) { 48 | getConditions().remove(index); 49 | // Clear out the boolean if it's the first entry 50 | getConditions().get(0).setBooleanOperator(""); 51 | fireTableDataChanged(); 52 | } 53 | 54 | public ArrayList getHighlighters() { 55 | return getConditions().stream() 56 | .map(x -> (Highlighter)x) 57 | .collect(Collectors.toCollection(ArrayList::new)); 58 | } 59 | 60 | public HighlighterTableModel (HighlighterTableModel highlighterTableModel) { 61 | super(); 62 | for (Highlighter highlighter : highlighterTableModel.getHighlighters()) { 63 | add(highlighter); 64 | } 65 | this.colorName = highlighterTableModel.getColorName(); 66 | this.isEnabled = highlighterTableModel.isEnabled(); 67 | this.backgroundColor = highlighterTableModel.getColor(); 68 | this.selectedBackgroundColor = highlighterTableModel.getSelectedColor(); 69 | this.comment = highlighterTableModel.getComment(); 70 | } 71 | 72 | public String getColorName() { return colorName; } 73 | 74 | public void setColorName(String colorName) { 75 | for(int i = 0; i < Highlighter.COLOR_NAMES.length; i++) { 76 | if (Highlighter.COLOR_NAMES[i].equals(colorName)) { 77 | this.colorName = colorName; 78 | this.backgroundColor = Highlighter.COLORS[i]; 79 | this.selectedBackgroundColor = Highlighter.SELECTED_COLORS[i]; 80 | fireTableDataChanged(); 81 | return; 82 | } 83 | } 84 | // This should never actually happen 85 | this.colorName = Highlighter.COLOR_NAMES[0]; 86 | this.backgroundColor = Highlighter.COLORS[0]; 87 | this.selectedBackgroundColor = Highlighter.SELECTED_COLORS[0]; 88 | fireTableDataChanged(); 89 | } 90 | 91 | public Highlighter get(int index) { return (Highlighter)super.get(index); } 92 | 93 | public boolean isEnabled() { 94 | return isEnabled; 95 | } 96 | 97 | public void setEnabled(boolean enabled) { 98 | isEnabled = enabled; 99 | fireTableDataChanged(); 100 | } 101 | 102 | @Override 103 | public boolean isCellEditable(int row, int column) { 104 | return (getColumnName(column).equals("Enabled")); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/burp/Highlighter/HighlighterUITableModel.java: -------------------------------------------------------------------------------- 1 | package burp.Highlighter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.stream.Collectors; 5 | import javax.swing.table.AbstractTableModel; 6 | 7 | public class HighlighterUITableModel extends AbstractTableModel { 8 | private ArrayList tableModels; 9 | 10 | private static final String[] columnNames = {"Enabled", "Color", "Comment"}; 11 | 12 | public HighlighterUITableModel() { 13 | tableModels = new ArrayList<>(); 14 | } 15 | 16 | public HighlighterUITableModel(HighlighterUITableModel highlighterUITableModel) { 17 | this(); 18 | tableModels.addAll(highlighterUITableModel.getTableModels()); 19 | } 20 | 21 | public ArrayList getTableModels() { return tableModels; } 22 | 23 | public void add(HighlighterTableModel tableModel) { 24 | tableModels.add(tableModel); 25 | fireTableDataChanged(); 26 | } 27 | 28 | public void update(int index, HighlighterTableModel tableModel) { 29 | tableModels.set(index, tableModel); 30 | fireTableDataChanged(); 31 | } 32 | 33 | public HighlighterTableModel get(int index) { 34 | return tableModels.get(index); 35 | } 36 | 37 | public void remove(int index) { 38 | tableModels.remove(index); 39 | } 40 | 41 | @Override 42 | public int getRowCount() { 43 | return tableModels.size(); 44 | } 45 | 46 | @Override 47 | public int getColumnCount() { 48 | return columnNames.length; 49 | } 50 | 51 | @Override 52 | public void setValueAt(Object value, int row, int col) { 53 | HighlighterTableModel tableModel = tableModels.get(row); 54 | switch (col) { 55 | case 0: 56 | tableModel.setEnabled((Boolean) value); 57 | break; 58 | default: 59 | break; 60 | } 61 | tableModels.set(row, tableModel); 62 | fireTableCellUpdated(row, col); 63 | } 64 | 65 | @Override 66 | public Class getColumnClass(int column) { 67 | switch (column) { 68 | case 0: 69 | return Boolean.class; 70 | case 1: 71 | return String.class; 72 | case 2: 73 | return String.class; 74 | default: 75 | throw new IllegalStateException("getColumnClass not defined for "+Integer.toString(column)); 76 | } 77 | } 78 | 79 | @Override 80 | public Object getValueAt(int row, int col) { 81 | HighlighterTableModel tableModel = tableModels.get(row); 82 | switch (col) { 83 | case 0: 84 | return tableModel.isEnabled(); 85 | case 1: 86 | return tableModel.getColorName(); 87 | case 2: 88 | return tableModel.getComment(); 89 | default: 90 | throw new IllegalStateException("getValueAt not defined for "+Integer.toString(col)); 91 | } 92 | } 93 | 94 | @Override 95 | public boolean isCellEditable(int row, int column) { 96 | return (getColumnName(column).equals("Enabled")); 97 | } 98 | 99 | @Override 100 | public String getColumnName(int col) { 101 | return columnNames[col]; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/burp/Highlighter/Highlighters.java: -------------------------------------------------------------------------------- 1 | package burp.Highlighter; 2 | 3 | import burp.AutoRepeater; 4 | import burp.AutoRepeater.LogTable; 5 | import burp.BurpExtender; 6 | import burp.Logs.LogEntry; 7 | import burp.Logs.LogManager; 8 | import java.awt.Component; 9 | import java.awt.GridBagConstraints; 10 | import java.awt.GridBagLayout; 11 | import javax.swing.BoxLayout; 12 | import javax.swing.DefaultComboBoxModel; 13 | import javax.swing.DefaultListCellRenderer; 14 | import javax.swing.JButton; 15 | import javax.swing.JComboBox; 16 | import javax.swing.JLabel; 17 | import javax.swing.JOptionPane; 18 | import javax.swing.JPanel; 19 | import javax.swing.JScrollPane; 20 | import javax.swing.JTable; 21 | import javax.swing.JTextField; 22 | import javax.swing.event.DocumentEvent; 23 | import javax.swing.event.DocumentListener; 24 | import javax.swing.table.DefaultTableCellRenderer; 25 | 26 | public class Highlighters { 27 | // Highlighters UI 28 | private JPanel highlightsPanel; 29 | 30 | // Highlighters Popup UI 31 | private JComboBox booleanOperatorComboBox; 32 | private JComboBox originalOrModifiedComboBox; 33 | private JComboBox matchTypeComboBox; 34 | private JComboBox matchRelationshipComboBox; 35 | private JTextField matchHighlighterTextField; 36 | 37 | // Highlighters Menu UI 38 | private JLabel booleanOperatorLabel; 39 | private JLabel originalOrModifiedLabel; 40 | private JLabel matchTypeLabel; 41 | private JLabel matchRelationshipLabel; 42 | private JLabel matchHighlighterLabel; 43 | private JTable highlighterTable; 44 | 45 | private HighlighterUITableModel highlighterUITableModel; 46 | private LogManager logManager; 47 | private LogTable logTable; 48 | 49 | public Highlighters(LogManager logManager, LogTable logTable) { 50 | highlighterUITableModel = new HighlighterUITableModel(); 51 | highlighterUITableModel.addTableModelListener(l -> highlight()); 52 | this.logManager = logManager; 53 | this.logTable = logTable; 54 | highlightsPanel = createMenuUI(); 55 | } 56 | 57 | public HighlighterUITableModel getHighlighterUITableModel() { return highlighterUITableModel; } 58 | public JPanel getUI() { return highlightsPanel; } 59 | 60 | public void highlight() { 61 | for (LogEntry logEntry : logManager.getLogTableModel().getLog()) { 62 | highlight(logEntry); 63 | } 64 | logTable.repaint(); 65 | } 66 | 67 | public void highlight(LogEntry logEntry) { 68 | logEntry.setBackgroundColor(Highlighter.COLORS[0], Highlighter.SELECTED_COLORS[0]); 69 | for (HighlighterTableModel highlighterTableModel : highlighterUITableModel.getTableModels()) { 70 | //if (highlighterTableModel.isEnabled()) { 71 | //for (Highlighter highlighter : highlighterTableModel.getHighlighters()) { 72 | if (highlighterTableModel.check(logEntry)) { 73 | logEntry.setBackgroundColor( 74 | highlighterTableModel.getColor(), highlighterTableModel.getSelectedColor()); 75 | } 76 | //} 77 | //} 78 | } 79 | logTable.repaint(); 80 | } 81 | 82 | private JPanel createMenuUI() { 83 | GridBagConstraints c; 84 | JPanel menuPanel = new JPanel(); 85 | JButton addHighlighterButton = new JButton("Add"); 86 | JButton editHighlighterButton = new JButton("Edit"); 87 | JButton deleteHighlighterButton = new JButton("Remove"); 88 | JButton duplicateHighlighterButton = new JButton("Duplicate"); 89 | JPanel buttonsPanel = new JPanel(); 90 | JTable menuTable = new JTable(highlighterUITableModel); 91 | 92 | menuTable.getColumnModel().getColumn(0).setMaxWidth(55); 93 | menuTable.getColumnModel().getColumn(0).setMinWidth(55); 94 | menuTable.getColumnModel().getColumn(1).setMaxWidth(70); 95 | menuTable.getColumnModel().getColumn(1).setMinWidth(70); 96 | //menuTable.getColumnModel().getColumn(2).setPreferredWidth(20); 97 | 98 | menuTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() { 99 | @Override 100 | public Component getTableCellRendererComponent( 101 | JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 102 | Component c = 103 | super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 104 | // Only color the color column 105 | if (column == 1) { 106 | c.setBackground(highlighterUITableModel.getTableModels().get(row).getColor()); 107 | if (isSelected) { 108 | c.setBackground(highlighterUITableModel.getTableModels().get(row).getSelectedColor()); 109 | } 110 | } else { 111 | c.setBackground(Highlighter.COLORS[0]); 112 | if (isSelected) { 113 | c.setBackground(Highlighter.SELECTED_COLORS[0]); 114 | } 115 | } 116 | return c; 117 | } 118 | }); 119 | 120 | addHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 121 | editHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 122 | deleteHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 123 | duplicateHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 124 | 125 | addHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 126 | editHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 127 | deleteHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 128 | duplicateHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 129 | 130 | addHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 131 | editHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 132 | deleteHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 133 | duplicateHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 134 | 135 | buttonsPanel.setPreferredSize(AutoRepeater.buttonPanelDimension); 136 | 137 | buttonsPanel.setLayout(new GridBagLayout()); 138 | c = new GridBagConstraints(); 139 | c.anchor = GridBagConstraints.FIRST_LINE_START; 140 | c.gridx = 0; 141 | c.weightx = 0; 142 | buttonsPanel.add(addHighlighterButton, c); 143 | buttonsPanel.add(editHighlighterButton, c); 144 | buttonsPanel.add(deleteHighlighterButton, c); 145 | buttonsPanel.add(duplicateHighlighterButton, c); 146 | 147 | JScrollPane menuScrollPane = new JScrollPane(menuTable); 148 | 149 | // Panel containing filter options 150 | menuPanel.setLayout(new GridBagLayout()); 151 | c = new GridBagConstraints(); 152 | c.ipady = 0; 153 | c.anchor = GridBagConstraints.PAGE_START; 154 | c.gridx = 0; 155 | c.gridy = 1; 156 | menuPanel.add(buttonsPanel, c); 157 | c.fill = GridBagConstraints.BOTH; 158 | c.weightx = 1; 159 | c.weighty = 1; 160 | c.gridx = 1; 161 | menuPanel.add(menuScrollPane, c); 162 | 163 | addHighlighterButton.addActionListener(l -> { 164 | HighlighterTableModel tableModel = new HighlighterTableModel(); 165 | tableModel.addTableModelListener(e -> highlight()); 166 | int result = JOptionPane.showConfirmDialog( 167 | BurpExtender.getParentTabbedPane(), 168 | createHighlighterUI(tableModel), 169 | "Add Highlighter", 170 | JOptionPane.OK_CANCEL_OPTION, 171 | JOptionPane.PLAIN_MESSAGE); 172 | if (result == JOptionPane.OK_OPTION) { 173 | HighlighterTableModel tempTableModel = new HighlighterTableModel(tableModel); 174 | if(tempTableModel.getConditions().size() > 0) { 175 | tempTableModel.setEnabled(true); 176 | highlighterUITableModel.add(tempTableModel); 177 | highlight(); 178 | highlighterUITableModel.fireTableDataChanged(); 179 | } 180 | } 181 | }); 182 | editHighlighterButton.addActionListener(l -> { 183 | if (menuTable.getSelectedRow() != -1) { 184 | HighlighterTableModel tableModel = highlighterUITableModel.get(menuTable.getSelectedRow()); 185 | tableModel.addTableModelListener(e -> highlight()); 186 | int result = JOptionPane.showConfirmDialog( 187 | BurpExtender.getParentTabbedPane(), 188 | createHighlighterUI(tableModel), 189 | "Add Highlighter", 190 | JOptionPane.OK_CANCEL_OPTION, 191 | JOptionPane.PLAIN_MESSAGE); 192 | if (result == JOptionPane.OK_OPTION) { 193 | HighlighterTableModel tempTableModel = new HighlighterTableModel(tableModel); 194 | if (tempTableModel.getConditions().size() > 0) { 195 | highlighterUITableModel.update(menuTable.getSelectedRow(), tempTableModel); 196 | highlight(); 197 | highlighterUITableModel.fireTableDataChanged(); 198 | } 199 | } 200 | } 201 | }); 202 | deleteHighlighterButton.addActionListener(l -> { 203 | if (menuTable.getSelectedRow() != -1) { 204 | highlighterUITableModel.remove(menuTable.getSelectedRow()); 205 | highlight(); 206 | highlighterUITableModel.fireTableDataChanged(); 207 | } 208 | }); 209 | duplicateHighlighterButton.addActionListener(l -> { 210 | int selectedRow = menuTable.getSelectedRow(); 211 | if (selectedRow != -1 && selectedRow < highlighterUITableModel.getRowCount()) { 212 | highlighterUITableModel.add(new HighlighterTableModel(highlighterUITableModel.get(selectedRow))); 213 | highlighterUITableModel.fireTableDataChanged(); 214 | } 215 | }); 216 | return menuPanel; 217 | } 218 | 219 | // This is the UI for the highlighter pop up with the table 220 | private JPanel createHighlighterUI(HighlighterTableModel highlighterTableModel) { 221 | GridBagConstraints c; 222 | JPanel menuPanel = new JPanel(); 223 | JButton addHighlighterButton = new JButton("Add"); 224 | JButton editHighlighterButton = new JButton("Edit"); 225 | JButton deleteHighlighterButton = new JButton("Remove"); 226 | JButton duplicateHighlighterButton = new JButton("Duplicate"); 227 | JPanel buttonsPanel = new JPanel(); 228 | JTextField commentTextField = new JTextField(); 229 | commentTextField.setText(highlighterTableModel.getComment()); 230 | JLabel commentTextFieldLabel = new JLabel("Comment: "); 231 | 232 | commentTextField.getDocument().addDocumentListener(new DocumentListener() { 233 | @Override 234 | public void insertUpdate(DocumentEvent e) { 235 | highlighterTableModel.setComment(commentTextField.getText()); 236 | } 237 | @Override 238 | public void removeUpdate(DocumentEvent e) { 239 | highlighterTableModel.setComment(commentTextField.getText()); 240 | } 241 | @Override 242 | public void changedUpdate(DocumentEvent e) { 243 | highlighterTableModel.setComment(commentTextField.getText()); 244 | } 245 | } 246 | ); 247 | 248 | commentTextFieldLabel.setMaximumSize(AutoRepeater.buttonDimension); 249 | commentTextFieldLabel.setMinimumSize(AutoRepeater.buttonDimension); 250 | commentTextFieldLabel.setPreferredSize(AutoRepeater.buttonDimension); 251 | 252 | highlighterTable = new JTable(highlighterTableModel); 253 | highlighterTable.getColumnModel().getColumn(0).setMaxWidth(55); 254 | highlighterTable.getColumnModel().getColumn(0).setMinWidth(55); 255 | 256 | JLabel colorComboBoxLabel = new JLabel("Highlight Color: "); 257 | JComboBox colorComboBox = new JComboBox<>(Highlighter.COLOR_NAMES); 258 | colorComboBox.setSelectedItem(highlighterTableModel.getColorName()); 259 | colorComboBox.addActionListener(e -> 260 | highlighterTableModel.setColorName((String)colorComboBox.getSelectedItem())); 261 | colorComboBox.setRenderer((list, value, index, isSelected, cellHasFocus) -> { 262 | DefaultListCellRenderer defaultListCellRenderer = new DefaultListCellRenderer(); 263 | JLabel label = 264 | (JLabel) defaultListCellRenderer 265 | .getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 266 | label.setOpaque(true); 267 | for (int i = 0; i < Highlighter.COLOR_NAMES.length; i++) { 268 | if (label.getText().equals(Highlighter.COLOR_NAMES[i])) { 269 | label.setBackground(Highlighter.COLORS[i]); 270 | } 271 | } 272 | return label; 273 | }); 274 | colorComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 275 | colorComboBox.setMinimumSize(AutoRepeater.comboBoxDimension); 276 | colorComboBox.setMaximumSize(AutoRepeater.comboBoxDimension); 277 | 278 | JPanel colorComboBoxPanel = new JPanel(); 279 | colorComboBoxPanel.setLayout(new GridBagLayout()); 280 | c = new GridBagConstraints(); 281 | c.anchor = GridBagConstraints.FIRST_LINE_START; 282 | c.gridx = 0; 283 | c.weightx = 0; 284 | colorComboBoxPanel.add(colorComboBoxLabel, c); 285 | c.gridx = 1; 286 | colorComboBoxPanel.add(colorComboBox, c); 287 | 288 | addHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 289 | addHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 290 | addHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 291 | 292 | editHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 293 | editHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 294 | editHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 295 | 296 | deleteHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 297 | deleteHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 298 | deleteHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 299 | 300 | duplicateHighlighterButton.setPreferredSize(AutoRepeater.buttonDimension); 301 | duplicateHighlighterButton.setMinimumSize(AutoRepeater.buttonDimension); 302 | duplicateHighlighterButton.setMaximumSize(AutoRepeater.buttonDimension); 303 | 304 | buttonsPanel.setPreferredSize(AutoRepeater.buttonPanelDimension); 305 | 306 | buttonsPanel.setLayout(new GridBagLayout()); 307 | c = new GridBagConstraints(); 308 | c.anchor = GridBagConstraints.FIRST_LINE_START; 309 | c.gridx = 0; 310 | c.weightx = 0; 311 | buttonsPanel.add(addHighlighterButton, c); 312 | buttonsPanel.add(editHighlighterButton, c); 313 | buttonsPanel.add(deleteHighlighterButton, c); 314 | buttonsPanel.add(duplicateHighlighterButton, c); 315 | 316 | JScrollPane menuScrollPane = new JScrollPane(highlighterTable); 317 | 318 | // Panel containing filter options 319 | menuPanel.setLayout(new GridBagLayout()); 320 | c = new GridBagConstraints(); 321 | c.ipady = 0; 322 | c.anchor = GridBagConstraints.PAGE_START; 323 | c.gridx = 0; 324 | c.gridy = 1; 325 | menuPanel.add(buttonsPanel, c); 326 | c.fill = GridBagConstraints.BOTH; 327 | c.weightx = 1; 328 | c.weighty = 1; 329 | c.gridx = 1; 330 | menuPanel.add(menuScrollPane, c); 331 | 332 | JPanel outputPanel = new JPanel(); 333 | outputPanel.setLayout(new BoxLayout(outputPanel, BoxLayout.PAGE_AXIS)); 334 | outputPanel.add(colorComboBoxPanel); 335 | outputPanel.add(menuPanel); 336 | outputPanel.setPreferredSize(AutoRepeater.dialogDimension); 337 | outputPanel.setMaximumSize(AutoRepeater.dialogDimension); 338 | outputPanel.setMinimumSize(AutoRepeater.dialogDimension); 339 | JPanel commentPanel = new JPanel(); 340 | commentPanel.setLayout(new BoxLayout(commentPanel, BoxLayout.LINE_AXIS)); 341 | commentPanel.add(commentTextFieldLabel); 342 | commentPanel.add(commentTextField); 343 | outputPanel.add(commentPanel); 344 | 345 | // Button Actions 346 | addHighlighterButton.addActionListener(l -> { 347 | int result = JOptionPane.showConfirmDialog( 348 | BurpExtender.getParentTabbedPane(), 349 | createHighlightEditorUI(highlighterTableModel), 350 | "Edit Highlighter", 351 | JOptionPane.OK_CANCEL_OPTION, 352 | JOptionPane.PLAIN_MESSAGE); 353 | if (result == JOptionPane.OK_OPTION) { 354 | highlighterTableModel.add( 355 | new Highlighter( 356 | (String) booleanOperatorComboBox.getSelectedItem(), 357 | (String) originalOrModifiedComboBox.getSelectedItem(), 358 | (String) matchTypeComboBox.getSelectedItem(), 359 | (String) matchRelationshipComboBox.getSelectedItem(), 360 | matchHighlighterTextField.getText(), 361 | true 362 | ) 363 | ); 364 | highlighterTableModel.fireTableDataChanged(); 365 | } 366 | resetHighlighterDialog(); 367 | }); 368 | editHighlighterButton.addActionListener(l -> { 369 | if (highlighterTable.getSelectedRow() != -1) { 370 | int result = JOptionPane.showConfirmDialog( 371 | BurpExtender.getParentTabbedPane(), 372 | createHighlightEditorUI(highlighterTableModel), 373 | "Edit Highlighter", 374 | JOptionPane.OK_CANCEL_OPTION, 375 | JOptionPane.PLAIN_MESSAGE); 376 | if (result == JOptionPane.OK_OPTION) { 377 | Highlighter newHighlighter = new Highlighter( 378 | (String) booleanOperatorComboBox.getSelectedItem(), 379 | (String) originalOrModifiedComboBox.getSelectedItem(), 380 | (String) matchTypeComboBox.getSelectedItem(), 381 | (String) matchRelationshipComboBox.getSelectedItem(), 382 | matchHighlighterTextField.getText() 383 | ); 384 | newHighlighter.setEnabled(newHighlighter.isEnabled()); 385 | highlighterTableModel.update(highlighterTable.getSelectedRow(), newHighlighter); 386 | highlighterTableModel.fireTableDataChanged(); 387 | } 388 | } 389 | resetHighlighterDialog(); 390 | }); 391 | deleteHighlighterButton.addActionListener(l -> { 392 | if (highlighterTable.getSelectedRow() != -1 ) { 393 | highlighterTableModel.remove(highlighterTable.getSelectedRow()); 394 | highlighterTableModel.fireTableDataChanged(); 395 | } 396 | }); 397 | duplicateHighlighterButton.addActionListener(l -> { 398 | int selectedRow = highlighterTable.getSelectedRow(); 399 | if (selectedRow != -1 && selectedRow < highlighterTableModel.getHighlighters().size()) { 400 | highlighterTableModel.add( 401 | new Highlighter( 402 | highlighterTableModel.getHighlighters().get(highlighterTable.getSelectedRow()))); 403 | highlighterTableModel.fireTableDataChanged(); 404 | } 405 | }); 406 | return outputPanel; 407 | } 408 | 409 | private void resetHighlighterDialog() { 410 | booleanOperatorComboBox.setSelectedIndex(0); 411 | originalOrModifiedComboBox.setSelectedIndex(0); 412 | matchTypeComboBox.setSelectedIndex(0); 413 | matchRelationshipComboBox.setSelectedItem(0); 414 | matchHighlighterTextField.setText(""); 415 | } 416 | 417 | public JPanel createHighlightEditorUI(HighlighterTableModel highlighterTableModel) { 418 | booleanOperatorComboBox = new JComboBox<>(Highlighter.BOOLEAN_OPERATOR_OPTIONS); 419 | originalOrModifiedComboBox = new JComboBox<>(Highlighter.ORIGINAL_OR_MODIFIED); 420 | matchTypeComboBox = new JComboBox<>(Highlighter.MATCH_TYPE_OPTIONS); 421 | matchRelationshipComboBox = new JComboBox<>( 422 | Highlighter.getMatchRelationshipOptions( 423 | Highlighter.MATCH_TYPE_OPTIONS[0])); 424 | matchHighlighterTextField = new JTextField(); 425 | 426 | booleanOperatorComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 427 | originalOrModifiedComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 428 | matchTypeComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 429 | matchRelationshipComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 430 | matchHighlighterTextField.setPreferredSize(AutoRepeater.textFieldDimension); 431 | 432 | matchTypeComboBox.addActionListener(e -> { 433 | matchRelationshipComboBox 434 | .setModel(new DefaultComboBoxModel<>(Highlighter.getMatchRelationshipOptions( 435 | (String) matchTypeComboBox.getSelectedItem()))); 436 | matchHighlighterTextField.setEnabled(Highlighter.matchConditionIsEditable( 437 | (String) matchTypeComboBox.getSelectedItem())); 438 | }); 439 | 440 | if (highlighterTable.getSelectedRow() != -1) { 441 | Highlighter tempHighlighter = highlighterTableModel.getHighlighters() 442 | .get(highlighterTable.getSelectedRow()); 443 | booleanOperatorComboBox.setSelectedItem(tempHighlighter.getBooleanOperator()); 444 | originalOrModifiedComboBox.setSelectedItem(tempHighlighter.getOriginalOrModified()); 445 | matchTypeComboBox.setSelectedItem(tempHighlighter.getMatchType()); 446 | matchRelationshipComboBox.setSelectedItem(tempHighlighter.getMatchRelationship()); 447 | matchHighlighterTextField.setText(tempHighlighter.getMatchCondition()); 448 | } 449 | 450 | booleanOperatorLabel = new JLabel("Boolean Operator: "); 451 | originalOrModifiedLabel = new JLabel("Match Original Or Modified: "); 452 | matchTypeLabel = new JLabel("Match Type: "); 453 | matchRelationshipLabel = new JLabel("Match Relationship: "); 454 | matchHighlighterLabel = new JLabel("Match Condition: "); 455 | 456 | JPanel outputPanel = new JPanel(); 457 | outputPanel.setLayout(new GridBagLayout()); 458 | GridBagConstraints c; 459 | c = new GridBagConstraints(); 460 | 461 | c.gridx = 0; 462 | c.gridy = 0; 463 | c.anchor = GridBagConstraints.WEST; 464 | outputPanel.add(booleanOperatorLabel, c); 465 | c.gridy = 1; 466 | outputPanel.add(originalOrModifiedLabel, c); 467 | c.gridy = 2; 468 | outputPanel.add(matchTypeLabel, c); 469 | c.gridy = 3; 470 | outputPanel.add(matchRelationshipLabel, c); 471 | c.gridy = 4; 472 | outputPanel.add(matchHighlighterLabel, c); 473 | 474 | c.anchor = GridBagConstraints.EAST; 475 | c.fill = GridBagConstraints.HORIZONTAL; 476 | c.gridx = 1; 477 | c.gridy = 0; 478 | outputPanel.add(booleanOperatorComboBox, c); 479 | c.gridy = 1; 480 | outputPanel.add(originalOrModifiedComboBox, c); 481 | c.gridy = 2; 482 | outputPanel.add(matchTypeComboBox, c); 483 | c.gridy = 3; 484 | outputPanel.add(matchRelationshipComboBox, c); 485 | c.gridy = 4; 486 | outputPanel.add(matchHighlighterTextField, c); 487 | return outputPanel; 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/burp/Logs/LogEntry.java: -------------------------------------------------------------------------------- 1 | package burp.Logs; 2 | 3 | import burp.BurpExtender; 4 | import burp.Highlighter.Highlighter; 5 | import burp.IHttpRequestResponsePersisted; 6 | import burp.IRequestInfo; 7 | import burp.IResponseInfo; 8 | import java.awt.Color; 9 | import java.net.URL; 10 | import java.util.Arrays; 11 | 12 | public class LogEntry { 13 | 14 | private long requestResponseId; 15 | private IHttpRequestResponsePersisted originalRequestResponse; 16 | private IHttpRequestResponsePersisted modifiedRequestResponse; 17 | 18 | private URL originalURL; 19 | private URL modifiedURL; 20 | 21 | private String originalMethod; 22 | private String modifiedMethod; 23 | 24 | private int originalLength; 25 | private int modifiedLength; 26 | private int lengthDifference; 27 | private double responseDistance; 28 | 29 | private int originalResponseStatus; 30 | private int modifiedResponseStatus; 31 | 32 | private int originalRequestHashCode; 33 | private int modifiedRequestHashCode; 34 | 35 | private int toolFlag; 36 | 37 | private long requestSentTime; 38 | 39 | private Color backgroundColor; 40 | private Color selectedBackgroundColor; 41 | 42 | public long getRequestResponseId() { 43 | return requestResponseId; 44 | } 45 | 46 | public Color getSelectedBackgroundColor() { 47 | return this.selectedBackgroundColor; 48 | } 49 | 50 | public void setBackgroundColor(Color backgroundColor, Color selectedBackgroundColor) { 51 | this.backgroundColor = backgroundColor; 52 | this.selectedBackgroundColor = selectedBackgroundColor; 53 | } 54 | 55 | public Color getBackgroundColor() { return this.backgroundColor; } 56 | 57 | public Color getFontColor() { return this.backgroundColor; } 58 | 59 | public void setRequestResponseId(long requestResponseId) { 60 | this.requestResponseId = requestResponseId; 61 | } 62 | 63 | public IHttpRequestResponsePersisted getOriginalRequestResponse() { 64 | return originalRequestResponse; 65 | } 66 | 67 | public void setOriginalRequestResponse(IHttpRequestResponsePersisted originalRequestResponse) { 68 | this.originalRequestResponse = originalRequestResponse; 69 | } 70 | 71 | public IHttpRequestResponsePersisted getModifiedRequestResponse() { 72 | return modifiedRequestResponse; 73 | } 74 | 75 | public void setModifiedRequestResponse(IHttpRequestResponsePersisted modifiedRequestResponse) { 76 | this.modifiedRequestResponse = modifiedRequestResponse; 77 | } 78 | 79 | public URL getOriginalURL() { 80 | return originalURL; 81 | } 82 | 83 | public void setOriginalURL(URL originalURL) { 84 | this.originalURL = originalURL; 85 | } 86 | 87 | public URL getModifiedURL() { 88 | return modifiedURL; 89 | } 90 | 91 | public void setModifiedURL(URL modifiedURL) { 92 | this.modifiedURL = modifiedURL; 93 | } 94 | 95 | public int getOriginalRequestHashCode() { 96 | return originalRequestHashCode; 97 | } 98 | 99 | public int getModifiedRequestHashCode() { 100 | return modifiedRequestHashCode; 101 | } 102 | 103 | public long getRequestSentTime() { 104 | return requestSentTime; 105 | } 106 | 107 | public String getOriginalMethod() { 108 | return originalMethod; 109 | } 110 | 111 | public void setOriginalMethod(String originalMethod) { 112 | this.originalMethod = originalMethod; 113 | } 114 | 115 | public String getModifiedMethod() { 116 | return modifiedMethod; 117 | } 118 | 119 | public void setModifiedMethod(String modifiedMethod) { 120 | this.modifiedMethod = modifiedMethod; 121 | } 122 | 123 | public int getOriginalLength() { 124 | return originalLength; 125 | } 126 | 127 | public void setOriginalLength(int originalLength) { 128 | this.originalLength = originalLength; 129 | } 130 | 131 | public int getModifiedLength() { 132 | return modifiedLength; 133 | } 134 | 135 | public void setModifiedLength(int modifiedLength) { 136 | this.modifiedLength = modifiedLength; 137 | } 138 | 139 | public int getLengthDifference() { 140 | return lengthDifference; 141 | } 142 | 143 | public void setLengthDifference(int lengthDifference) { 144 | this.lengthDifference = lengthDifference; 145 | } 146 | 147 | public double getResponseDistance() { 148 | return responseDistance; 149 | } 150 | 151 | public void setResponseDistance(double responseDistance) { 152 | this.responseDistance = responseDistance; 153 | } 154 | 155 | public int getOriginalResponseStatus() { 156 | return originalResponseStatus; 157 | } 158 | 159 | public void setOriginalResponseStatus(int originalResponseStatus) { 160 | this.originalResponseStatus = originalResponseStatus; 161 | } 162 | 163 | public int getModifiedResponseStatus() { 164 | return modifiedResponseStatus; 165 | } 166 | 167 | public void setModifiedResponseStatus(int modifiedResponseStatus) { 168 | this.modifiedResponseStatus = modifiedResponseStatus; 169 | } 170 | 171 | public void setOriginalRequestHashCode(int originalRequestHashCode) { 172 | this.originalRequestHashCode = originalRequestHashCode; 173 | } 174 | 175 | public void setModifiedRequestHashCode(int modifiedRequestHashCode) { 176 | this.modifiedRequestHashCode = modifiedRequestHashCode; 177 | } 178 | 179 | public void setRequestSentTime(long requestSentTime) { 180 | this.requestSentTime = requestSentTime; 181 | } 182 | 183 | //#, Host, Method, URL, Status, Length 184 | // # 185 | // Host 186 | // Orig. Method 187 | // Mod. Method 188 | // Orig. URL 189 | // Mod. URL 190 | // Orig. Status 191 | // Mod. Status 192 | // Orig. Length 193 | // Mod. Length 194 | 195 | public LogEntry(long requestResponseId, 196 | int toolFlag, 197 | IHttpRequestResponsePersisted originalRequestResponse, 198 | IHttpRequestResponsePersisted modifiedRequestResponse) { 199 | 200 | IRequestInfo originalAnalyzedRequest = BurpExtender.getHelpers() 201 | .analyzeRequest(originalRequestResponse); 202 | 203 | IRequestInfo modifiedAnalyzedRequest = BurpExtender.getHelpers() 204 | .analyzeRequest(modifiedRequestResponse); 205 | 206 | IResponseInfo originalAnalyzedResponse = BurpExtender.getHelpers() 207 | .analyzeResponse(originalRequestResponse.getResponse()); 208 | this.originalResponseStatus = originalAnalyzedResponse.getStatusCode(); 209 | 210 | IResponseInfo modifiedAnalyzedResponse = BurpExtender.getHelpers() 211 | .analyzeResponse(modifiedRequestResponse.getResponse()); 212 | this.modifiedResponseStatus = modifiedAnalyzedResponse.getStatusCode(); 213 | 214 | // Request ID 215 | this.requestResponseId = requestResponseId; 216 | 217 | // Original Request Info 218 | this.originalRequestResponse = originalRequestResponse; 219 | this.originalURL = originalAnalyzedRequest.getUrl(); 220 | this.originalMethod = originalAnalyzedRequest.getMethod(); 221 | this.originalLength = originalRequestResponse.getResponse().length; 222 | 223 | // Modified Request Info 224 | this.modifiedRequestResponse = modifiedRequestResponse; 225 | this.modifiedURL = modifiedAnalyzedRequest.getUrl(); 226 | this.modifiedMethod = modifiedAnalyzedRequest.getMethod(); 227 | this.modifiedLength = modifiedRequestResponse.getResponse().length; 228 | 229 | // Comparisons 230 | this.lengthDifference = Math.abs(this.originalLength - this.modifiedLength); 231 | this.responseDistance = 0; 232 | 233 | this.originalRequestHashCode = Arrays.hashCode(originalRequestResponse.getRequest()); 234 | this.modifiedRequestHashCode = Arrays.hashCode(modifiedRequestResponse.getRequest()); 235 | 236 | this.toolFlag = toolFlag; 237 | 238 | this.requestSentTime = System.currentTimeMillis(); 239 | 240 | backgroundColor = Highlighter.COLORS[0]; 241 | selectedBackgroundColor = Highlighter.SELECTED_COLORS[0]; 242 | } 243 | 244 | public int getToolFlag() { 245 | return toolFlag; 246 | } 247 | 248 | public void setToolFlag(int toolFlag) { 249 | this.toolFlag = toolFlag; 250 | } 251 | } 252 | 253 | -------------------------------------------------------------------------------- /src/burp/Logs/LogEntryMenu.java: -------------------------------------------------------------------------------- 1 | package burp.Logs; 2 | 3 | import burp.AutoRepeater.LogTable; 4 | import javax.swing.*; 5 | import java.awt.event.ActionEvent; 6 | 7 | /** 8 | * Largely taken from Logger++ 9 | */ 10 | public class LogEntryMenu extends JPopupMenu { 11 | 12 | public LogEntryMenu(final LogManager logManager, final LogTable logTable, final int row, final int col) { 13 | final LogEntry entry = logManager.getLogTableModel().getLogEntry(row); 14 | final String columnName = logManager.getLogTableModel().getColumnName(col); 15 | final String columnValue = logManager.getLogTableModel().getValueAt(row, col).toString(); 16 | //this.add(new JPopupMenu.Separator()); 17 | 18 | JMenuItem useAsFilter = new JMenuItem( 19 | new AbstractAction("Clear Logs") { 20 | @Override 21 | public void actionPerformed(ActionEvent actionEvent) { 22 | logManager.getLogTableModel().clearLogs(); 23 | logTable.revalidate(); 24 | } 25 | }); 26 | this.add(useAsFilter); 27 | } 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/burp/Logs/LogManager.java: -------------------------------------------------------------------------------- 1 | package burp.Logs; 2 | 3 | import burp.BurpExtender; 4 | import burp.Filter.Filters; 5 | import burp.IHttpRequestResponse; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.ListIterator; 9 | import javax.swing.SwingUtilities; 10 | import javax.swing.table.TableRowSorter; 11 | 12 | public class LogManager { 13 | 14 | private LogTableModel logTableModel; 15 | private ArrayList entriesWithoutResponses; 16 | private TableRowSorter tableRowSorter; 17 | private int matchCounter = 0; 18 | 19 | public LogManager() { 20 | //tableRowSorter.setRowFilter(filter.getRowFilter()); 21 | entriesWithoutResponses = new ArrayList<>(); 22 | logTableModel = new LogTableModel(); 23 | tableRowSorter = new TableRowSorter<>(logTableModel); 24 | } 25 | 26 | public synchronized int getRowCount() { 27 | return logTableModel.getRowCount(); 28 | } 29 | 30 | public synchronized LogTableModel getLogTableModel() { 31 | return logTableModel; 32 | } 33 | 34 | public synchronized LogEntry getLogEntry(int row) { 35 | return logTableModel.getLogEntry(row); 36 | } 37 | 38 | public synchronized void addEntry(LogEntry logEntry, Filters filters) { 39 | SwingUtilities.invokeLater(() -> { 40 | logTableModel.addLogEntry(logEntry, filters); 41 | logTableModel.fireTableDataChanged(); 42 | }); 43 | //entriesWithoutResponses.add(logEntry); 44 | } 45 | 46 | public synchronized void setFilter(Filters filters) { 47 | logTableModel.filterLogs(filters); 48 | logTableModel.fireTableDataChanged(); 49 | } 50 | 51 | //Keeping this around incase i go to switch the trigger back to on request 52 | public synchronized void addEntryResponse(IHttpRequestResponse messageInfo) { 53 | int requestHashCode = Arrays.hashCode(messageInfo.getRequest()); 54 | ListIterator iter = entriesWithoutResponses.listIterator(); 55 | 56 | System.out.println("LogEntriesWithoutResponses Length"); 57 | System.out.println(entriesWithoutResponses.size()); 58 | 59 | for (LogEntry lg : entriesWithoutResponses) { 60 | System.out.print("Entries Hash is: "); 61 | System.out.println(lg.getOriginalRequestHashCode()); 62 | } 63 | System.out.print("Current hashcode: "); 64 | System.out.println(requestHashCode); 65 | 66 | System.out.print("matchCounter is: "); 67 | System.out.println(matchCounter); 68 | 69 | while (iter.hasNext()) { 70 | // If the current LogEntry's originalRequest matches the received request set them equal 71 | LogEntry currentLogEntry = iter.next(); 72 | if (currentLogEntry.getOriginalRequestHashCode() == requestHashCode) { 73 | currentLogEntry.setOriginalRequestResponse( 74 | BurpExtender.getCallbacks().saveBuffersToTempFiles(messageInfo)); 75 | iter.remove(); 76 | matchCounter++; 77 | break; 78 | } 79 | if (currentLogEntry.getRequestSentTime() + 60000 < System.currentTimeMillis()) { 80 | System.out.println("TIME OUT"); 81 | System.out.println(currentLogEntry.getRequestSentTime()); 82 | System.out.println(System.currentTimeMillis()); 83 | iter.remove(); 84 | } 85 | } 86 | System.out.println(); 87 | } 88 | 89 | public synchronized void fireTableRowsUpdated(int firstRow, int lastRow) { 90 | logTableModel.fireTableRowsInserted(firstRow, lastRow); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/burp/Logs/LogTableModel.java: -------------------------------------------------------------------------------- 1 | package burp.Logs; 2 | 3 | import burp.Filter.Filters; 4 | import javax.swing.table.AbstractTableModel; 5 | import java.util.ArrayList; 6 | 7 | public class LogTableModel extends AbstractTableModel { 8 | 9 | private final ArrayList log; 10 | private ArrayList filteredLogs; 11 | 12 | public LogTableModel() { 13 | log = new ArrayList<>(); 14 | filteredLogs = new ArrayList<>(); 15 | } 16 | 17 | public void addLogEntry(LogEntry logEntry, Filters filters) { 18 | log.add(logEntry); 19 | if(filters.filter(logEntry)) { 20 | filteredLogs.add(logEntry); 21 | } 22 | fireTableDataChanged(); 23 | } 24 | 25 | public void filterLogs(Filters filters) { 26 | filteredLogs = new ArrayList<>(); 27 | for (LogEntry logEntry : log) { 28 | if(filters.filter(logEntry)) { 29 | filteredLogs.add(logEntry); 30 | //System.out.println("Adding row "+logEntry.getRequestResponseId()); 31 | fireTableDataChanged(); 32 | } 33 | } 34 | fireTableDataChanged(); 35 | //new Thread(() -> { 36 | //}).start(); 37 | } 38 | 39 | public void clearLogs() { 40 | log.clear(); 41 | filteredLogs.clear(); 42 | fireTableDataChanged(); 43 | } 44 | 45 | public LogEntry getLogEntry(int row) { 46 | //return log.get(row); 47 | return filteredLogs.get(row); 48 | } 49 | 50 | public ArrayList getLog() { 51 | //return log; 52 | return log; 53 | } 54 | 55 | public ArrayList getFilteredLogs() { 56 | //return log; 57 | return filteredLogs; 58 | } 59 | 60 | public int getLogCount() { 61 | return log.size(); 62 | } 63 | 64 | @Override 65 | public int getRowCount() { 66 | //return log.size(); 67 | return filteredLogs.size(); 68 | } 69 | 70 | @Override 71 | public int getColumnCount() { 72 | return 8; 73 | } 74 | 75 | @Override 76 | public String getColumnName(int columnIndex) { 77 | switch (columnIndex) { 78 | case 0: 79 | return "#"; 80 | case 1: 81 | return "Method"; 82 | case 2: 83 | return "URL"; 84 | case 3: 85 | return "Orig. Status"; 86 | case 4: 87 | return "Status"; 88 | case 5: 89 | return "Orig. Resp. Len."; 90 | case 6: 91 | return "Resp. Len."; 92 | case 7: 93 | return "Resp. Len. Diff."; 94 | default: 95 | return ""; 96 | } 97 | } 98 | 99 | @Override 100 | public Class getColumnClass(int columnIndex) { 101 | switch (columnIndex) { 102 | case 0: 103 | return Integer.class; 104 | case 1: 105 | return String.class; 106 | case 2: 107 | return String.class; 108 | case 3: 109 | return Integer.class; 110 | case 4: 111 | return Integer.class; 112 | case 5: 113 | return Integer.class; 114 | case 6: 115 | return Integer.class; 116 | case 7: 117 | return Integer.class; 118 | default: 119 | return null; 120 | } 121 | } 122 | 123 | @Override 124 | public Object getValueAt(int rowIndex, int columnIndex) { 125 | //LogEntry logEntry = log.get(rowIndex); 126 | LogEntry logEntry = filteredLogs.get(rowIndex); 127 | // ID, Mod Method, Mod URL, Orig Status, Mod Status, Orig Size, Mod Size, Size Difference, Response Distance 128 | switch (columnIndex) { 129 | case 0: 130 | return logEntry.getRequestResponseId(); 131 | case 1: 132 | return logEntry.getModifiedMethod(); 133 | case 2: 134 | return logEntry.getModifiedURL().toString(); 135 | case 3: 136 | return logEntry.getOriginalResponseStatus(); 137 | case 4: 138 | return logEntry.getModifiedResponseStatus(); 139 | case 5: 140 | return logEntry.getOriginalLength(); 141 | case 6: 142 | return logEntry.getModifiedLength(); 143 | case 7: 144 | return logEntry.getLengthDifference(); 145 | default: 146 | return ""; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/burp/Replacements/Replacement.java: -------------------------------------------------------------------------------- 1 | package burp.Replacements; 2 | 3 | import burp.BurpExtender; 4 | import burp.IExtensionHelpers; 5 | import burp.IHttpRequestResponse; 6 | import burp.IParameter; 7 | import burp.IRequestInfo; 8 | import burp.Utils.Utils; 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.ListIterator; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | import java.util.stream.Collectors; 16 | 17 | public class Replacement { 18 | public static final String[] REPLACEMENT_TYPE_OPTIONS = { 19 | "Request String", 20 | 21 | "Request Header", 22 | "Request Body", 23 | "Request Param Name", 24 | "Request Param Value", 25 | "Request Cookie Name", 26 | "Request Cookie Value", 27 | "Request First Line", 28 | 29 | "Add Header", 30 | 31 | "Remove Parameter By Name", 32 | "Remove Parameter By Value", 33 | "Remove Cookie By Name", 34 | "Remove Cookie By Value", 35 | "Remove Header By Name", 36 | "Remove Header By Value", 37 | 38 | "Match Param Name, Replace Value", 39 | "Match Cookie Name, Replace Value", 40 | "Match Header Name, Replace Value" 41 | //"Remove Header By Name", 42 | //"Remove Header By Value" 43 | }; 44 | 45 | public static final String[] REPLACEMENT_COUNT_OPTINONS = { 46 | "Replace First", 47 | "Replace All" 48 | }; 49 | 50 | private enum MatchAndReplaceType { 51 | MATCH_NAME_REPLACE_NAME, 52 | MATCH_NAME_REPLACE_VALUE, 53 | MATCH_VALUE_REPLACE_VALUE, 54 | MATCH_VALUE_REPLACE_NAME, 55 | MATCH_NAME_REMOVE, 56 | MATCH_VALUE_REMOVE 57 | } 58 | 59 | private String type; 60 | private String match; 61 | private String replace; 62 | private String comment; 63 | private String which; 64 | 65 | private Boolean isRegexMatch; 66 | private Boolean isEnabled; 67 | 68 | public Replacement( 69 | String type, 70 | String match, 71 | String replace, 72 | String which, 73 | String comment, 74 | boolean isRegexMatch) { 75 | this.type = type; 76 | this.match = match; 77 | this.replace = replace; 78 | this.which = which; 79 | this.comment = comment; 80 | this.isRegexMatch = isRegexMatch; 81 | this.isEnabled = true; 82 | } 83 | 84 | public Replacement( 85 | String type, 86 | String match, 87 | String replace, 88 | String which, 89 | String comment, 90 | boolean isRegexMatch, 91 | boolean isEnabled) { 92 | this(type, match, replace, which, comment, isRegexMatch); 93 | this.setEnabled(isEnabled); 94 | } 95 | 96 | public Replacement(Replacement replacement) { 97 | this(replacement.getType(), 98 | replacement.getMatch(), 99 | replacement.getReplace(), 100 | replacement.getWhich(), 101 | replacement.getComment(), 102 | replacement.isRegexMatch(), 103 | replacement.isEnabled()); 104 | } 105 | 106 | private byte[] updateBurpParam( 107 | byte[] request, 108 | int parameterType, 109 | MatchAndReplaceType matchAndReplaceType) { 110 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 111 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 112 | // Need to only use params that can be added or removed. 113 | List parameters = analyzedRequest.getParameters().stream() 114 | .filter(p -> p.getType() == parameterType) 115 | .collect(Collectors.toList()); 116 | List originalParameters = analyzedRequest.getParameters().stream() 117 | .filter(p -> p.getType() == parameterType) 118 | .collect(Collectors.toList()); 119 | 120 | //for (IParameter param : originalParameters) { 121 | // BurpExtender.getCallbacks().printOutput(param.getName()); 122 | //} 123 | //BurpExtender.getCallbacks().printOutput("-----"); 124 | //for (IParameter param : parameters) { 125 | // BurpExtender.getCallbacks().printOutput(param.getName()); 126 | //} 127 | 128 | boolean wasChanged = false; 129 | for (ListIterator iterator = parameters.listIterator(); iterator.hasNext(); ) { 130 | int i = iterator.nextIndex(); 131 | IParameter currentParameter = iterator.next(); 132 | //BurpExtender.getCallbacks().printOutput(currentParameter.getName()); 133 | //BurpExtender.getCallbacks().printOutput(currentParameter.getValue()); 134 | if (currentParameter.getType() == parameterType) { 135 | switch (matchAndReplaceType) { 136 | case MATCH_NAME_REPLACE_NAME: 137 | // Each if statement checks if isRegexMatch && check regex 138 | // || regular string compare 139 | if ((this.isRegexMatch && currentParameter.getName().matches(this.match)) 140 | || currentParameter.getName().equals(this.match)) { 141 | parameters.set(i, helpers.buildParameter( 142 | this.replace, 143 | currentParameter.getValue(), 144 | currentParameter.getType())); 145 | wasChanged = true; 146 | } 147 | break; 148 | case MATCH_NAME_REPLACE_VALUE: 149 | if ((this.isRegexMatch && currentParameter.getName().matches(this.match)) 150 | || currentParameter.getName().equals(this.match)) { 151 | parameters.set(i, helpers.buildParameter( 152 | currentParameter.getName(), 153 | this.replace, 154 | currentParameter.getType())); 155 | wasChanged = true; 156 | } 157 | break; 158 | case MATCH_VALUE_REPLACE_VALUE: 159 | if ((this.isRegexMatch && currentParameter.getValue().matches(this.match)) 160 | || currentParameter.getValue().equals(this.match)) { 161 | parameters.set(i, helpers.buildParameter( 162 | currentParameter.getName(), 163 | this.replace, 164 | currentParameter.getType())); 165 | wasChanged = true; 166 | } 167 | break; 168 | case MATCH_VALUE_REPLACE_NAME: 169 | if ((this.isRegexMatch && currentParameter.getValue().matches(this.match)) 170 | || currentParameter.getValue().equals(this.match)) { 171 | parameters.set(i, helpers.buildParameter( 172 | currentParameter.getName(), 173 | this.replace, 174 | currentParameter.getType())); 175 | wasChanged = true; 176 | } 177 | break; 178 | case MATCH_NAME_REMOVE: 179 | if ((this.isRegexMatch && currentParameter.getName().matches(this.match)) 180 | || currentParameter.getName().equals(this.match)) { 181 | iterator.remove(); 182 | wasChanged = true; 183 | } 184 | break; 185 | case MATCH_VALUE_REMOVE: 186 | if ((this.isRegexMatch && currentParameter.getValue().matches(this.match)) 187 | || currentParameter.getValue().equals(this.match)) { 188 | iterator.remove(); 189 | wasChanged = true; 190 | } 191 | break; 192 | default: 193 | break; 194 | } 195 | } 196 | // Bail if anything was changed 197 | if (this.which.equals("Replace First")) { 198 | if (wasChanged) { 199 | break; 200 | } 201 | } 202 | } 203 | if (wasChanged) { 204 | byte[] tempRequest = Arrays.copyOf(request, request.length); 205 | // Remove every parameter 206 | for (IParameter param : originalParameters) { 207 | tempRequest = helpers.removeParameter(tempRequest, param); 208 | } 209 | // Add them back 210 | for (IParameter param : parameters) { 211 | tempRequest = helpers.addParameter(tempRequest, param); 212 | } 213 | // Update the body and headers 214 | IRequestInfo tempAnalyzedRequest = helpers.analyzeRequest(tempRequest); 215 | byte[] body = Arrays 216 | .copyOfRange(tempRequest, tempAnalyzedRequest.getBodyOffset(), tempRequest.length); 217 | List headers = tempAnalyzedRequest.getHeaders(); 218 | return helpers.buildHttpMessage(headers, body); 219 | } 220 | // Return the modified request 221 | return request; 222 | } 223 | 224 | // This is a hack around binary content causing requests to send 225 | private byte[] updateContent(byte[] request) { 226 | if (replaceFirst()) { 227 | return Utils.byteArrayRegexReplaceFirst(request, this.match, this.replace); 228 | } else { 229 | return Utils.byteArrayRegexReplaceAll(request, this.match, this.replace); 230 | } 231 | } 232 | 233 | private boolean replaceFirst() { 234 | return this.which.equals("Replace First"); 235 | } 236 | 237 | private byte[] updateHeader(byte[] request) { 238 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 239 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 240 | List headers = analyzedRequest.getHeaders(); 241 | byte[] body = Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length); 242 | ArrayList newHeaders = new ArrayList<>(); 243 | boolean wasChanged = false; 244 | for (String header : headers) { 245 | if (!replaceFirst() || (replaceFirst() && !wasChanged)) { 246 | if (this.isRegexMatch) { 247 | if (header.matches(this.match)) { 248 | header = this.replace; 249 | wasChanged = true; 250 | } 251 | } else { 252 | if (header.equals(this.match)) { 253 | header = this.replace; 254 | wasChanged = true; 255 | } 256 | } 257 | } 258 | // Don't add empty headers, they mess things up 259 | if (!header.equals("")) { 260 | newHeaders.add(header); 261 | } 262 | } 263 | return helpers.buildHttpMessage(newHeaders, body); 264 | } 265 | 266 | private byte[] addHeader(byte[] request) { 267 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 268 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 269 | List headers = analyzedRequest.getHeaders(); 270 | // Strip content-length to make sure it's the last param 271 | if (headers.get(headers.size()-1).startsWith("Content-Length:")) { 272 | headers.remove(headers.size()-1); 273 | } 274 | byte[] body = Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length); 275 | headers.add(this.replace); 276 | return helpers.buildHttpMessage(headers, body); 277 | } 278 | 279 | private byte[] matchHeaderNameUpdateValue(byte[] request) { 280 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 281 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 282 | List headers = analyzedRequest.getHeaders(); 283 | byte[] body = Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length); 284 | ArrayList newHeaders = new ArrayList<>(); 285 | boolean wasChanged = false; 286 | for (String header : headers) { 287 | String[] splitHeader = header.split(":", 2); 288 | if (splitHeader.length == 2) { 289 | String headerName = splitHeader[0]; 290 | if (!replaceFirst() || (replaceFirst() && !wasChanged)) { 291 | if (this.isRegexMatch) { 292 | if (headerName.matches(this.match)) { 293 | header = headerName+": "+this.replace; 294 | wasChanged = true; 295 | } 296 | } else { 297 | if (headerName.equals(this.match)) { 298 | header = headerName+": "+this.replace; 299 | wasChanged = true; 300 | } 301 | } 302 | } 303 | } 304 | // Don't add empty headers, they mess things up 305 | if (!header.equals("")) { 306 | newHeaders.add(header); 307 | } 308 | } 309 | return helpers.buildHttpMessage(newHeaders, body); 310 | } 311 | 312 | private byte[] updateRequestBody(byte[] request) { 313 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 314 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 315 | List headers = analyzedRequest.getHeaders(); 316 | byte[] body = Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length); 317 | boolean wasChanged = false; 318 | String bodyString; 319 | 320 | try { 321 | bodyString = new String(body, "UTF-8"); 322 | } catch (UnsupportedEncodingException e) { 323 | return request; 324 | } 325 | 326 | if (this.isRegexMatch) { 327 | if (bodyString.matches(this.match)) { 328 | body = this.replace.getBytes(); 329 | wasChanged = true; 330 | } 331 | } else { 332 | if (bodyString.equals(this.match)) { 333 | body = bodyString.replace(this.match, this.replace).getBytes(); 334 | wasChanged = true; 335 | } 336 | } 337 | // This helps deal with binary data getting messed up from the string conversion and causing a new request. 338 | if (wasChanged) { 339 | return helpers.buildHttpMessage(headers, body); 340 | } else { 341 | return request; 342 | } 343 | } 344 | 345 | private byte[] updateRequestParamName(byte[] request) { 346 | if (!Utils.isRequestMultipartForm(request)) { 347 | request = updateBurpParam(request, IParameter.PARAM_BODY, 348 | MatchAndReplaceType.MATCH_NAME_REPLACE_NAME); 349 | return updateBurpParam(request, IParameter.PARAM_URL, 350 | MatchAndReplaceType.MATCH_NAME_REPLACE_NAME); 351 | } else { 352 | return request; 353 | } 354 | } 355 | 356 | private byte[] updateRequestParamValue(byte[] request) { 357 | if (!Utils.isRequestMultipartForm(request)) { 358 | request = updateBurpParam(request, IParameter.PARAM_BODY, 359 | MatchAndReplaceType.MATCH_VALUE_REPLACE_VALUE); 360 | return updateBurpParam(request, IParameter.PARAM_URL, 361 | MatchAndReplaceType.MATCH_VALUE_REPLACE_VALUE); 362 | } else { 363 | return request; 364 | } 365 | } 366 | 367 | private byte[] updateRequestParamValueByName(byte[] request) { 368 | if (!Utils.isRequestMultipartForm(request)) { 369 | request = updateBurpParam(request, IParameter.PARAM_BODY, 370 | MatchAndReplaceType.MATCH_NAME_REPLACE_VALUE); 371 | return updateBurpParam(request, IParameter.PARAM_URL, 372 | MatchAndReplaceType.MATCH_NAME_REPLACE_VALUE); 373 | } else { 374 | return request; 375 | } 376 | } 377 | 378 | private byte[] updateCookieName(byte[] request) { 379 | return updateBurpParam(request, IParameter.PARAM_COOKIE, 380 | MatchAndReplaceType.MATCH_NAME_REPLACE_NAME); 381 | } 382 | 383 | private byte[] updateCookieValue(byte[] request) { 384 | return updateBurpParam(request, IParameter.PARAM_COOKIE, 385 | MatchAndReplaceType.MATCH_VALUE_REPLACE_VALUE); 386 | } 387 | 388 | private byte[] removeParameterByName(byte[] request) { 389 | if (!Utils.isRequestMultipartForm(request)) { 390 | request = updateBurpParam(request, IParameter.PARAM_BODY, 391 | MatchAndReplaceType.MATCH_NAME_REMOVE); 392 | return updateBurpParam(request, IParameter.PARAM_URL, 393 | MatchAndReplaceType.MATCH_NAME_REMOVE); 394 | } else { 395 | return request; 396 | } 397 | } 398 | 399 | private byte[] removeParameterByValue(byte[] request) { 400 | if (Utils.isRequestMultipartForm(request)) { 401 | request = updateBurpParam(request, IParameter.PARAM_BODY, 402 | MatchAndReplaceType.MATCH_VALUE_REMOVE); 403 | return updateBurpParam(request, IParameter.PARAM_URL, 404 | MatchAndReplaceType.MATCH_VALUE_REMOVE); 405 | } else { 406 | return request; 407 | } 408 | } 409 | 410 | private byte[] removeCookieByName(byte[] request) { 411 | return updateBurpParam(request, IParameter.PARAM_COOKIE, 412 | MatchAndReplaceType.MATCH_NAME_REMOVE); 413 | } 414 | 415 | private byte[] removeCookieByValue(byte[] request) { 416 | return updateBurpParam(request, IParameter.PARAM_COOKIE, 417 | MatchAndReplaceType.MATCH_VALUE_REMOVE); 418 | } 419 | 420 | private byte[] removeHeaderByName(byte[] request) { 421 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 422 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 423 | byte[] body = Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length); 424 | List headers; 425 | if(replaceFirst()) { 426 | AtomicInteger index = new AtomicInteger(0); 427 | if(isRegexMatch()) { 428 | headers = analyzedRequest.getHeaders().stream() 429 | .filter((x -> !(x.split(":")[0].matches(getMatch()) && index.getAndIncrement() < 1))) 430 | .collect(Collectors.toCollection(ArrayList::new)); 431 | } else { 432 | headers = analyzedRequest.getHeaders().stream() 433 | .filter(x -> !(x.split(":")[0].equals(getMatch()) && index.getAndIncrement() < 1)) 434 | .collect(Collectors.toCollection(ArrayList::new)); 435 | } 436 | } else { 437 | if(isRegexMatch()) { 438 | headers = analyzedRequest.getHeaders().stream() 439 | .filter(x -> !(x.split(":")[0].matches(getMatch()))) 440 | .collect(Collectors.toCollection(ArrayList::new)); 441 | } else { 442 | headers = analyzedRequest.getHeaders().stream() 443 | .filter(x -> !(x.split(":")[0].equals(getMatch()))) 444 | .collect(Collectors.toCollection(ArrayList::new)); 445 | } 446 | } 447 | return helpers.buildHttpMessage(headers, body); 448 | } 449 | 450 | private byte[] removeHeaderByValue(byte[] request) { 451 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 452 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 453 | byte[] body = Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length); 454 | List headers; 455 | if(replaceFirst()) { 456 | AtomicInteger index = new AtomicInteger(0); 457 | if(isRegexMatch()) { 458 | headers = analyzedRequest.getHeaders().stream() 459 | .filter(x -> x.split(":")[1].matches(getMatch()) && index.getAndIncrement() < 1) 460 | .collect(Collectors.toCollection(ArrayList::new)); 461 | } else { 462 | headers = analyzedRequest.getHeaders().stream() 463 | .filter(x -> x.split(":")[1].equals(getMatch()) && index.getAndIncrement() < 1) 464 | .collect(Collectors.toCollection(ArrayList::new)); 465 | } 466 | } else { 467 | if(isRegexMatch()) { 468 | headers = analyzedRequest.getHeaders().stream() 469 | .filter(x -> x.split(":")[1].matches(getMatch())) 470 | .collect(Collectors.toCollection(ArrayList::new)); 471 | } else { 472 | headers = analyzedRequest.getHeaders().stream() 473 | .filter(x -> x.split(":")[1].equals(getMatch())) 474 | .collect(Collectors.toCollection(ArrayList::new)); 475 | } 476 | } 477 | return helpers.buildHttpMessage(headers, body); 478 | } 479 | 480 | private byte[] updateCookieValueByName(byte[] request) { 481 | return updateBurpParam(request, IParameter.PARAM_COOKIE, 482 | MatchAndReplaceType.MATCH_NAME_REPLACE_VALUE); 483 | } 484 | 485 | private byte[] updateRequestFirstLine(byte[] request) { 486 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 487 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 488 | List headers = analyzedRequest.getHeaders(); 489 | byte[] body = Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length); 490 | String firstRequestString = headers.get(0); 491 | if (replaceFirst()) { 492 | headers.set(0, firstRequestString.replaceFirst(this.match, this.replace)); 493 | } else { 494 | headers.set(0, firstRequestString.replaceAll(this.match, this.replace)); 495 | } 496 | return helpers.buildHttpMessage(headers, body); 497 | } 498 | 499 | // TODO: Modify this to return List to support "Replace Each" 500 | public byte[] performReplacement(IHttpRequestResponse messageInfo) { 501 | byte[] request = messageInfo.getRequest(); 502 | if (this.isEnabled) { 503 | switch (this.type) { 504 | case ("Request Header"): 505 | return updateHeader(request); 506 | case ("Request Body"): 507 | return updateRequestBody(request); 508 | case ("Request Param Name"): 509 | return updateRequestParamName(request); 510 | case ("Request Param Value"): 511 | return updateRequestParamValue(request); 512 | case ("Request Cookie Name"): 513 | return updateCookieName(request); 514 | case ("Request Cookie Value"): 515 | return updateCookieValue(request); 516 | case ("Request First Line"): 517 | return updateRequestFirstLine(request); 518 | case ("Request String"): 519 | return updateContent(request); 520 | case ("Add Header"): 521 | return addHeader(request); 522 | case ("Remove Parameter By Name"): 523 | return removeParameterByName(request); 524 | case ("Remove Parameter By Value"): 525 | return removeParameterByValue(request); 526 | case ("Remove Cookie By Name"): 527 | return removeCookieByName(request); 528 | case ("Remove Cookie By Value"): 529 | return removeCookieByValue(request); 530 | case ("Remove Header By Name"): 531 | return removeHeaderByName(request); 532 | case ("Remove Header By Value"): 533 | return removeHeaderByValue(request); 534 | case ("Match Param Name, Replace Value"): 535 | return updateRequestParamValueByName(request); 536 | case ("Match Cookie Name, Replace Value"): 537 | return updateCookieValueByName(request); 538 | case ("Match Header Name, Replace Value"): 539 | return matchHeaderNameUpdateValue(request); 540 | default: 541 | return request; 542 | } 543 | } 544 | return request; 545 | } 546 | 547 | public String getType() { 548 | return type; 549 | } 550 | 551 | public String getMatch() { 552 | return match; 553 | } 554 | 555 | public String getReplace() { 556 | return replace; 557 | } 558 | 559 | public String getComment() { 560 | return comment; 561 | } 562 | 563 | public boolean isRegexMatch() { 564 | return isRegexMatch; 565 | } 566 | 567 | public boolean isEnabled() { 568 | return isEnabled; 569 | } 570 | 571 | public void setType(String type) { 572 | this.type = type; 573 | } 574 | 575 | public void setMatch(String match) { 576 | this.match = match; 577 | } 578 | 579 | public void setReplace(String replace) { 580 | this.replace = replace; 581 | } 582 | 583 | public void setComment(String comment) { 584 | this.comment = comment; 585 | } 586 | 587 | public void setRegexMatch(Boolean regexMatch) { 588 | isRegexMatch = regexMatch; 589 | } 590 | 591 | public void setEnabled(Boolean enabled) { 592 | isEnabled = enabled; 593 | } 594 | 595 | public String getWhich() { 596 | return which; 597 | } 598 | 599 | public void setWhich(String which) { 600 | this.which = which; 601 | } 602 | 603 | public Boolean getRegexMatch() { 604 | return isRegexMatch; 605 | } 606 | 607 | public Boolean getEnabled() { 608 | return isEnabled; 609 | } 610 | 611 | 612 | } 613 | -------------------------------------------------------------------------------- /src/burp/Replacements/ReplacementTableModel.java: -------------------------------------------------------------------------------- 1 | package burp.Replacements; 2 | 3 | import burp.Replacements.Replacement; 4 | import java.util.stream.Collectors; 5 | import javax.swing.table.AbstractTableModel; 6 | import java.util.ArrayList; 7 | 8 | public class ReplacementTableModel extends AbstractTableModel { 9 | 10 | private String[] columnNames = { 11 | "Enabled", 12 | "Type", 13 | "Match", 14 | "Replace", 15 | "Which", 16 | "Comment", 17 | "Regex Match", 18 | }; 19 | 20 | private ArrayList replacements; 21 | 22 | public ReplacementTableModel() { 23 | replacements = new ArrayList<>(); 24 | } 25 | 26 | public void addReplacement(Replacement newReplacement) { 27 | replacements.add(newReplacement); 28 | } 29 | 30 | public void updateReplacement(int replacementIndex, Replacement newReplacement) { 31 | replacements.set(replacementIndex, newReplacement); 32 | } 33 | 34 | public Replacement getReplacement(int replacementIndex) { 35 | return replacements.get(replacementIndex); 36 | } 37 | 38 | public ArrayList getReplacements() { 39 | return replacements.stream() 40 | .filter(Replacement::isEnabled) 41 | .collect(Collectors.toCollection(ArrayList::new)); 42 | } 43 | 44 | public void deleteReplacement(int replacementIndex) { 45 | replacements.remove(replacementIndex); 46 | } 47 | 48 | @Override 49 | public int getColumnCount() { 50 | return columnNames.length; 51 | } 52 | 53 | @Override 54 | public int getRowCount() { 55 | return replacements.size(); 56 | } 57 | 58 | @Override 59 | public String getColumnName(int col) { 60 | return columnNames[col]; 61 | } 62 | 63 | @Override 64 | public Object getValueAt(int row, int col) { 65 | Replacement tempReplacement = replacements.get(row); 66 | switch (col) { 67 | case 0: 68 | return tempReplacement.isEnabled(); 69 | case 1: 70 | return tempReplacement.getType(); 71 | case 2: 72 | return tempReplacement.getMatch(); 73 | case 3: 74 | return tempReplacement.getReplace(); 75 | case 4: 76 | return tempReplacement.getWhich(); 77 | case 5: 78 | return tempReplacement.getComment(); 79 | default: 80 | return tempReplacement.isRegexMatch(); 81 | } 82 | } 83 | 84 | @Override 85 | public Class getColumnClass(int column) { 86 | return (getValueAt(0, column).getClass()); 87 | } 88 | 89 | @Override 90 | public boolean isCellEditable(int row, int column) { 91 | return (getColumnName(column).equals("Enabled")); 92 | } 93 | 94 | @Override 95 | public void setValueAt(Object value, int row, int col) { 96 | Replacement tempReplacement = replacements.get(row); 97 | switch (col) { 98 | case 0: 99 | tempReplacement.setEnabled((Boolean) value); 100 | break; 101 | case 1: 102 | tempReplacement.setType((String) value); 103 | break; 104 | case 2: 105 | tempReplacement.setMatch((String) value); 106 | break; 107 | case 3: 108 | tempReplacement.setReplace((String) value); 109 | break; 110 | case 4: 111 | tempReplacement.setWhich((String) value); 112 | break; 113 | case 5: 114 | tempReplacement.setComment((String) value); 115 | break; 116 | default: 117 | tempReplacement.setRegexMatch((Boolean) value); 118 | break; 119 | } 120 | replacements.set(row, tempReplacement); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/burp/Replacements/Replacements.java: -------------------------------------------------------------------------------- 1 | package burp.Replacements; 2 | 3 | import burp.AutoRepeater; 4 | import burp.BurpExtender; 5 | import java.awt.GridBagConstraints; 6 | import java.awt.GridBagLayout; 7 | import javax.swing.JButton; 8 | import javax.swing.JCheckBox; 9 | import javax.swing.JComboBox; 10 | import javax.swing.JLabel; 11 | import javax.swing.JOptionPane; 12 | import javax.swing.JPanel; 13 | import javax.swing.JScrollPane; 14 | import javax.swing.JTable; 15 | import javax.swing.JTextField; 16 | 17 | public class Replacements { 18 | 19 | // Replacements UI 20 | private JPanel replacementsPanel; 21 | private JScrollPane replacementScrollPane; 22 | private JTable replacementTable; 23 | private JButton addReplacementButton; 24 | private JPanel replacementsButtonPanel; 25 | private JButton editReplacementButton; 26 | private JButton deleteReplacementButton; 27 | private JButton duplicateReplacementButton; 28 | 29 | // Replacements popup UI 30 | private JPanel replacementPanel; 31 | private JComboBox replacementTypeComboBox; 32 | private JTextField replacementMatchTextField; 33 | private JTextField replacementReplaceTextField; 34 | private JTextField replacementCommentTextField; 35 | private JCheckBox replacementIsRegexCheckBox; 36 | private JComboBox replacementCountComboBox; 37 | private JLabel replacementMatchLabel; 38 | private JLabel replacementReplaceLabel; 39 | private JLabel replacementCommentLabel; 40 | private JLabel replacementTypeLabel; 41 | private JLabel replacementIsRegexLabel; 42 | private JLabel replacementCountLabel; 43 | 44 | // Replacements Data Store 45 | private ReplacementTableModel replacementTableModel; 46 | 47 | public Replacements() { 48 | replacementTableModel = new ReplacementTableModel(); 49 | createUI(); 50 | } 51 | 52 | public ReplacementTableModel getReplacementTableModel() { return replacementTableModel; } 53 | public JPanel getUI() { return replacementsPanel; } 54 | 55 | private void createUI() { 56 | GridBagConstraints c; 57 | replacementPanel = new JPanel(); 58 | replacementPanel.setLayout(new GridBagLayout()); 59 | replacementPanel.setPreferredSize(AutoRepeater.dialogDimension); 60 | 61 | c = new GridBagConstraints(); 62 | 63 | replacementTypeComboBox = new JComboBox<>(Replacement.REPLACEMENT_TYPE_OPTIONS); 64 | replacementCountComboBox = new JComboBox<>(Replacement.REPLACEMENT_COUNT_OPTINONS); 65 | replacementMatchTextField = new JTextField(); 66 | replacementReplaceTextField = new JTextField(); 67 | replacementCommentTextField = new JTextField(); 68 | replacementIsRegexCheckBox = new JCheckBox(); 69 | 70 | replacementTypeComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 71 | replacementCountComboBox.setPreferredSize(AutoRepeater.comboBoxDimension); 72 | replacementMatchTextField.setPreferredSize(AutoRepeater.textFieldDimension); 73 | replacementReplaceTextField.setPreferredSize(AutoRepeater.textFieldDimension); 74 | replacementCommentTextField.setPreferredSize(AutoRepeater.textFieldDimension); 75 | 76 | replacementTypeLabel = new JLabel("Type: "); 77 | replacementMatchLabel = new JLabel("Match: "); 78 | replacementCountLabel = new JLabel("Which: "); 79 | replacementReplaceLabel = new JLabel("Replace: "); 80 | replacementCommentLabel = new JLabel("Comment: "); 81 | replacementIsRegexLabel = new JLabel("Regex Match: "); 82 | 83 | c.anchor = GridBagConstraints.WEST; 84 | c.gridx = 0; 85 | c.gridy = 0; 86 | replacementPanel.add(replacementTypeLabel, c); 87 | c.gridy = 1; 88 | replacementPanel.add(replacementMatchLabel, c); 89 | c.gridy = 2; 90 | replacementPanel.add(replacementReplaceLabel, c); 91 | c.gridy = 3; 92 | replacementPanel.add(replacementCountLabel, c); 93 | c.gridy = 4; 94 | replacementPanel.add(replacementCommentLabel, c); 95 | c.gridy = 5; 96 | replacementPanel.add(replacementIsRegexLabel, c); 97 | 98 | c.anchor = GridBagConstraints.EAST; 99 | c.fill = GridBagConstraints.HORIZONTAL; 100 | c.gridx = 1; 101 | c.gridy = 0; 102 | replacementPanel.add(replacementTypeComboBox, c); 103 | c.gridy = 1; 104 | replacementPanel.add(replacementMatchTextField, c); 105 | c.gridy = 2; 106 | replacementPanel.add(replacementReplaceTextField, c); 107 | c.gridy = 3; 108 | replacementPanel.add(replacementCountComboBox, c); 109 | c.gridy = 4; 110 | replacementPanel.add(replacementCommentTextField, c); 111 | c.gridy = 5; 112 | replacementPanel.add(replacementIsRegexCheckBox, c); 113 | 114 | // Replacement Buttons 115 | addReplacementButton = new JButton("Add"); 116 | addReplacementButton.setPreferredSize(AutoRepeater.buttonDimension); 117 | addReplacementButton.setMinimumSize(AutoRepeater.buttonDimension); 118 | addReplacementButton.setMaximumSize(AutoRepeater.buttonDimension); 119 | 120 | // Add New Replacement 121 | addReplacementButton.addActionListener(e -> { 122 | int result = JOptionPane.showConfirmDialog( 123 | BurpExtender.getParentTabbedPane(), 124 | replacementPanel, 125 | "Add Replacement", 126 | JOptionPane.OK_CANCEL_OPTION, 127 | JOptionPane.PLAIN_MESSAGE); 128 | if (result == JOptionPane.OK_OPTION) { 129 | Replacement newReplacement = new Replacement( 130 | (String) replacementTypeComboBox.getSelectedItem(), 131 | replacementMatchTextField.getText(), 132 | replacementReplaceTextField.getText(), 133 | (String) replacementCountComboBox.getSelectedItem(), 134 | replacementCommentTextField.getText(), 135 | replacementIsRegexCheckBox.isSelected() 136 | ); 137 | replacementTableModel.addReplacement(newReplacement); 138 | replacementTableModel.fireTableDataChanged(); 139 | } 140 | resetReplacementDialog(); 141 | }); 142 | 143 | editReplacementButton = new JButton("Edit"); 144 | editReplacementButton.setPreferredSize(AutoRepeater.buttonDimension); 145 | editReplacementButton.setMinimumSize(AutoRepeater.buttonDimension); 146 | editReplacementButton.setMaximumSize(AutoRepeater.buttonDimension); 147 | 148 | // Edit selected Replacement 149 | editReplacementButton.addActionListener(e -> { 150 | int selectedRow = replacementTable.getSelectedRow(); 151 | if (selectedRow != -1) { 152 | Replacement tempReplacement = replacementTableModel.getReplacement(selectedRow); 153 | 154 | replacementTypeComboBox.setSelectedItem(tempReplacement.getType()); 155 | replacementMatchTextField.setText(tempReplacement.getMatch()); 156 | replacementReplaceTextField.setText(tempReplacement.getReplace()); 157 | replacementCountComboBox.setSelectedItem(tempReplacement.getWhich()); 158 | replacementCommentTextField.setText(tempReplacement.getComment()); 159 | replacementIsRegexCheckBox.setSelected(tempReplacement.isRegexMatch()); 160 | 161 | int result = JOptionPane.showConfirmDialog( 162 | BurpExtender.getParentTabbedPane(), 163 | replacementPanel, 164 | "Edit Replacement", 165 | JOptionPane.OK_CANCEL_OPTION, 166 | JOptionPane.PLAIN_MESSAGE); 167 | if (result == JOptionPane.OK_OPTION) { 168 | Replacement newReplacement = new Replacement( 169 | (String) replacementTypeComboBox.getSelectedItem(), 170 | replacementMatchTextField.getText(), 171 | replacementReplaceTextField.getText(), 172 | (String) replacementCountComboBox.getSelectedItem(), 173 | replacementCommentTextField.getText(), 174 | replacementIsRegexCheckBox.isSelected() 175 | ); 176 | replacementTableModel.updateReplacement(selectedRow, newReplacement); 177 | replacementTableModel.fireTableDataChanged(); 178 | } 179 | resetReplacementDialog(); 180 | } 181 | }); 182 | 183 | deleteReplacementButton = new JButton("Remove"); 184 | deleteReplacementButton.setPreferredSize(AutoRepeater.buttonDimension); 185 | deleteReplacementButton.setMinimumSize(AutoRepeater.buttonDimension); 186 | deleteReplacementButton.setMaximumSize(AutoRepeater.buttonDimension); 187 | 188 | //Delete Replacement 189 | deleteReplacementButton.addActionListener(e -> { 190 | int selectedRow = replacementTable.getSelectedRow(); 191 | if (selectedRow != -1) { 192 | replacementTableModel.deleteReplacement(selectedRow); 193 | replacementTableModel.fireTableDataChanged(); 194 | } 195 | }); 196 | 197 | duplicateReplacementButton = new JButton("Duplicate"); 198 | duplicateReplacementButton.setPreferredSize(AutoRepeater.buttonDimension); 199 | duplicateReplacementButton.setMinimumSize(AutoRepeater.buttonDimension); 200 | duplicateReplacementButton.setMaximumSize(AutoRepeater.buttonDimension); 201 | 202 | // Duplicate a replacement 203 | duplicateReplacementButton.addActionListener(e -> { 204 | int selectedRow = replacementTable.getSelectedRow(); 205 | if (selectedRow != -1 && selectedRow < replacementTableModel.getReplacements().size()) { 206 | replacementTableModel.addReplacement( 207 | new Replacement(replacementTableModel.getReplacement(selectedRow))); 208 | replacementTableModel.fireTableDataChanged(); 209 | } 210 | }); 211 | 212 | replacementsButtonPanel = new JPanel(); 213 | replacementsButtonPanel.setLayout(new GridBagLayout()); 214 | replacementsButtonPanel.setPreferredSize(AutoRepeater.buttonPanelDimension); 215 | 216 | c = new GridBagConstraints(); 217 | c.anchor = GridBagConstraints.FIRST_LINE_END; 218 | c.gridx = 0; 219 | c.weightx = 1; 220 | 221 | replacementsButtonPanel.add(addReplacementButton, c); 222 | replacementsButtonPanel.add(editReplacementButton, c); 223 | replacementsButtonPanel.add(deleteReplacementButton, c); 224 | replacementsButtonPanel.add(duplicateReplacementButton, c); 225 | 226 | replacementTableModel = new ReplacementTableModel(); 227 | replacementTable = new JTable(replacementTableModel); 228 | replacementTable.getColumnModel().getColumn(0).setMaxWidth(55); 229 | replacementTable.getColumnModel().getColumn(0).setMinWidth(55); 230 | replacementScrollPane = new JScrollPane(replacementTable); 231 | replacementScrollPane.setMinimumSize(AutoRepeater.tableDimension); 232 | 233 | // Panel containing replacement options 234 | replacementsPanel = new JPanel(); 235 | replacementsPanel.setLayout(new GridBagLayout()); 236 | 237 | c = new GridBagConstraints(); 238 | c.anchor = GridBagConstraints.PAGE_START; 239 | c.gridx = 0; 240 | replacementsPanel.add(replacementsButtonPanel, c); 241 | 242 | c.fill = GridBagConstraints.BOTH; 243 | c.weightx = 1; 244 | c.weighty = 1; 245 | c.gridx = 1; 246 | replacementsPanel.add(replacementScrollPane, c); 247 | } 248 | 249 | private void resetReplacementDialog() { 250 | replacementTypeComboBox.setSelectedIndex(0); 251 | replacementCountComboBox.setSelectedIndex(0); 252 | replacementMatchTextField.setText(""); 253 | replacementReplaceTextField.setText(""); 254 | replacementCommentTextField.setText(""); 255 | replacementIsRegexCheckBox.setSelected(false); 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/burp/Utils/AutoRepeaterMenu.java: -------------------------------------------------------------------------------- 1 | package burp.Utils; 2 | 3 | import burp.AutoRepeater; 4 | import burp.BurpExtender; 5 | import burp.IExtensionStateListener; 6 | import burp.Logs.LogEntry; 7 | import burp.Logs.LogManager; 8 | 9 | import com.google.gson.JsonArray; 10 | import com.google.gson.JsonElement; 11 | import javax.swing.*; 12 | import java.awt.event.ActionEvent; 13 | import java.awt.event.ActionListener; 14 | import java.io.File; 15 | import java.io.FileNotFoundException; 16 | import java.io.PrintWriter; 17 | import java.util.ArrayList; 18 | 19 | public class AutoRepeaterMenu implements Runnable, IExtensionStateListener { 20 | private final JRootPane rootPane; 21 | private static ArrayList autoRepeaters; 22 | 23 | private static JMenu autoRepeaterJMenu; 24 | private static JMenuItem toggleSettingsVisibility; 25 | 26 | private static boolean showSettingsPanel; 27 | 28 | public static boolean sendRequestsToPassiveScanner; 29 | public static boolean addRequestsToSiteMap; 30 | 31 | public AutoRepeaterMenu(JRootPane rootPane) { 32 | this.rootPane = rootPane; 33 | autoRepeaters = BurpExtender.getAutoRepeaters(); 34 | showSettingsPanel = true; 35 | BurpExtender.getCallbacks().registerExtensionStateListener(this); 36 | } 37 | 38 | /** 39 | * Action listener for setting visibility 40 | */ 41 | class SettingVisibilityListener implements ActionListener { 42 | @Override 43 | public void actionPerformed(ActionEvent e) { 44 | // Toggling settings panel 45 | if (toggleSettingsVisibility.getText().equals("Hide Settings Panel")) { 46 | showSettingsPanel = false; 47 | toggleSettingsVisibility.setText("Show Settings Panel"); 48 | } else { 49 | showSettingsPanel = true; 50 | toggleSettingsVisibility.setText("Hide Settings Panel"); 51 | } 52 | // toggling every AutoRepeater tab 53 | for (AutoRepeater ar : autoRepeaters) { 54 | ar.toggleConfigurationPane(showSettingsPanel); 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Action listener for import settings menu 61 | */ 62 | class ImportSettingListener implements ActionListener { 63 | 64 | @Override 65 | public void actionPerformed(ActionEvent e) { 66 | final JFileChooser importPathChooser = new JFileChooser(); 67 | int replaceTabs = JOptionPane.showConfirmDialog(rootPane, "Would you like to replace the current tabs?", "Replace Tabs", JOptionPane.YES_NO_CANCEL_OPTION); 68 | if (replaceTabs == 2) { 69 | // cancel selected 70 | return; 71 | } 72 | int returnVal = importPathChooser.showOpenDialog(rootPane); 73 | if (returnVal != JFileChooser.APPROVE_OPTION) { 74 | BurpExtender.getCallbacks().printOutput("Cannot open a file dialog for importing settings."); 75 | return; 76 | } 77 | File file = importPathChooser.getSelectedFile(); 78 | String fileData = Utils.readFile(file); 79 | if (fileData.equals("")) { 80 | // file empty 81 | return; 82 | } 83 | if (replaceTabs == 1) { 84 | // do not replace current tabs 85 | BurpExtender.initializeFromSave(fileData, false); 86 | } else if (replaceTabs == 0) { 87 | // replace current tabs 88 | BurpExtender.getCallbacks().printOutput("Removing Tabs"); 89 | BurpExtender.initializeFromSave(fileData, true); 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * Action listener for export settings menu. 96 | */ 97 | class ExportSettingListener implements ActionListener { 98 | @Override 99 | public void actionPerformed(ActionEvent e) { 100 | Object[] options = {"Current Tab", "Every Tab", "Cancel"}; 101 | int option = JOptionPane.showOptionDialog(rootPane, "Which tab would you like to export?", "Export Tabs", 102 | JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); 103 | if (option == 2) { return; } 104 | final JFileChooser exportPathChooser = new JFileChooser(); 105 | int returnVal = exportPathChooser.showSaveDialog(rootPane); 106 | if (returnVal != JFileChooser.APPROVE_OPTION) { 107 | BurpExtender.getCallbacks().printOutput("Cannot open a file dialog for exporting settings."); 108 | return; 109 | } 110 | File file = exportPathChooser.getSelectedFile(); 111 | try (PrintWriter out = new PrintWriter(file.getAbsolutePath())) { 112 | if (option == 0) { 113 | // export current tab 114 | out.println(BurpExtender.exportSave(BurpExtender.getSelectedAutoRepeater())); 115 | } else if (option == 1) { 116 | // export every tab 117 | out.println(BurpExtender.exportSave()); 118 | } 119 | } catch (FileNotFoundException error) { 120 | error.printStackTrace(); 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * Action listener for export logs menu. 127 | */ 128 | class ExportLogsListener implements ActionListener { 129 | 130 | @Override 131 | public void actionPerformed(ActionEvent e) { 132 | Object[] options = {"Export", "Cancel"}; 133 | final String[] EXPORT_OPTIONS = {"CSV", "JSON"}; 134 | final String[] EXPORT_WHICH_OPTIONS = {"All Tab Logs", "Selected Tab Logs"}; 135 | final String[] EXPORT_VALUE_OPTIONS = {"Log Entry", "Log Entry + Full HTTP Request"}; 136 | 137 | final JComboBox exportTypeComboBox = new JComboBox<>(EXPORT_OPTIONS); 138 | final JComboBox exportWhichComboBox = new JComboBox<>(EXPORT_WHICH_OPTIONS); 139 | final JComboBox exportValueComboBox = new JComboBox<>(EXPORT_VALUE_OPTIONS); 140 | 141 | final JFileChooser exportLogsPathChooser = new JFileChooser(); 142 | 143 | JPanel exportLogsPanel = new JPanel(); 144 | exportLogsPanel.setLayout(new BoxLayout(exportLogsPanel, BoxLayout.PAGE_AXIS)); 145 | exportLogsPanel.add(exportWhichComboBox); 146 | exportLogsPanel.add(exportValueComboBox); 147 | exportLogsPanel.add(exportTypeComboBox); 148 | JPanel buttonPanel = new JPanel(); 149 | exportLogsPanel.add(buttonPanel); 150 | 151 | int option = JOptionPane.showOptionDialog(rootPane, exportLogsPanel, 152 | "Export Logs", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); 153 | if (option == 1) { 154 | return; 155 | } 156 | int returnVal = exportLogsPathChooser.showSaveDialog(rootPane); 157 | if (returnVal != JFileChooser.APPROVE_OPTION) { 158 | BurpExtender.getCallbacks().printOutput("Cannot open a file dialog for exporting logs."); 159 | return; 160 | } 161 | AutoRepeater autoRepeater = BurpExtender.getSelectedAutoRepeater(); 162 | LogManager logManager = autoRepeater.getLogManager(); 163 | File file = exportLogsPathChooser.getSelectedFile(); 164 | ArrayList logEntries = new ArrayList<>(); 165 | // collect relevant entries 166 | if ((exportWhichComboBox.getSelectedItem()).equals("All Tab Logs")) { 167 | logEntries = autoRepeater.getLogTableModel().getLog(); 168 | } else if ((exportWhichComboBox.getSelectedItem()).equals("Selected Tab Logs")) { 169 | int[] selectedRows = autoRepeater.getLogTable().getSelectedRows(); 170 | for (int row : selectedRows) { 171 | logEntries.add(logManager.getLogEntry(autoRepeater.getLogTable().convertRowIndexToModel(row))); 172 | } 173 | } 174 | // determine if whole request should be exported or just the log contents 175 | boolean exportFullHttp = !((exportValueComboBox.getSelectedItem()).equals("Log Entry")); 176 | 177 | try (PrintWriter out = new PrintWriter(file.getAbsolutePath())) { 178 | if ((exportTypeComboBox.getSelectedItem()).equals("CSV")) { 179 | out.println(Utils.exportLogEntriesToCsv(logEntries, exportFullHttp)); 180 | } else if ((exportTypeComboBox.getSelectedItem()).equals("JSON")) { 181 | out.println(Utils.exportLogEntriesToJson(logEntries, exportFullHttp)); 182 | } 183 | } catch (FileNotFoundException error) { 184 | error.printStackTrace(); 185 | } 186 | } 187 | } 188 | 189 | class DuplicateCurrentTabListener implements ActionListener { 190 | 191 | @Override 192 | public void actionPerformed(ActionEvent e) { 193 | JsonArray serializedTab = BurpExtender.exportSaveAsJson(BurpExtender.getSelectedAutoRepeater()); 194 | for (JsonElement tabConfiguration : serializedTab) { 195 | BurpExtender.addNewTab(tabConfiguration.getAsJsonObject()); 196 | } 197 | } 198 | } 199 | 200 | @Override 201 | public void extensionUnloaded() { 202 | // unregister menu 203 | JMenuBar burpMenuBar = rootPane.getJMenuBar(); 204 | BurpExtender.getCallbacks().printOutput("Unregistering menu"); 205 | burpMenuBar.remove(autoRepeaterJMenu); 206 | burpMenuBar.repaint(); 207 | } 208 | 209 | @Override 210 | public void run() { 211 | JMenuBar burpJMenuBar = rootPane.getJMenuBar(); 212 | autoRepeaterJMenu = new JMenu("AutoRepeater"); 213 | // initialize menu items and add action listeners 214 | JMenuItem duplicateCurrentTab = new JMenuItem("Duplicate Selected Tab"); 215 | duplicateCurrentTab.addActionListener(new DuplicateCurrentTabListener()); 216 | 217 | toggleSettingsVisibility = new JMenuItem("Hide Settings Panel"); 218 | toggleSettingsVisibility.addActionListener(new SettingVisibilityListener()); 219 | JCheckBoxMenuItem toggleSendRequestsToPassiveScanner = new JCheckBoxMenuItem("Send Requests To Passive Scanner"); 220 | toggleSendRequestsToPassiveScanner.addActionListener(l -> 221 | sendRequestsToPassiveScanner = toggleSendRequestsToPassiveScanner.getState()); 222 | 223 | JCheckBoxMenuItem toggleAddRequestsToSiteMap = new JCheckBoxMenuItem("Add Requests To Site Map"); 224 | toggleAddRequestsToSiteMap.addActionListener(l -> 225 | addRequestsToSiteMap = toggleAddRequestsToSiteMap.getState()); 226 | 227 | JMenuItem showImportMenu = new JMenuItem("Import Settings"); 228 | showImportMenu.addActionListener(new ImportSettingListener()); 229 | 230 | JMenuItem showExportMenu = new JMenuItem("Export Settings"); 231 | showExportMenu.addActionListener(new ExportSettingListener()); 232 | 233 | JMenuItem showExportLogsMenu = new JMenuItem("Export Logs"); 234 | showExportLogsMenu.addActionListener(new ExportLogsListener()); 235 | // add menu items to the menu 236 | autoRepeaterJMenu.add(duplicateCurrentTab); 237 | autoRepeaterJMenu.add(toggleSettingsVisibility); 238 | autoRepeaterJMenu.addSeparator(); 239 | autoRepeaterJMenu.add(toggleAddRequestsToSiteMap); 240 | autoRepeaterJMenu.add(toggleSendRequestsToPassiveScanner); 241 | autoRepeaterJMenu.addSeparator(); 242 | autoRepeaterJMenu.add(showImportMenu); 243 | autoRepeaterJMenu.add(showExportMenu); 244 | autoRepeaterJMenu.add(showExportLogsMenu); 245 | // add menu to menu bar 246 | burpJMenuBar.add(autoRepeaterJMenu, burpJMenuBar.getMenuCount() - 2); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/burp/Utils/DiffViewerPane.java: -------------------------------------------------------------------------------- 1 | package burp.Utils; 2 | 3 | import javax.swing.*; 4 | 5 | public class DiffViewerPane extends JEditorPane { 6 | 7 | public DiffViewerPane(byte[] original, byte[] modified) { 8 | this.setEditable(false); 9 | this.setContentType("text/html"); 10 | } 11 | 12 | public DiffViewerPane() { 13 | this.setEditable(false); 14 | this.setContentType("text/html"); 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/burp/Utils/HttpComparer.java: -------------------------------------------------------------------------------- 1 | package burp.Utils; 2 | 3 | import burp.Utils.diff_match_patch; 4 | import java.util.LinkedList; 5 | 6 | public class HttpComparer { 7 | 8 | public static String diffText(String original, String modified) { 9 | if (original.length() > 10000 || modified.length() > 10000) { 10 | return "Input too large, cannot generate diff."; 11 | } else { 12 | diff_match_patch differ = new diff_match_patch(); 13 | LinkedList diff; 14 | diff = differ.diff_main(original, modified, true); 15 | differ.diff_cleanupSemantic(diff); 16 | return diffToHtml(diff); 17 | } 18 | } 19 | 20 | public static String diffLines(String original, String modified) { 21 | if (original.length() > 20000 || modified.length() > 20000) { 22 | return "Input too large, cannot generate diff."; 23 | } else { 24 | diff_match_patch differ = new diff_match_patch(); 25 | diff_match_patch.LinesToCharsResult linesTochars = differ 26 | .diff_linesToChars(original, modified); 27 | LinkedList diff = differ 28 | .diff_main(linesTochars.chars1, linesTochars.chars2, false); 29 | differ.diff_charsToLines(diff, linesTochars.lineArray); 30 | //differ.diff_cleanupSemantic(diff); 31 | return diffToHtml(diff); 32 | } 33 | } 34 | 35 | private static String diffToHtml(LinkedList diff) { 36 | StringBuilder html = new StringBuilder(); 37 | for (diff_match_patch.Diff aDiff : diff) { 38 | String text = aDiff.text 39 | .replace("&", "&") 40 | .replace("<", "<") 41 | .replace(">", ">") 42 | .replace("\n", "
"); 43 | switch (aDiff.operation) { 44 | case INSERT: 45 | html.append("").append(text).append(""); 46 | break; 47 | case DELETE: 48 | html.append("").append(text).append(""); 49 | break; 50 | case EQUAL: 51 | html.append("").append(text).append(""); 52 | break; 53 | } 54 | } 55 | return html.toString(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/burp/Utils/MessageEditorController.java: -------------------------------------------------------------------------------- 1 | package burp.Utils; 2 | 3 | import burp.IHttpRequestResponse; 4 | import burp.IHttpService; 5 | import burp.IMessageEditorController; 6 | 7 | public class MessageEditorController implements IMessageEditorController { 8 | 9 | private IHttpRequestResponse displayedItem; 10 | 11 | public IHttpRequestResponse getDisplayedItem() { 12 | return displayedItem; 13 | } 14 | 15 | public void setDisplayedItem(IHttpRequestResponse displayedItem) { 16 | this.displayedItem = displayedItem; 17 | } 18 | 19 | @Override 20 | public byte[] getRequest() { 21 | return displayedItem.getRequest(); 22 | } 23 | 24 | @Override 25 | public byte[] getResponse() { 26 | return displayedItem.getResponse(); 27 | } 28 | 29 | @Override 30 | public IHttpService getHttpService() { 31 | return displayedItem.getHttpService(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/burp/Utils/ResponseStore.java: -------------------------------------------------------------------------------- 1 | package burp.Utils; 2 | 3 | import burp.IHttpListener; 4 | import burp.IHttpRequestResponse; 5 | import java.util.HashMap; 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | 9 | // This will allow AutoRepeater to replace values in Requests based on previous responses from domains 10 | // This will be its own HTTPListener to keep track of all requests, not just the one's AutoRepeater sends 11 | // The Data store should be something along the lines of a Map of some storage object which holds the timestamp + RequestResponse 12 | // and the key being the domain. It might make sense to use the full path instead of just the URL. 13 | // TODO: Move this idea into a new plugin. 14 | public class ResponseStore implements IHttpListener { 15 | 16 | // Container class for the response body + time it was received 17 | public class Response { 18 | byte[] responseBody; 19 | long time; 20 | 21 | public Response(byte[] responseBody) { 22 | time = System.currentTimeMillis(); 23 | this.responseBody = responseBody; 24 | } 25 | } 26 | 27 | // Hashmap to store responses and their URL 28 | private HashMap responseHashMap; 29 | 30 | public ResponseStore() { 31 | responseHashMap = new HashMap<>(); 32 | } 33 | 34 | public byte[] getMostRecentResponseBodyByRegex (String urlRegex) { 35 | // Get all the keys that match the regex 36 | Set matchingUrls = responseHashMap.keySet() 37 | .stream() 38 | .filter(key -> key.matches(urlRegex)) 39 | .collect(Collectors.toSet()); 40 | // Get an element from the hashmap to start from, it doesn't matter which element 41 | Response mostRecentResponse = responseHashMap.get(matchingUrls.iterator().next()); 42 | // Iterate over the matching url responses to find the most recent one 43 | for (String matchingUrl : matchingUrls) { 44 | Response tempResponse = responseHashMap.get(matchingUrl); 45 | if (mostRecentResponse.time < tempResponse.time) { 46 | mostRecentResponse = tempResponse; 47 | } 48 | } 49 | return mostRecentResponse.responseBody; 50 | } 51 | 52 | public byte[] getMostRecentResponseBody (String url) { 53 | return responseHashMap.get(url).responseBody; 54 | } 55 | 56 | @Override 57 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, 58 | IHttpRequestResponse messageInfo) { 59 | if (!messageIsRequest) { 60 | // Add the response body to the hashmap 61 | responseHashMap.put(messageInfo.getHttpService().getHost(), new Response(messageInfo.getResponse())); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/burp/Utils/Utils.java: -------------------------------------------------------------------------------- 1 | package burp.Utils; 2 | 3 | import burp.BurpExtender; 4 | import burp.IExtensionHelpers; 5 | import burp.IHttpRequestResponse; 6 | import burp.IHttpService; 7 | import burp.IParameter; 8 | import burp.IRequestInfo; 9 | import burp.Logs.LogEntry; 10 | import com.google.gson.JsonArray; 11 | import com.google.gson.JsonObject; 12 | import java.io.BufferedReader; 13 | import java.io.File; 14 | import java.io.FileReader; 15 | import java.io.IOException; 16 | import java.io.UnsupportedEncodingException; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.regex.Matcher; 20 | import java.util.regex.Pattern; 21 | import javax.swing.*; 22 | import java.awt.*; 23 | import java.util.List; 24 | 25 | public class Utils { 26 | 27 | public static String exportLogEntriesToJson (ArrayList logEntries, boolean exportHttp) { 28 | JsonArray json = new JsonArray(); 29 | if(exportHttp) { 30 | for (LogEntry log : logEntries) { 31 | JsonObject logJson = new JsonObject(); 32 | logJson.addProperty("#",log.getRequestResponseId()); 33 | logJson.addProperty("Method",log.getModifiedMethod()); 34 | logJson.addProperty("URL",log.getModifiedURL().toString()); 35 | logJson.addProperty("Orig. Status",log.getOriginalResponseStatus()); 36 | logJson.addProperty("Status",log.getModifiedResponseStatus()); 37 | logJson.addProperty("Orig. Resp. Len.",log.getOriginalLength()); 38 | logJson.addProperty("Resp. Len.",log.getModifiedLength()); 39 | logJson.addProperty("Resp. Len. Diff.",log.getLengthDifference()); 40 | logJson.addProperty("Orig. Request",new String(log.getOriginalRequestResponse().getRequest())); 41 | logJson.addProperty("Orig. Response",new String(log.getOriginalRequestResponse().getResponse())); 42 | logJson.addProperty("Request",new String(log.getModifiedRequestResponse().getRequest())); 43 | logJson.addProperty("Response",new String(log.getModifiedRequestResponse().getResponse())); 44 | json.add(logJson); 45 | 46 | } 47 | } else { 48 | for (LogEntry log : logEntries) { 49 | JsonObject logJson = new JsonObject(); 50 | logJson.addProperty("#",log.getRequestResponseId()); 51 | logJson.addProperty("Method",log.getModifiedMethod()); 52 | logJson.addProperty("URL",log.getModifiedURL().toString()); 53 | logJson.addProperty("Orig. Status",log.getOriginalResponseStatus()); 54 | logJson.addProperty("Status",log.getModifiedResponseStatus()); 55 | logJson.addProperty("Orig. Resp. Len.",log.getOriginalLength()); 56 | logJson.addProperty("Resp. Len.",log.getModifiedLength()); 57 | logJson.addProperty("Resp. Len. Diff.",log.getLengthDifference()); 58 | json.add(logJson); 59 | } 60 | } 61 | return json.toString(); 62 | } 63 | 64 | public static String readFile(File file) { 65 | BufferedReader br; 66 | StringBuilder output = new StringBuilder(); 67 | try { 68 | br = new BufferedReader(new FileReader(file)); 69 | String st; 70 | while ((st = br.readLine()) != null) { 71 | output.append(st); 72 | } 73 | } catch (IOException e) { 74 | return ""; 75 | } 76 | return output.toString(); 77 | } 78 | 79 | public static String exportLogEntriesToCsv (ArrayList logEntries, boolean exportHttp) { 80 | StringBuilder csv = new StringBuilder(); 81 | if(exportHttp) { 82 | String csvHeader = 83 | "#"+","+ 84 | "Method"+","+ 85 | "URL"+","+ 86 | "Orig. Status"+","+ 87 | "Status"+","+ 88 | "Orig. Resp. Len."+","+ 89 | "Resp. Len."+","+ 90 | "Resp. Len. Diff."+","+ 91 | "Orig. Request"+","+ 92 | "Orig. Response"+","+ 93 | "Request"+","+ 94 | "Response"+"\n"; 95 | csv.append(csvHeader); 96 | for (LogEntry log : logEntries) { 97 | csv.append(log.getRequestResponseId()); 98 | csv.append(","); 99 | csv.append(Utils.sanitizeForCsv(log.getModifiedMethod())); 100 | csv.append(","); 101 | csv.append(Utils.sanitizeForCsv(log.getModifiedURL().toString())); 102 | csv.append(","); 103 | csv.append(log.getOriginalResponseStatus()); 104 | csv.append(","); 105 | csv.append(log.getModifiedResponseStatus()); 106 | csv.append(","); 107 | csv.append(log.getOriginalLength()); 108 | csv.append(","); 109 | csv.append(log.getModifiedLength()); 110 | csv.append(","); 111 | csv.append(log.getLengthDifference()); 112 | csv.append(","); 113 | csv.append(Utils.sanitizeForCsv(new String(log.getOriginalRequestResponse().getRequest()))); 114 | csv.append(","); 115 | csv.append(Utils.sanitizeForCsv(new String(log.getOriginalRequestResponse().getResponse()))); 116 | csv.append(","); 117 | csv.append(Utils.sanitizeForCsv(new String(log.getModifiedRequestResponse().getRequest()))); 118 | csv.append(","); 119 | csv.append(Utils.sanitizeForCsv(new String(log.getModifiedRequestResponse().getResponse()))); 120 | csv.append("\n"); 121 | } 122 | } else { 123 | String csvHeader = "#"+","+ 124 | "Method"+","+ 125 | "URL"+","+ 126 | "Orig. Status"+","+ 127 | "Status"+","+ 128 | "Orig. Resp. Len."+","+ 129 | "Resp. Len."+","+ 130 | "Resp. Len. Diff."+"\n"; 131 | csv.append(csvHeader); 132 | for (LogEntry log : logEntries) { 133 | csv.append(log.getRequestResponseId()); 134 | csv.append(","); 135 | csv.append(Utils.sanitizeForCsv(log.getModifiedMethod())); 136 | csv.append(","); 137 | csv.append(Utils.sanitizeForCsv(log.getModifiedURL().toString())); 138 | csv.append(","); 139 | csv.append(log.getOriginalResponseStatus()); 140 | csv.append(","); 141 | csv.append(log.getModifiedResponseStatus()); 142 | csv.append(","); 143 | csv.append(log.getOriginalLength()); 144 | csv.append(","); 145 | csv.append(log.getModifiedLength()); 146 | csv.append(","); 147 | csv.append(log.getLengthDifference()); 148 | csv.append("\n"); 149 | } 150 | } 151 | return csv.toString(); 152 | } 153 | 154 | private static String sanitizeForCsv(String input) { 155 | return input.replaceAll("\n", "\\n").replaceAll(",", "\\,"); 156 | } 157 | 158 | public static IHttpRequestResponse cloneIHttpRequestResponse( 159 | IHttpRequestResponse originalRequestResponse) { 160 | return new IHttpRequestResponse() { 161 | byte[] request = originalRequestResponse.getRequest(); 162 | byte[] response = originalRequestResponse.getResponse(); 163 | String comment = originalRequestResponse.getComment(); 164 | String highlight = originalRequestResponse.getHighlight(); 165 | IHttpService httpService = originalRequestResponse.getHttpService(); 166 | 167 | @Override 168 | public byte[] getRequest() { 169 | return request; 170 | } 171 | 172 | @Override 173 | public void setRequest(byte[] message) { 174 | this.request = message; 175 | } 176 | 177 | @Override 178 | public byte[] getResponse() { 179 | return response; 180 | } 181 | 182 | @Override 183 | public void setResponse(byte[] message) { 184 | this.response = message; 185 | } 186 | 187 | @Override 188 | public String getComment() { 189 | return comment; 190 | } 191 | 192 | @Override 193 | public void setComment(String comment) { 194 | this.comment = comment; 195 | } 196 | 197 | @Override 198 | public String getHighlight() { 199 | return highlight; 200 | } 201 | 202 | @Override 203 | public void setHighlight(String color) { 204 | this.highlight = color; 205 | } 206 | 207 | @Override 208 | public IHttpService getHttpService() { 209 | return httpService; 210 | } 211 | 212 | @Override 213 | public void setHttpService(IHttpService httpService) { 214 | this.httpService = httpService; 215 | } 216 | }; 217 | } 218 | 219 | public static String getStringAfterSubstring (String input, String substring) { 220 | return(input.substring(input.lastIndexOf(substring) + substring.length())); 221 | } 222 | 223 | public static byte[] byteArrayRegexReplaceFirst(byte[] input, String regex, String replacement) { 224 | try { 225 | // I need to specify ASCII here because it's the easiest way for me to ensure the byte[] and 226 | // resulting string are the same length. 227 | String inputString = new String(input, "US-ASCII"); 228 | Pattern pattern = Pattern.compile(regex); 229 | Matcher matcher = pattern.matcher(inputString); 230 | // I'll be appending a lot of it's just easier to use a list here 231 | ArrayList output = new ArrayList<>(); 232 | // the index of the start of the last match 233 | int currentIndex = 0; 234 | // Check all occurrences 235 | if (matcher.find()) { 236 | int start = matcher.start(); 237 | // Add every item between start of the last match and the current match 238 | for (int i = currentIndex; i < start; i++) { 239 | output.add(input[i]); 240 | } 241 | // Add every character in the replacement 242 | for (int i = 0; i < replacement.length(); i++) { 243 | output.add((byte)replacement.charAt(i)); 244 | } 245 | // Skip over the body of the match 246 | currentIndex = matcher.end(); 247 | } else { 248 | // 249 | return input; 250 | } 251 | // Add everything after the last match 252 | for (int i = currentIndex; i < input.length; i++) { 253 | output.add(input[i]); 254 | } 255 | return byteArrayListToByteArray(output); 256 | } catch (UnsupportedEncodingException e) { 257 | return input; 258 | } 259 | 260 | } 261 | 262 | public static byte[] byteArrayRegexReplaceAll(byte[] input, String regex, String replacement) { 263 | try { 264 | // I need to specify ASCII here because it's the easiest way for me to ensure the byte[] and 265 | // resulting string are the same length. 266 | String inputString = new String(input, "US-ASCII"); 267 | //String inputString = new String(input, "UTF-16"); 268 | Pattern pattern = Pattern.compile(regex); 269 | Matcher matcher = pattern.matcher(inputString); 270 | // I'll be appending a lot of it's just easier to use a list here 271 | ArrayList output = new ArrayList<>(); 272 | //BurpExtender.getCallbacks().printOutput("Input Length is: "); 273 | //BurpExtender.getCallbacks().printOutput(Integer.toString(input.length)); 274 | //BurpExtender.getCallbacks().printOutput("Input String Length is: "); 275 | //BurpExtender.getCallbacks().printOutput(Integer.toString(inputString.length())); 276 | // the index of the start of the last match 277 | int currentIndex = 0; 278 | // Check all occurrences 279 | while (matcher.find()) { 280 | int start = matcher.start(); 281 | // Add every item between start of the last match and the current match 282 | for (int i = currentIndex; i < start; i++) { 283 | output.add(input[i]); 284 | } 285 | // Add every character in the replacement 286 | for (int i = 0; i < replacement.length(); i++) { 287 | output.add((byte)replacement.charAt(i)); 288 | } 289 | // Skip over the body of the match 290 | currentIndex = matcher.end(); 291 | } 292 | // Add everything after the last match 293 | for (int i = currentIndex; i < input.length; i++) { 294 | output.add(input[i]); 295 | } 296 | return byteArrayListToByteArray(output); 297 | } catch (UnsupportedEncodingException e) { 298 | return input; 299 | } 300 | } 301 | 302 | public static byte[] byteArrayListToByteArray(ArrayList input) { 303 | byte[] output = new byte[input.size()]; 304 | for (int i = 0; i < input.size(); i++) { 305 | output[i] = input.get(i); 306 | } 307 | return output; 308 | } 309 | 310 | //This method isn't efficient, i should refactor 311 | public static String getMultipartBoundary(byte[] request) { 312 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 313 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 314 | List headers = analyzedRequest.getHeaders(); 315 | return headers.stream() 316 | .filter((h) -> h.startsWith("Content-Type: multipart/form-data;")) 317 | .findFirst() 318 | .map((h) -> getStringAfterSubstring(h, "Content-Type: multipart/form-data;")) 319 | .map((h) -> getStringAfterSubstring(h, "boundary=")) 320 | .map((h) -> "--"+h) 321 | .orElse(null); 322 | } 323 | 324 | public static Color getBurpOrange() { 325 | return new Color(0xff6633); 326 | } 327 | 328 | // This method is a mess. 329 | public static ArrayList getMultipartParameters(byte[] request) { 330 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 331 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 332 | ArrayList parameters = new ArrayList<>(); 333 | String boundary = getMultipartBoundary(request); 334 | String requestBodyString = new String(Arrays.copyOfRange(request, analyzedRequest.getBodyOffset(), request.length)); 335 | int index = requestBodyString.indexOf(boundary); 336 | while (index >= 0) { 337 | //BurpExtender.getCallbacks().printOutput(Integer.toString(index)); 338 | int nextNewLineIndex = requestBodyString.indexOf('\n', index); 339 | index = requestBodyString.indexOf(boundary, index+1); 340 | } 341 | return parameters; 342 | } 343 | 344 | // It seems that IParameter types are incorrectly stated by the Burp Suite API 345 | // so I need to check 346 | public static boolean isRequestMultipartForm(byte[] request) { 347 | IExtensionHelpers helpers = BurpExtender.getHelpers(); 348 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request); 349 | List headers = analyzedRequest.getHeaders(); 350 | return headers.stream().anyMatch((h) -> h.startsWith("Content-Type: multipart/form-data;")); 351 | } 352 | 353 | public static void highlightParentTab(JTabbedPane parentTabbedPane, Component childComponent) { 354 | if (parentTabbedPane != null) { 355 | for (int i = 0; i < parentTabbedPane.getTabCount(); i++) { 356 | if (parentTabbedPane.getComponentAt(i).equals(childComponent)) { 357 | parentTabbedPane.setBackgroundAt(i, getBurpOrange()); 358 | Timer timer = new Timer(3000, e -> { 359 | for (int j = 0; j < parentTabbedPane.getTabCount(); j++) { 360 | if (parentTabbedPane.getComponentAt(j).equals(childComponent)) { 361 | parentTabbedPane.setBackgroundAt(j, Color.BLACK); 362 | break; 363 | } 364 | } 365 | }); 366 | timer.setRepeats(false); 367 | timer.start(); 368 | break; 369 | } 370 | } 371 | } 372 | } 373 | } 374 | --------------------------------------------------------------------------------