├── .gitattributes
├── .gitignore
├── README.md
├── build.gradle
├── gradlew
├── gradlew.bat
├── releases
└── Response Pattern Matcher-v2.0.1.jar
├── settings.gradle
└── src
├── main
├── java
│ ├── burp
│ │ └── BurpExtender.java
│ └── rpm
│ │ ├── MessageProcessor.java
│ │ ├── Payload.java
│ │ ├── ResponsePatternMatcher.java
│ │ ├── ResultEntry.java
│ │ ├── controller
│ │ └── ContentController.java
│ │ ├── model
│ │ ├── PayloadsTableModel.java
│ │ └── ResultsTableModel.java
│ │ └── ui
│ │ ├── CellRenderer.java
│ │ ├── ExtenderPopupMenu.java
│ │ ├── GUI.java
│ │ ├── JHyperlink.java
│ │ ├── JSONParser.java
│ │ ├── PayloadTable.java
│ │ └── ResultTable.java
└── resources
│ └── GitHub-Mark-32px.png
└── test
├── java
├── Start.java
└── burp
│ ├── JSONParserTests.java
│ ├── PayloadsTableModelTests.java
│ └── ResultsTableModelTests.java
└── resources
└── output.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # These are explicitly windows files and should use crlf
5 | *.bat text eol=crlf
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Gradle project-specific cache directory and other unnecessary Gradle dirs
2 | .gradle/
3 | gradle/
4 |
5 | #Ignore IDE stuff
6 | .idea/
7 | *.iml
8 |
9 | # Ignore Gradle build output directory
10 | build/
11 |
12 | # Ignore complied classes
13 | out/
14 |
15 | # for OS specific
16 | .DS_Store
17 | .DS_Store?
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Response Pattern Matcher
2 | Adds extensibility to Burp by using a list of payloads to pattern match on HTTP responses highlighting interesting and potentially vulnerable areas. Can be used to search HTML source code for interesting keywords, developer comments, passwords, admin panel links, hidden form fields, and more.
3 |
4 | # Usage
5 | The .jar file is available in the /releases directory, this can be loaded in through Burp Extender.
6 |
7 | - When the extension is loaded in you will see a Response Pattern Matcher tab, by default pre-existing payloads exist that will be pattern matched against every response that goes through Burp. This includes tools such as the Scanner.
8 | - Configure these payloads accordingly, these are quite generic so for an assessment you may want to add project specific keywords and regular expressions.
9 | - The is regex check box indicates whether to search the responses for the provided Pattern using Java's Pattern Matcher functionality. A good example is available below.
10 | - The active check box indicates whether the payload will be used. Uncheck this to disable the payload.
11 | - Use the "In Scope Only" checkbox to search only within responses that are in Scope defined under Target > Scope.
12 | - Use the config tab to choose whether to match on Requests and or Responses. Matches against either of these are available under the Matches tab.
13 | - For best results, define your scope, configure your payloads, and then start testing. Burp's Scanner will kick in and push everything through the Response Pattern Matcher too so the tool searches the full sitemap.
14 | - If you want to test the matches against a request or response again you can send the item to repeater from the HTTP history in Burp.
15 | - Note /* cannot be set to be regex, this will most likely crash burp as it matches on everything.
16 |
17 |
18 | [Java regex tutorial](http://vogella.com/tutorials/JavaRegularExpressions/article.html)
19 |
20 | # Matches
21 | As of v2.0 additional functionality has been added to organise matches.
22 | If you highlight items in the Matches tab you can highlight identical matches, as well as delete and export them to a .json file.
23 |
24 | # Requirements
25 | Built using:
26 | - Oracle OpenJDK 17.0.8
27 | - burp-extender-api (2.3)
28 | - Tested on Burp Community Edition 2023.7.1
29 |
30 | # Acknowledgements
31 | Author: Jack Jarvis, Bridewell
32 | Developed using IntelliJ IDE and the Gradle Build Tool.
33 |
34 | CoreyD97 Burp Extender Utilities:
35 | https://github.com/CoreyD97/BurpExtenderUtilities
36 |
37 | For further Burp Extension development please refer to:
38 | https://portswigger.net/burp/documentation/desktop/extensions
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | *
4 | * This is a general purpose Gradle build.
5 | * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
6 | */
7 | apply plugin: 'java'
8 | apply plugin: 'maven-publish'
9 | apply plugin: 'pmd'
10 |
11 | group 'com.jackj07.rpm'
12 |
13 | //Add version to jar name
14 | version getVersion()
15 |
16 | sourceCompatibility = 17.0
17 | targetCompatibility = 17.0
18 |
19 | //Keep updated
20 | def getVersion(){
21 | return "v2.0.1"
22 | }
23 |
24 | allprojects {
25 | repositories {
26 | mavenCentral()
27 | maven {url "https://jitpack.io"}
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation group: 'net.portswigger.burp.extender', name: 'burp-extender-api', version: '2.3'
33 | implementation 'com.github.CoreyD97:BurpExtenderUtilities:c03c1a2a1f'
34 | implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.0'
35 | testCompileOnly files("${System.properties['user.home']}\\AppData\\Local\\Programs\\BurpSuiteCommunity\\burpsuite_community.jar")
36 | testCompileOnly group: 'org.mockito', name: 'mockito-core', version: '2.23.0'
37 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
38 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
39 | }
40 |
41 | jar{
42 | archiveBaseName = project.name
43 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE
44 | from {
45 | (configurations.compileClasspath).collect { it.isDirectory() ? it : zipTree(it) }
46 | }{
47 | exclude '/test/*'
48 | exclude "META-INF/*.SF"
49 | exclude "META-INF/*.DSA"
50 | exclude "META-INF/*.RSA"
51 | }
52 | }
53 |
54 | task testJar(type: Jar) {
55 | archiveBaseName = project.name + "-TEST"
56 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE
57 | dependsOn testClasses
58 | dependsOn pmdTest
59 | dependsOn pmdMain
60 | sourceSets.main.output.each {
61 | from it
62 | }
63 | sourceSets.test.output.each {
64 | from it
65 | }
66 |
67 | sourceSets.test.runtimeClasspath.each { File f ->
68 | if (f.name.endsWith('.jar')) {
69 | from zipTree(f)
70 | } else {
71 | from f
72 | }
73 | }
74 |
75 | }
76 |
77 | //Designate jar output directory
78 | tasks.withType(Jar) {
79 | destinationDirectory = file("$rootDir/releases")
80 | }
81 |
82 | //Run JUnit tests using Gradle
83 | test{
84 | useJUnitPlatform()
85 | }
86 |
87 | pmd {
88 | consoleOutput = true
89 | toolVersion = "6.21.0"
90 | rulesMinimumPriority = 5
91 | ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"]
92 | }
--------------------------------------------------------------------------------
/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 | # https://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 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/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 https://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 Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 |
88 | @rem Execute Gradle
89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
90 |
91 | :end
92 | @rem End local scope for the variables with windows NT shell
93 | if "%ERRORLEVEL%"=="0" goto mainEnd
94 |
95 | :fail
96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
97 | rem the _cmd.exe /c_ return code!
98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
99 | exit /b 1
100 |
101 | :mainEnd
102 | if "%OS%"=="Windows_NT" endlocal
103 |
104 | :omega
105 |
--------------------------------------------------------------------------------
/releases/Response Pattern Matcher-v2.0.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackj07/Response-Pattern-Matcher/d5f38c1e3323c83f7168400543bc1097d98cef9d/releases/Response Pattern Matcher-v2.0.1.jar
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | *
4 | * The settings file is used to specify which projects to include in your build.
5 | *
6 | * Detailed information about configuring a multi-project build in Gradle can be found
7 | * in the user manual at https://docs.gradle.org/6.0.1/userguide/multi_project_builds.html
8 | */
9 |
10 | rootProject.name = 'Response Pattern Matcher'
11 |
--------------------------------------------------------------------------------
/src/main/java/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import rpm.ResponsePatternMatcher;
4 |
5 | public class BurpExtender extends ResponsePatternMatcher { }
6 | //This class has to be in this package for the Burp extension to work
--------------------------------------------------------------------------------
/src/main/java/rpm/MessageProcessor.java:
--------------------------------------------------------------------------------
1 | package rpm;
2 |
3 | import burp.IHttpRequestResponse;
4 | import burp.IHttpRequestResponsePersisted;
5 | import rpm.model.ResultsTableModel;
6 | import rpm.ui.GUI;
7 |
8 | import java.net.MalformedURLException;
9 | import java.net.URL;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.regex.Matcher;
13 | import java.util.regex.Pattern;
14 |
15 | public class MessageProcessor implements Runnable {
16 | private final int toolFlag;
17 | private final boolean messageIsRequest;
18 | private final IHttpRequestResponse messageInfo;
19 | private final boolean inScopeOnly;
20 | private final boolean matchOnRequests;
21 | private final boolean matchOnResponses;
22 | private final List payloads;
23 | private final List results_responses;
24 | private final List results_requests;
25 | private final ResultsTableModel resultsTableModel_responses;
26 | private final ResultsTableModel resultsTableModel_requests;
27 |
28 | public MessageProcessor(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo, GUI gui) {
29 | this.toolFlag = toolFlag;
30 | this.messageIsRequest = messageIsRequest;
31 | this.messageInfo = messageInfo;
32 | this.inScopeOnly = gui.getInScopeOnly();
33 | this.matchOnRequests = gui.getMatchOnRequests();
34 | this.matchOnResponses = gui.getMatchOnResponses();
35 | this.payloads = gui.getPayloads();
36 | this.results_responses = gui.getResults_responses();
37 | this.results_requests = gui.getResults_requests();
38 | this.resultsTableModel_responses = gui.getResultsTableModel_responses();
39 | this.resultsTableModel_requests = gui.getResultsTableModel_requests();
40 | }
41 |
42 | @Override
43 | public void run() {
44 | try {
45 | URL intercepted_url = ResponsePatternMatcher.helpers.analyzeRequest(messageInfo.getHttpService(), messageInfo.getRequest()).getUrl();
46 | if (!inScopeOnly || ResponsePatternMatcher.callbacks.isInScope(intercepted_url)) {
47 |
48 | //Responses
49 | if(!messageIsRequest && matchOnResponses){
50 | String messageAsString = new String(messageInfo.getResponse());
51 | List latestResults = new ArrayList<>();
52 | URL url = ResponsePatternMatcher.helpers.analyzeRequest(messageInfo).getUrl();
53 | IHttpRequestResponsePersisted requestResponsePersisted = ResponsePatternMatcher.callbacks.saveBuffersToTempFiles(messageInfo);
54 |
55 | for (Payload payload : payloads) {
56 | if (payload.getActive()) {//Only use the payload if it is active
57 | Boolean isRegex = payload.getIsRegex();
58 | String payloadContent = payload.getContent();
59 |
60 | if (!isRegex)payloadContent = Pattern.quote(payloadContent);//Take as literal string, not a search pattern.
61 | Pattern pattern = Pattern.compile(payloadContent, Pattern.CASE_INSENSITIVE);
62 | Matcher matcher = pattern.matcher(messageAsString);
63 |
64 | while (matcher.find()) {
65 | String extract;
66 | try {
67 | extract = messageAsString.substring(matcher.start() - 30, matcher.end() + 30);
68 | } catch (StringIndexOutOfBoundsException ex) {
69 | //Only want to do this when OOB Exception is thrown following Extraction.
70 | //Too much overhead to do it for every response.
71 | extract = messageAsString.substring(
72 | getMaxStartIndex(messageAsString, matcher.start(), matcher.end()),
73 | getMaxEndIndex(messageAsString, matcher.start(), matcher.end()));
74 | }
75 |
76 | //Update result table with a result
77 | latestResults.add(new ResultEntry(
78 | 0,
79 | toolFlag,
80 | url,
81 | requestResponsePersisted,
82 | extract,
83 | payload.getContent()));
84 | }
85 | }
86 | }
87 | if (latestResults.size() > 0) {
88 | synchronized (results_responses) {
89 | try {
90 | for (ResultEntry result : latestResults) {
91 | result.setNumber(results_responses.size());
92 | results_responses.add(result);
93 | int row = results_responses.size() - 1;
94 | resultsTableModel_responses.fireTableRowsInserted(row, row);//Have to add 1 by 1 to keep model and view aligned to prevent IOOBException
95 | }
96 | } catch (Exception e) {
97 | ResponsePatternMatcher.stderror.println("An exception occurred when adding response to Results");
98 | ResponsePatternMatcher.stderror.println(e);
99 | e.printStackTrace();
100 | }
101 | }
102 | }
103 | }
104 |
105 | //Requests
106 | if(messageIsRequest && matchOnRequests){
107 | String messageAsString = new String(messageInfo.getRequest());
108 | List latestResults = new ArrayList<>();
109 | URL url = ResponsePatternMatcher.helpers.analyzeRequest(messageInfo).getUrl();
110 | IHttpRequestResponsePersisted requestResponsePersisted = ResponsePatternMatcher.callbacks.saveBuffersToTempFiles(messageInfo);
111 |
112 | for (Payload payload : payloads) {
113 | if (payload.getActive()) {//Only use the payload if it is active
114 | Boolean isRegex = payload.getIsRegex();
115 | String payloadContent = payload.getContent();
116 |
117 | if (!isRegex)payloadContent = Pattern.quote(payloadContent);//Take as literal string, not a search pattern.
118 | Pattern pattern = Pattern.compile(payloadContent, Pattern.CASE_INSENSITIVE);
119 | Matcher matcher = pattern.matcher(messageAsString);
120 |
121 | while (matcher.find()) {
122 | String extract;
123 | try {
124 | extract = messageAsString.substring(matcher.start() - 30, matcher.end() + 30);
125 | } catch (StringIndexOutOfBoundsException ex) {
126 | //Only want to do this when OOB Exception is thrown following Extraction.
127 | //Too much overhead to do it for every response.
128 | extract = messageAsString.substring(
129 | getMaxStartIndex(messageAsString, matcher.start(), matcher.end()),
130 | getMaxEndIndex(messageAsString, matcher.start(), matcher.end()));
131 | }
132 |
133 | //Update result table with a result
134 | latestResults.add(new ResultEntry(
135 | 0,
136 | toolFlag,
137 | url,
138 | requestResponsePersisted,
139 | extract,
140 | payload.getContent()));
141 | }
142 | }
143 | }
144 | if (latestResults.size() > 0) {
145 | synchronized (results_requests) {
146 | try {
147 | for (ResultEntry result : latestResults) {
148 | result.setNumber(results_requests.size());
149 | results_requests.add(result);
150 | int row = results_requests.size() - 1;
151 | resultsTableModel_requests.fireTableRowsInserted(row, row);//Have to add 1 by 1 to keep model and view aligned to prevent IOOBException
152 | }
153 | } catch (Exception e) {
154 | ResponsePatternMatcher.stderror.println("An exception occurred when adding request to Results");
155 | ResponsePatternMatcher.stderror.println(e);
156 | e.printStackTrace();
157 | }
158 | }
159 | }
160 | }
161 | }
162 | } catch (Exception e){
163 | ResponsePatternMatcher.stderror.println("An error occurred processing message:");
164 | ResponsePatternMatcher.stderror.println(e);
165 | e.printStackTrace();
166 | }
167 | }
168 |
169 | //Return the start index for the extract by going back as far as you can whilst staying within bounds of response
170 | private int getMaxStartIndex(String response, int extractStartIndex, int extractEndIndex) {
171 | int toReturn = 0;
172 | try {
173 | while (true) {
174 | response.substring(extractStartIndex + toReturn - 1, extractEndIndex);
175 | toReturn--;
176 | if (toReturn <= -25) break;
177 | }
178 | } catch (StringIndexOutOfBoundsException ex) { }
179 | return extractStartIndex + toReturn;
180 | }
181 |
182 | //Return the start index for the extract by going forward as far as you can whilst staying within bounds of response
183 | private int getMaxEndIndex(String response, int extractStartIndex, int extractEndIndex) {
184 | int toReturn = 0;
185 | try {
186 | while (true) {
187 | response.substring(extractStartIndex, extractEndIndex + toReturn + 1);
188 | toReturn++;
189 | if (toReturn >= 25) break;
190 | }
191 | } catch (StringIndexOutOfBoundsException ex) { }
192 | return extractEndIndex + toReturn;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/main/java/rpm/Payload.java:
--------------------------------------------------------------------------------
1 | package rpm;
2 |
3 | public class Payload {
4 | private final String content;
5 | private final Boolean isRegex;
6 | private final Boolean active;
7 |
8 | public Payload(String content, Boolean isRegex, Boolean active){
9 | this.content=content;
10 | this.isRegex=isRegex;
11 | this.active = active;
12 | }
13 |
14 | public String getContent(){
15 | return content;
16 | }
17 |
18 | public Boolean getIsRegex(){
19 | return isRegex;
20 | }
21 |
22 | public Boolean getActive(){
23 | return active;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/rpm/ResponsePatternMatcher.java:
--------------------------------------------------------------------------------
1 | package rpm;
2 |
3 | import burp.*;
4 |
5 | import rpm.controller.ContentController;
6 |
7 | import rpm.ui.GUI;
8 | import java.awt.*;
9 | import java.io.*;
10 | import java.util.concurrent.ExecutorService;
11 | import java.util.concurrent.Executors;
12 |
13 | public class ResponsePatternMatcher implements IBurpExtender, ITab, IHttpListener,
14 | IMessageEditorController, IExtensionStateListener {
15 | //Static Burp objects
16 | public static IBurpExtenderCallbacks callbacks;
17 | public static IExtensionHelpers helpers;
18 | public static PrintWriter stdout;
19 | public static PrintWriter stderror;
20 |
21 | //Threading
22 | private ExecutorService service;
23 |
24 | //UI
25 | GUI gui;
26 |
27 | //Controller
28 | private ContentController contentController;
29 |
30 | //
31 | // implement IBurpExtender
32 | //
33 | @Override
34 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){
35 | service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
36 | contentController = new ContentController();
37 |
38 | // keep a reference to our callbacks object
39 | ResponsePatternMatcher.callbacks = callbacks;
40 |
41 | //Terminal Output
42 | stdout = new PrintWriter(callbacks.getStdout(), true);
43 | stderror = new PrintWriter(callbacks.getStderr(), true);
44 |
45 | // obtain an extension helpers object
46 | helpers = callbacks.getHelpers();
47 |
48 | // set our extension name
49 | callbacks.setExtensionName("Response Pattern Matcher");
50 |
51 | //Set up the GUI
52 | gui = new GUI(contentController, this);
53 | gui.initialise();
54 | }
55 |
56 | //
57 | // implement ITab
58 | //
59 | @Override
60 | public String getTabCaption(){
61 | return "Response Pattern Matcher";
62 | }
63 |
64 | @Override
65 | public Component getUiComponent(){
66 | return gui.getTabs_outer();
67 | }
68 |
69 | //
70 | // implement IHttpListener
71 | //
72 | @Override
73 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo){
74 | service.execute(new MessageProcessor(toolFlag, messageIsRequest, messageInfo, gui));
75 | }
76 |
77 | //
78 | // implement IMessageEditorController
79 | //
80 | @Override
81 | public byte[] getRequest() {
82 | return contentController.getCurrentlyDisplayedItem().getRequest();
83 | }
84 |
85 | @Override
86 | public byte[] getResponse() {
87 | return contentController.getCurrentlyDisplayedItem().getResponse();
88 | }
89 |
90 | @Override
91 | public IHttpService getHttpService() {
92 | return contentController.getCurrentlyDisplayedItem().getHttpService();
93 | }
94 |
95 | @Override
96 | public void extensionUnloaded() {
97 | //Close Thread Pool
98 | service.shutdownNow();
99 | stdout.println("Extension Unloaded Successfully");
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/rpm/ResultEntry.java:
--------------------------------------------------------------------------------
1 | package rpm;
2 |
3 | import burp.IHttpRequestResponsePersisted;
4 |
5 | import java.awt.*;
6 | import java.net.URL;
7 |
8 | public class ResultEntry {
9 | //Needs to be in serializable (Setter getter) format for Jackson json parser
10 | private int number;
11 | private int tool;
12 | private URL url;
13 | private IHttpRequestResponsePersisted requestResponse;
14 | private String sampleExtract;
15 | private String payloadContent;
16 | private Color color;
17 |
18 | public ResultEntry(int number, int tool, URL url, IHttpRequestResponsePersisted requestResponse,
19 | String sampleExtract, String payloadContent) {
20 | this.number = number;
21 | this.tool = tool;
22 | this.url = url;
23 | this.requestResponse = requestResponse;
24 | this.sampleExtract = sampleExtract;
25 | this.payloadContent = payloadContent;
26 | }
27 |
28 | public int getNumber() {
29 | return number;
30 | }
31 |
32 | public void setNumber(int number) {
33 | this.number = number;
34 | }
35 |
36 | public int getTool() {
37 | return tool;
38 | }
39 |
40 | public void setTool(int tool) {
41 | this.tool = tool;
42 | }
43 |
44 | public URL getUrl() {
45 | return url;
46 | }
47 |
48 | public void setUrl(URL url) {
49 | this.url = url;
50 | }
51 |
52 | public IHttpRequestResponsePersisted getRequestResponse() {
53 | return requestResponse;
54 | }
55 |
56 | public void setRequestResponse(IHttpRequestResponsePersisted requestResponse) {
57 | this.requestResponse = requestResponse;
58 | }
59 |
60 | public String getSampleExtract() {
61 | return sampleExtract;
62 | }
63 |
64 | public void setSampleExtract(String sampleExtract) {
65 | this.sampleExtract = sampleExtract;
66 | }
67 |
68 | public String getPayloadContent() {
69 | return payloadContent;
70 | }
71 |
72 | public void setPayloadContent(String payloadContent) {
73 | this.payloadContent = payloadContent;
74 | }
75 |
76 | public Color getColor() {
77 | return color;
78 | }
79 |
80 | public void setColor(Color color) {
81 | this.color = color;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/rpm/controller/ContentController.java:
--------------------------------------------------------------------------------
1 | package rpm.controller;
2 |
3 | import burp.IHttpRequestResponse;
4 | import burp.IHttpRequestResponsePersisted;
5 | import burp.IMessageEditor;
6 |
7 | public class ContentController {
8 | private IHttpRequestResponse currentlyDisplayedItem;
9 | private int selectedPayloadRow;
10 | private IMessageEditor requestViewer;
11 | private IMessageEditor responseViewer;
12 |
13 | public IHttpRequestResponse getCurrentlyDisplayedItem() {
14 | return currentlyDisplayedItem;
15 | }
16 |
17 | public void setCurrentlyDisplayedItem(IHttpRequestResponsePersisted currentlyDisplayedItem) {
18 | this.currentlyDisplayedItem = currentlyDisplayedItem;
19 | }
20 |
21 | public void setSelectedPayloadRow(int selectedPayloadRow) {
22 | this.selectedPayloadRow = selectedPayloadRow;
23 | }
24 |
25 | public int getSelectedPayloadRow() {
26 | return selectedPayloadRow;
27 | }
28 |
29 | public IMessageEditor getRequestViewer() {
30 | return requestViewer;
31 | }
32 |
33 | public void setRequestViewer(IMessageEditor requestViewer) {
34 | this.requestViewer = requestViewer;
35 | }
36 |
37 | public IMessageEditor getResponseViewer() {
38 | return responseViewer;
39 | }
40 |
41 | public void setResponseViewer(IMessageEditor responseViewer) {
42 | this.responseViewer = responseViewer;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/rpm/model/PayloadsTableModel.java:
--------------------------------------------------------------------------------
1 | package rpm.model;
2 |
3 | import rpm.Payload;
4 | import com.coreyd97.BurpExtenderUtilities.Preferences;
5 |
6 | import javax.swing.table.AbstractTableModel;
7 | import java.util.List;
8 |
9 | public class PayloadsTableModel extends AbstractTableModel {
10 |
11 | private final List payloads;
12 | private final Preferences prefs;
13 | public PayloadsTableModel(List payloads, Preferences prefs){
14 | this.payloads = payloads;
15 | this.prefs = prefs;
16 | }
17 |
18 | @Override
19 | public int getRowCount() {
20 | return payloads.size();
21 | }
22 |
23 | @Override
24 | public int getColumnCount() { return 3; }
25 |
26 | @Override
27 | public String getColumnName(int columnIndex)
28 | {
29 | switch (columnIndex)
30 | {
31 | case 0:
32 | return "Payload";
33 | case 1:
34 | return "Is Regex";
35 | case 2:
36 | return "Active";
37 | default:
38 | return "";
39 | }
40 | }
41 |
42 | @Override
43 | public Class> getColumnClass(int columnIndex)
44 | {
45 | switch(columnIndex){
46 | case 0:
47 | return String.class;
48 | case 1:
49 | return Boolean.class;
50 | case 2:
51 | return Boolean.class;
52 | default:
53 | return String.class;
54 | }
55 | }
56 |
57 | @Override
58 | public boolean isCellEditable(int row, int col) {
59 | if (col == 1) {
60 | return !payloads.get(row).getContent().equals("/*");
61 | } else return col == 2;
62 | }
63 |
64 | @Override
65 | public Object getValueAt(int rowIndex, int columnIndex) {
66 | switch(columnIndex) {
67 | case 0:
68 | return payloads.get(rowIndex).getContent();
69 | case 1:
70 | return payloads.get(rowIndex).getIsRegex();
71 | case 2:
72 | return payloads.get(rowIndex).getActive();
73 | default:
74 | return payloads.get(rowIndex).getContent();
75 | }
76 | }
77 |
78 | @Override
79 | public void setValueAt(Object value, int row, int col) {
80 | if(row > payloads.size())return;
81 | super.setValueAt(value, row, col);
82 |
83 | if (col == 1) {
84 | if ((Boolean) this.getValueAt(row, col)) {
85 | payloads.set(row, new Payload(payloads.get(row).getContent(),false, payloads.get(row).getActive()));
86 | prefs.setSetting("Payloads", payloads);
87 | }
88 | else if (!(Boolean) this.getValueAt(row, col)) {
89 | payloads.set(row, new Payload(payloads.get(row).getContent(),true, payloads.get(row).getActive()));
90 | prefs.setSetting("Payloads", payloads);
91 | }
92 | }
93 |
94 | if (col == 2){
95 | if ((Boolean) this.getValueAt(row, col)) {
96 | payloads.set(row, new Payload(payloads.get(row).getContent(), payloads.get(row).getIsRegex(), false));
97 | prefs.setSetting("Payloads", payloads);
98 | }
99 | else if (!(Boolean) this.getValueAt(row, col)) {
100 | payloads.set(row, new Payload(payloads.get(row).getContent(), payloads.get(row).getIsRegex(), true));
101 | prefs.setSetting("Payloads", payloads);
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/rpm/model/ResultsTableModel.java:
--------------------------------------------------------------------------------
1 | package rpm.model;
2 |
3 | import rpm.ResponsePatternMatcher;
4 | import rpm.ResultEntry;
5 |
6 | import javax.swing.table.AbstractTableModel;
7 | import java.util.List;
8 |
9 | public class ResultsTableModel extends AbstractTableModel {
10 | private final List results;
11 | public ResultsTableModel(List results) {
12 | this.results=results;
13 | }
14 |
15 | @Override
16 | public int getRowCount() {
17 | return results.size();
18 | }
19 |
20 | @Override
21 | public int getColumnCount() {
22 | return 5;
23 | }
24 |
25 | @Override
26 | public String getColumnName(int columnIndex) {
27 | switch (columnIndex) {
28 | case 0:
29 | return "#";
30 | case 1:
31 | return "Tool";
32 | case 2:
33 | return "URL";
34 | case 3:
35 | return "Payload";
36 | case 4:
37 | return "Sample Extract";
38 | default:
39 | return "";
40 | }
41 | }
42 |
43 | @Override
44 | public Class> getColumnClass(int columnIndex) {
45 | if(columnIndex == 0)return Integer.class;
46 | return String.class;
47 | }
48 |
49 | @Override
50 | public Object getValueAt(int rowIndex, int columnIndex) {
51 | if(rowIndex > results.size()) return "";
52 | ResultEntry resultEntry = results.get(rowIndex);
53 |
54 | switch (columnIndex) {
55 | case 0:
56 | return resultEntry.getNumber();
57 | case 1:
58 | return ResponsePatternMatcher.callbacks.getToolName(resultEntry.getTool());
59 | case 2:
60 | return resultEntry.getUrl().toString();
61 | case 3:
62 | return resultEntry.getPayloadContent();
63 | case 4:
64 | return resultEntry.getSampleExtract();
65 | default:
66 | return "";
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/rpm/ui/CellRenderer.java:
--------------------------------------------------------------------------------
1 | package rpm.ui;
2 |
3 | import rpm.ResultEntry;
4 |
5 | import javax.swing.*;
6 | import javax.swing.table.DefaultTableCellRenderer;
7 | import java.awt.*;
8 | import java.util.List;
9 |
10 | class CellRenderer extends DefaultTableCellRenderer{
11 | private Color highlightColour;
12 | private List results;
13 | public void setHighlightColor(Color colour){ this.highlightColour=colour; }
14 | public void setResults(Listresults){ this.results = results; }
15 |
16 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column){
17 | Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
18 |
19 | ResultEntry result = null;
20 | if(results != null)result = results.get(table.convertRowIndexToModel(row));
21 |
22 | if(result != null && result.getColor() != null){
23 | c.setBackground(result.getColor());
24 | }else{
25 | c.setBackground(table.getBackground());//no highlight
26 | }
27 |
28 | if(isSelected && highlightColour != null)c.setBackground(highlightColour);//Put burp selection highlight defaults back
29 | return c;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/rpm/ui/ExtenderPopupMenu.java:
--------------------------------------------------------------------------------
1 | package rpm.ui;
2 |
3 | import burp.BurpExtender;
4 | import rpm.model.ResultsTableModel;
5 | import rpm.ResultEntry;
6 |
7 | import javax.swing.*;
8 | import javax.swing.table.TableModel;
9 | import java.awt.*;
10 | import java.io.File;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | class ExtenderPopupMenu extends JPopupMenu {
15 | private final ResultsTableModel model;
16 | private final ResultTable table;
17 | private final List results;
18 | private final CellRenderer cellRenderer;
19 |
20 | JSONParser parser = new JSONParser(BurpExtender.callbacks);
21 |
22 | public ExtenderPopupMenu(TableModel model_, ResultTable table_, List results_, CellRenderer cellRenderer_) {
23 | this.model = (ResultsTableModel)model_;
24 | this.table = table_;
25 | this.results = results_;
26 | this.cellRenderer = cellRenderer_;
27 |
28 | cellRenderer.setHighlightColor(table.getSelectionBackground());
29 |
30 | JMenu highlightIdentical = new JMenu("Highlight Identical Matches");
31 | JMenuItem highlightBLUE = new JMenuItem("Blue");
32 | highlightIdentical.add(highlightBLUE);
33 | JMenuItem highlightGREEN = new JMenuItem("Green");
34 | highlightIdentical.add(highlightGREEN);
35 | JMenuItem highlightRED = new JMenuItem("Red");
36 | highlightIdentical.add(highlightRED);
37 | JMenuItem highlightORANGE = new JMenuItem("Orange");
38 | highlightIdentical.add(highlightORANGE);
39 | JMenuItem highlightCYAN = new JMenuItem("Cyan");
40 | highlightIdentical.add(highlightCYAN);
41 | JMenuItem highlightYELLOW = new JMenuItem("Yellow");
42 | highlightIdentical.add(highlightYELLOW);
43 | JMenuItem highlightMAGENTA = new JMenuItem("Magenta");
44 | highlightIdentical.add(highlightMAGENTA);
45 | JMenuItem highlightPINK = new JMenuItem("Pink");
46 | highlightIdentical.add(highlightPINK);
47 | add(highlightIdentical);
48 |
49 | JMenuItem removeHighlight = new JMenuItem("Remove Highlight");
50 | add(removeHighlight);
51 |
52 | JMenu export = new JMenu("Export");
53 | JMenuItem exportSelected = new JMenuItem("Selected");
54 | JMenuItem exportHighlighted = new JMenuItem("Highlighted Colour");
55 | JMenuItem exportAll = new JMenuItem("All");
56 | export.add(exportSelected);
57 | export.add(exportHighlighted);
58 | export.add(exportAll);
59 | add(export);
60 |
61 | JMenu delete = new JMenu("Delete");
62 | JMenuItem deleteSelected = new JMenuItem("Selected");
63 | JMenuItem deleteHighlighted = new JMenuItem("Highlighted Colour");
64 | JMenuItem deleteAll = new JMenuItem("All");
65 | delete.add(deleteSelected);
66 | delete.add(deleteHighlighted);
67 | delete.add(deleteAll);
68 | add(delete);
69 |
70 | highlightBLUE.addActionListener(e -> highlightColour(Color.BLUE));
71 | highlightGREEN.addActionListener(e -> highlightColour(Color.GREEN));
72 | highlightRED.addActionListener(e -> highlightColour(Color.RED));
73 | highlightORANGE.addActionListener(e -> highlightColour(Color.ORANGE));
74 | highlightCYAN.addActionListener(e -> highlightColour(Color.CYAN));
75 | highlightYELLOW.addActionListener(e -> highlightColour(Color.YELLOW));
76 | highlightMAGENTA.addActionListener(e -> highlightColour(Color.MAGENTA));
77 | highlightPINK.addActionListener(e -> highlightColour(Color.PINK));
78 |
79 | removeHighlight.addActionListener(e -> {
80 | ResultEntry selectedResult = results.get(table.convertRowIndexToModel(table.getSelectedRow()));
81 | if(selectedResult.getColor() != null) {
82 | Color colourToClear = selectedResult.getColor();
83 | for (ResultEntry result : results)if (result.getColor() == colourToClear)result.setColor(null);
84 | model.fireTableDataChanged();
85 | cellRenderer.setResults(results);
86 | table.repaint();
87 | }
88 | });
89 |
90 | exportSelected.addActionListener(e -> {
91 | int[] selectedRows = table.getSelectedRows();
92 | if(selectedRows.length > 0){
93 | ArrayList results_to_export = new ArrayList<>();
94 | for(int selectedRow : selectedRows)results_to_export.add(results.get(table.convertRowIndexToModel(selectedRow)));
95 | if(!results_to_export.isEmpty())parser.writeResultsToFile(selectFile(),results_to_export);
96 | }
97 | });
98 |
99 | exportHighlighted.addActionListener(e -> {
100 | ResultEntry selectedResult = results.get(table.convertRowIndexToModel(table.getSelectedRow()));
101 | if(selectedResult.getColor() != null) {
102 | ArrayList results_to_export = new ArrayList<>();
103 | Color colourToExport = selectedResult.getColor();
104 | for (ResultEntry result : results)if(result.getColor() == colourToExport)results_to_export.add(result);
105 | if(!results_to_export.isEmpty())parser.writeResultsToFile(selectFile(),results_to_export);
106 | }
107 | });
108 |
109 | exportAll.addActionListener(e -> {
110 | parser.writeResultsToFile(selectFile(),new ArrayList<>(results));//convert back from Collection.syncList
111 | });
112 |
113 | deleteSelected.addActionListener(e -> {
114 | int[] selectedRows = table.getSelectedRows();
115 | if(selectedRows.length > 0){
116 | List results_to_remove = new ArrayList<>();
117 | for(int selectedRow : selectedRows)results_to_remove.add(results.get(table.convertRowIndexToModel(selectedRow)));
118 |
119 | if(!results_to_remove.isEmpty()) {
120 | for(ResultEntry resultToRemove : results_to_remove){
121 | results.remove(resultToRemove);
122 | model.fireTableRowsDeleted(results.indexOf(resultToRemove),results.indexOf(resultToRemove));
123 | }
124 | cellRenderer.setResults(results);
125 | table.repaint();
126 | table.setDefaultColumnSizes();
127 | }
128 | }
129 | });
130 |
131 | deleteHighlighted.addActionListener(e -> {
132 | ResultEntry selectedResult = results.get(table.convertRowIndexToModel(table.getSelectedRow()));
133 | if(selectedResult.getColor() != null) {
134 | List results_to_remove = new ArrayList<>();
135 | Color colourToDelete = selectedResult.getColor();
136 | for (ResultEntry result : results)if (result.getColor() == colourToDelete)results_to_remove.add(result);
137 |
138 | if(!results_to_remove.isEmpty()) {
139 | for(ResultEntry resultToRemove : results_to_remove){
140 | results.remove(resultToRemove);
141 | model.fireTableRowsDeleted(results.indexOf(resultToRemove),results.indexOf(resultToRemove));
142 | }
143 | cellRenderer.setResults(results);
144 | table.repaint();
145 | table.setDefaultColumnSizes();
146 | }
147 | }
148 | });
149 |
150 | deleteAll.addActionListener(e -> {
151 | int resultsSize = results.size()-1;
152 | results.clear();
153 | model.fireTableRowsDeleted(0,resultsSize);
154 | cellRenderer.setResults(results);
155 | table.repaint();
156 | table.setDefaultColumnSizes();
157 | });
158 | }
159 |
160 | private void highlightColour(Color c){
161 | ResultEntry selectedResult = results.get(table.convertRowIndexToModel(table.getSelectedRow()));
162 | for (ResultEntry result : results)if(result.getRequestResponse().equals(selectedResult.getRequestResponse()))result.setColor(c);
163 | model.fireTableDataChanged();
164 | cellRenderer.setResults(results);
165 | table.repaint(); //This calls getTableCellRendererComponent
166 | }
167 |
168 | private File selectFile(){
169 | JFileChooser fileChooser = new JFileChooser();
170 | fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
171 | fileChooser.showSaveDialog(null);
172 | return fileChooser.getSelectedFile();
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/rpm/ui/GUI.java:
--------------------------------------------------------------------------------
1 | package rpm.ui;
2 |
3 | import com.coreyd97.BurpExtenderUtilities.DefaultGsonProvider;
4 | import com.coreyd97.BurpExtenderUtilities.Preferences;
5 | import com.google.gson.reflect.TypeToken;
6 | import rpm.Payload;
7 | import rpm.ResponsePatternMatcher;
8 | import rpm.ResultEntry;
9 |
10 | import rpm.controller.ContentController;
11 |
12 | import rpm.model.PayloadsTableModel;
13 | import rpm.model.ResultsTableModel;
14 |
15 | import javax.swing.*;
16 | import javax.swing.border.EtchedBorder;
17 | import java.awt.*;
18 | import java.awt.event.ItemEvent;
19 | import java.io.BufferedReader;
20 | import java.io.File;
21 | import java.io.FileReader;
22 | import java.io.IOException;
23 | import java.util.ArrayList;
24 | import java.util.Collections;
25 | import java.util.List;
26 |
27 | public class GUI {
28 | private Preferences prefs; //BurpExtenderUtilities (Credit: CoreyD97) used to save configs
29 | private Boolean inScopeOnly;
30 | private Boolean matchOnResponses;
31 | private Boolean matchOnRequests;
32 | private final ResponsePatternMatcher responsePatternMatcher;
33 | private List payloads;
34 | private final List results_responses;
35 | private final List results_requests;
36 |
37 | //Controller
38 | private final ContentController contentController;
39 |
40 | //Model
41 | private PayloadsTableModel payloadsTableModel;
42 | private final ResultsTableModel resultsTableModel_responses;
43 | private final ResultsTableModel resultsTableModel_requests;
44 |
45 | //UI
46 | private JCheckBox checkBox_isInScope;
47 | private JCheckBox checkBox_matchOnResponses;
48 | private JCheckBox checkBox_matchOnRequests;
49 | private JTabbedPane tabs_outer;
50 | private PayloadTable table_payloads;
51 | private ResultTable table_results_responses;
52 | private ResultTable table_results_requests;
53 |
54 | public GUI(ContentController contentController, ResponsePatternMatcher responsePatternMatcher){
55 | this.contentController = contentController;
56 | this.responsePatternMatcher = responsePatternMatcher;
57 | this.results_responses = Collections.synchronizedList(new ArrayList<>());
58 | this.results_requests = Collections.synchronizedList(new ArrayList<>());
59 | this.resultsTableModel_responses = new ResultsTableModel(results_responses);
60 | this.resultsTableModel_requests = new ResultsTableModel(results_requests);
61 | }
62 |
63 | public void initialise(){
64 | //Define the preferences objects
65 | prefs = new Preferences("Response Pattern Matcher", new DefaultGsonProvider(), ResponsePatternMatcher.callbacks);
66 | prefs.registerSetting("Payloads", new TypeToken>(){}.getType(), Preferences.Visibility.GLOBAL);
67 | prefs.registerSetting("In Scope Only", new TypeToken(){}.getType(), Preferences.Visibility.GLOBAL);
68 | prefs.registerSetting("First Run", new TypeToken(){}.getType(), Preferences.Visibility.GLOBAL);
69 | prefs.registerSetting("Match On Responses", new TypeToken(){}.getType(), Preferences.Visibility.GLOBAL);
70 | prefs.registerSetting("Match On Requests", new TypeToken(){}.getType(), Preferences.Visibility.GLOBAL);
71 |
72 | //Get the saved payloads from the Preferences object
73 | payloads = (prefs.getSetting("Payloads") == null)
74 | ? Collections.synchronizedList(new ArrayList<>())
75 | : Collections.synchronizedList(prefs.getSetting("Payloads"));
76 | payloadsTableModel = new PayloadsTableModel(payloads, prefs);
77 |
78 | // create UI
79 | SwingUtilities.invokeLater(() -> {
80 | // Main Panel for user input items (Config Tab)
81 | JPanel panel_input = new JPanel(new FlowLayout(FlowLayout.CENTER));
82 | JScrollPane scrollPane_input = new JScrollPane(panel_input);
83 | panel_input.setLayout(new BoxLayout(panel_input, BoxLayout.PAGE_AXIS));
84 |
85 | // Wordlist Input Label (Config Tab)
86 | JPanel panel_label_wordlist = new JPanel(new FlowLayout(FlowLayout.CENTER));
87 | JLabel label_wordlist = new JLabel("Wordlist Input");
88 | label_wordlist.setHorizontalAlignment(SwingConstants.CENTER);
89 | panel_label_wordlist.add(label_wordlist);
90 | panel_input.add(panel_label_wordlist);
91 |
92 | // Payloads Input (Config Tab)
93 | JPanel panel_table_payloads = new JPanel(new FlowLayout(FlowLayout.CENTER));
94 | table_payloads = new PayloadTable(payloadsTableModel, contentController);
95 | table_payloads.setDefaultColumnSizes();
96 | JScrollPane scrollPane_payloads = new JScrollPane(table_payloads);
97 | scrollPane_payloads.setPreferredSize(new Dimension(600, 270));
98 | panel_table_payloads.add(scrollPane_payloads);
99 | panel_input.add(panel_table_payloads);
100 |
101 | //Button Load (Config Tab)
102 | JPanel panel_wordlist_buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
103 | JButton button_upload = new JButton("Load ...");
104 | button_upload.addActionListener(e -> {
105 | if (e.getSource() == button_upload) {
106 | final JFileChooser fc = new JFileChooser();
107 | int returnVal = fc.showOpenDialog(panel_wordlist_buttons);
108 |
109 | if (returnVal == JFileChooser.APPROVE_OPTION) {
110 | File file = fc.getSelectedFile();
111 | BufferedReader reader = null;
112 | try {
113 | String filePath = file.getPath();
114 |
115 | reader = new BufferedReader(new FileReader(filePath));
116 | for(String line; (line = reader.readLine()) != null;) {
117 | if(line == null || line.equals(""))continue;//prevent empty items being added
118 | int row = payloads.size();
119 | payloads.add(new Payload(line, false, true));
120 | payloadsTableModel.fireTableRowsInserted(row, row);
121 | }
122 | } catch (IOException ex) {
123 | ResponsePatternMatcher.stderror.println("An IOException occurred attempting to Load file");
124 | ResponsePatternMatcher.stderror.println(ex.getMessage());
125 | }finally {
126 | if(reader != null){
127 | try{
128 | reader.close();
129 | }catch(IOException ex2){
130 | ResponsePatternMatcher.stderror.println("Cannot close file reader");
131 | ResponsePatternMatcher.stderror.println(ex2.getMessage());
132 | }
133 | }
134 | prefs.setSetting("Payloads", payloads);
135 | }
136 | } else {
137 | ResponsePatternMatcher.stdout.println("File load cancelled");
138 | }
139 | }
140 | });
141 | panel_wordlist_buttons.add(button_upload);
142 |
143 | //Button Remove (Config Tab)
144 | JButton button_remove = new JButton("Remove");
145 | button_remove.addActionListener(e -> {
146 | if(contentController.getSelectedPayloadRow() >=0){
147 | payloads.remove(contentController.getSelectedPayloadRow());
148 | payloadsTableModel.fireTableRowsDeleted(contentController.getSelectedPayloadRow(), contentController.getSelectedPayloadRow());
149 | contentController.setSelectedPayloadRow(-1);
150 | prefs.setSetting("Payloads", payloads);
151 | }
152 | });
153 | panel_wordlist_buttons.add(button_remove);
154 |
155 | //Button Clear (Config Tab)
156 | JButton button_clear = new JButton("Clear");
157 | button_clear.addActionListener(e -> {
158 | int row = payloads.size();
159 | payloads.clear();
160 | payloadsTableModel.fireTableRowsDeleted(0,row);
161 | prefs.setSetting("Payloads", payloads);
162 | });
163 | panel_wordlist_buttons.add(button_clear);
164 |
165 | //Button Restore Defaults (Config Tab)
166 | JButton button_restoreDefaults = new JButton("Restore Defaults");
167 | button_restoreDefaults.addActionListener(e -> restoreDefaults());
168 | panel_wordlist_buttons.add(button_restoreDefaults);
169 |
170 | //Checkbox is in scope (Config Tab)
171 | inScopeOnly = prefs.getSetting("In Scope Only");
172 | if(inScopeOnly == null)inScopeOnly = true;
173 | checkBox_isInScope = new JCheckBox("In Scope Only", inScopeOnly);
174 | checkBox_isInScope.addItemListener(e -> {
175 | //checkbox has been selected
176 | inScopeOnly = e.getStateChange() == ItemEvent.SELECTED;
177 | prefs.setSetting("In Scope Only", inScopeOnly);
178 | });
179 | panel_wordlist_buttons.add(checkBox_isInScope);
180 | panel_input.add(panel_wordlist_buttons);
181 |
182 | //Single Input Text field (Config Tab)
183 | JPanel panel_single_input = new JPanel(new FlowLayout(FlowLayout.CENTER));
184 | JTextField textField_input = new JTextField(25);
185 | panel_single_input.add(textField_input);
186 |
187 | //Single Input Button (Config Tab)
188 | JButton button_add = new JButton("Add");
189 | button_add.addActionListener(e -> {
190 | String input = textField_input.getText();
191 | if(input != null && !input.isEmpty()){
192 | int row = payloads.size();
193 | payloads.add(new Payload(input, false, true));
194 | payloadsTableModel.fireTableRowsInserted(row, row);
195 | textField_input.setText("");
196 | prefs.setSetting("Payloads", payloads);
197 | }
198 | });
199 | panel_single_input.add(button_add);
200 | panel_input.add(panel_single_input);
201 |
202 | //First Separator (Config Tab)
203 | panel_input.add(new JSeparator());
204 |
205 | // Response Config Label (Config Tab)
206 | JPanel panel_label_responses = new JPanel(new FlowLayout(FlowLayout.CENTER));
207 | JLabel label_responses = new JLabel("Response Match Configuration");
208 | label_responses.setHorizontalAlignment(SwingConstants.CENTER);
209 | panel_label_responses.add(label_responses);
210 | panel_input.add(panel_label_responses);
211 |
212 | //Clear response results table Button (Config Tab)
213 | JPanel panel_responseConfig_buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
214 | JButton button_clear_responses = new JButton("Clear Matches On Responses");
215 | button_clear_responses.addActionListener(e -> {
216 | synchronized (results_responses) {
217 | results_responses.clear();
218 | contentController.getRequestViewer().setMessage(new byte[0], true);
219 | contentController.getResponseViewer().setMessage(new byte[0], false);
220 | resultsTableModel_responses.fireTableDataChanged();
221 | }
222 | });
223 | panel_responseConfig_buttons.add(button_clear_responses);
224 |
225 | //Checkbox match on responses (Config Tab)
226 | matchOnResponses = prefs.getSetting("Match On Responses");
227 | if(matchOnResponses == null)matchOnResponses = true;
228 | checkBox_matchOnResponses = new JCheckBox("Match On Responses", matchOnResponses);
229 | checkBox_matchOnResponses.addItemListener(e -> {
230 | //checkbox has been selected
231 | matchOnResponses = e.getStateChange() == ItemEvent.SELECTED;
232 | prefs.setSetting("Match On Responses", matchOnResponses);
233 | });
234 | panel_responseConfig_buttons.add(checkBox_matchOnResponses);
235 | panel_input.add(panel_responseConfig_buttons);
236 |
237 | //Second Separator (Config Tab)
238 | panel_input.add(new JSeparator());
239 |
240 | // Request Config Label (Config Tab)
241 | JPanel panel_label_requests = new JPanel(new FlowLayout(FlowLayout.CENTER));
242 | JLabel label_requests = new JLabel("Request Match Configuration");
243 | label_requests.setHorizontalAlignment(SwingConstants.CENTER);
244 | panel_label_requests.add(label_requests);
245 | panel_input.add(panel_label_requests);
246 |
247 | //Clear requests table Button (Config Tab)
248 | JPanel panel_requestConfig_buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
249 | JButton button_clear_requests = new JButton("Clear Matches On Requests");
250 | button_clear_requests.addActionListener(e -> {
251 | synchronized (results_requests) {
252 | results_requests.clear();
253 | contentController.getRequestViewer().setMessage(new byte[0], true);
254 | contentController.getResponseViewer().setMessage(new byte[0], false);
255 | resultsTableModel_requests.fireTableDataChanged();
256 | }
257 | });
258 | panel_requestConfig_buttons.add(button_clear_requests);
259 |
260 | //Checkbox match on responses (Config Tab)
261 | matchOnRequests = prefs.getSetting("Match On Requests");
262 | if(matchOnRequests == null)matchOnRequests = false;
263 | checkBox_matchOnRequests = new JCheckBox("Match On Requests", matchOnRequests);
264 | checkBox_matchOnRequests.addItemListener(e -> {
265 | //checkbox has been selected
266 | matchOnRequests = e.getStateChange() == ItemEvent.SELECTED;
267 | prefs.setSetting("Match On Requests", matchOnRequests);
268 | });
269 | panel_requestConfig_buttons.add(checkBox_matchOnRequests);
270 | panel_input.add(panel_requestConfig_buttons);
271 |
272 | // main split pane for response items (Results Tab)
273 | JSplitPane splitPane_results = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
274 | JTabbedPane tabbedPane_results = new JTabbedPane();
275 |
276 | // Table Results Tab - Responses
277 | table_results_responses = new ResultTable(resultsTableModel_responses, results_responses, contentController);
278 | table_results_responses.setDefaultColumnSizes();
279 | JScrollPane scrollPane_Responses = new JScrollPane(table_results_responses);
280 | scrollPane_Responses.setPreferredSize(new Dimension(0,330));
281 | tabbedPane_results.addTab("Matches On Responses", scrollPane_Responses);
282 |
283 | // Table of Results Tab - Requests
284 | table_results_requests = new ResultTable(resultsTableModel_requests, results_requests, contentController);
285 | table_results_requests.setDefaultColumnSizes();
286 | JScrollPane scrollPane_Requests = new JScrollPane(table_results_requests);
287 | scrollPane_Requests.setPreferredSize(new Dimension(0,330));
288 | tabbedPane_results.addTab("Matches On Requests", scrollPane_Requests);
289 | splitPane_results.setLeftComponent(tabbedPane_results);
290 |
291 | //Tabbed pane with request/response viewers (Results Tab)
292 | JTabbedPane tabs_requestResponses = new JTabbedPane();
293 | contentController.setRequestViewer(ResponsePatternMatcher.callbacks.createMessageEditor(responsePatternMatcher, false));
294 | contentController.setResponseViewer(ResponsePatternMatcher.callbacks.createMessageEditor(responsePatternMatcher, false));
295 | tabs_requestResponses.addTab("Request", contentController.getRequestViewer().getComponent());
296 | tabs_requestResponses.addTab("Response", contentController.getResponseViewer().getComponent());
297 | splitPane_results.setRightComponent(tabs_requestResponses);
298 |
299 | // Main Panel for more info (About Tab)
300 | JPanel panel_about = new JPanel();
301 | JScrollPane scrollPane_about = new JScrollPane(panel_about);
302 |
303 | JPanel panel_about_contents = new JPanel();
304 | panel_about_contents.setPreferredSize(new Dimension(300,250));
305 | panel_about_contents.setLayout(new BoxLayout(panel_about_contents, BoxLayout.PAGE_AXIS));
306 | panel_about_contents.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
307 |
308 | JPanel panel_about_author = new JPanel(new FlowLayout(FlowLayout.CENTER));
309 | JLabel label_author = new JLabel("Author");
310 | label_author.setHorizontalAlignment(SwingConstants.CENTER);
311 | label_author.setVerticalAlignment(SwingConstants.TOP);
312 | label_author.setFont(new Font(label_author.getName(), Font.PLAIN, 20));
313 | panel_about_author.add(label_author);
314 | panel_about_contents.add(panel_about_author);
315 |
316 | JPanel panel_about_name = new JPanel(new FlowLayout(FlowLayout.CENTER));
317 | JLabel label_name = new JLabel("Jack Jarvis");
318 | panel_about_name.add(label_name);
319 | panel_about_contents.add(panel_about_name);
320 |
321 | JPanel panel_about_tag = new JPanel(new FlowLayout(FlowLayout.CENTER));
322 | JLabel label_tag = new JLabel("Web Hacking Enthusiast");
323 | panel_about_tag.add(label_tag);
324 | panel_about_contents.add(panel_about_tag);
325 |
326 | JPanel panel_about_role = new JPanel(new FlowLayout(FlowLayout.CENTER));
327 | JLabel label_role = new JLabel("Security Consultant");
328 | panel_about_role.add(label_role);
329 | panel_about_contents.add(panel_about_role);
330 |
331 | JPanel panel_about_follow = new JPanel(new FlowLayout(FlowLayout.CENTER));
332 | JLabel label_follow = new JLabel("Find Me On");
333 | label_follow.setPreferredSize(new Dimension(500,50));
334 | label_follow.setHorizontalAlignment(SwingConstants.CENTER);
335 | label_follow.setVerticalAlignment(SwingConstants.BOTTOM);
336 | label_follow.setFont(new Font(label_author.getName(), Font.PLAIN, 20));
337 | panel_about_follow.add(label_follow);
338 | panel_about_contents.add(panel_about_follow);
339 |
340 | JPanel panel_github = new JPanel(new FlowLayout(FlowLayout.CENTER));
341 | String githubURL="https://github.com/JackJ07";
342 | JHyperlink githubLogo = new JHyperlink(new ImageIcon(getClass().getClassLoader().getResource("GitHub-Mark-32px.png")),githubURL, githubURL);
343 | if(githubLogo != null) {
344 | githubLogo.setPreferredSize(new Dimension(80, 50));
345 | panel_github.add(githubLogo);
346 | }
347 |
348 | panel_about_contents.add(panel_github);
349 |
350 | panel_about.add(panel_about_contents);
351 |
352 | //Setting up the tabs
353 | tabs_outer = new JTabbedPane();
354 | tabs_outer.addTab("Config", scrollPane_input);
355 | tabs_outer.addTab("Matches", splitPane_results);
356 | tabs_outer.addTab("About", scrollPane_about);
357 |
358 | // customize our UI components
359 | ResponsePatternMatcher.callbacks.customizeUiComponent(tabs_outer);
360 |
361 | // add the custom tab to Burp's UI
362 | ResponsePatternMatcher.callbacks.addSuiteTab(responsePatternMatcher);
363 |
364 | // register ourselves as an HTTP listener
365 | ResponsePatternMatcher.callbacks.registerHttpListener(responsePatternMatcher);
366 |
367 | //Setup configs for first time usage
368 | Boolean firstRun = prefs.getSetting("First Run");
369 | if(firstRun == null){
370 | restoreDefaults();
371 | prefs.setSetting("First Run", false);
372 | }
373 |
374 | ResponsePatternMatcher.stdout.println("Extension Loaded Successfully");
375 | });
376 | }
377 |
378 | private void restoreDefaults(){
379 | //Default payloads
380 | payloads.clear();
381 | payloads.add(new Payload("admin", false, true));
382 | payloads.add(new Payload("password", false, true));
383 | payloads.add(new Payload("passcode", false, true));
384 | payloads.add(new Payload("port.{0,7}\\d+", true, true));
385 | payloads.add(new Payload("sql", false, true));
386 | payloads.add(new Payload("