├── .gitignore
├── BappDescription.html
├── BappManifest.bmf
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── burp
├── BurpExtender.java
├── ConfigDialog.form
├── ConfigDialog.java
├── ConfigImport.form
├── ConfigImport.java
├── CookieJar.form
├── CookieJar.java
├── CookieSelector.form
├── CookieSelector.java
├── DeleteCookie.java
├── Issue.java
├── IssueChecker.java
├── IssueTableModel.java
├── JsonMarshaller.java
├── LoginSessionRule.json
├── MacroRunner.java
├── MacrosMarshaller.java
├── Menu.java
├── OptionsPanel.form
├── OptionsPanel.java
├── ReplicatorPanel.java
├── SessionRulesMarshaller.java
├── TraceItem.java
├── UseCookiesSessionRule.json
├── Utils.java
└── your-logo-here.png
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/*
2 | .gradle/
3 | build/
4 | .DS_Store
5 | out/*
6 | .idea/
7 | .settings
8 |
--------------------------------------------------------------------------------
/BappDescription.html:
--------------------------------------------------------------------------------
1 |
Replicator helps developers to reproduce issues discovered by pen testers. The pen tester produces a Replicator file
2 | which contains the findings in the report. Each finding includes a request, associated session rules or macros, and
3 | logic to detect presence of the vulnerability. The tester sends the Replicator file to the client alongside the report.
4 | Developers can then open the file within Burp and replicate the issues. When vulnerabilities have been fixed, Replicator
5 | provides confirmation that the attack vector used in the pen test is now blocked. A retest is still recommended, in
6 | case alternative attack vectors remain exploitable.
7 |
8 | Developer workflow
9 |
10 |
11 | - Load the Replicator file.
12 | - If you want to test a different application instance (perhaps a development instance) edit the Hosts section to point to the instance.
13 | - Click Test all. All the vulnerabilities should get status Vulnerable. If any do not, you need to investigate why. You can use the Start Trace button to generate a trace file that may help the pen tester diagnose the issue.
14 | - Save the file. This is important for confirming fixes later.
15 | - Identify an issue to work on. Consult the pen test report for a full description.
16 | - When the application has been updated, click Test to see if it's still vulnerable.
17 |
18 |
19 | Issues can have the following status:
20 |
21 |
22 | - Vulnerable - The application is still vulnerable.
23 | - Resolved (tentative) - The vulnerability appears to be resolved. Replicator cannot confirm this with certainty; a retest is required for that.
24 | - Unable to replicate - It wasn't possible to determine if the application is vulnerable. This may be because credentials are invalid. Some fixes (e.g. removing the whole page) can cause this.
25 |
26 |
27 |
28 | Tester workflow
29 |
30 |
31 | - Put Replicator in Tester mode using the menu.
32 | - It is recommended to add issues to Replicator when they are discovered. This will assist with report writing.
33 |
34 | - Issues detected by Scanner can be sent to Replicator, using the context menu.
35 | - Other issues can be sent from the relevant tool to Replicator. You need to complete the issue details, including grep expression.
36 | - If any issues require a login session, you must create a login macro, and select this in Replicator.
37 | - If an issue is more complex than a single request/response, use macros and session handling rules. Replicator will automatically detect rules and macros that apply to a request and include them in the Replicator file.
38 |
39 |
40 | - When the report is complete, verify the Replicator file, to ensure it will work in a fresh environment where current tokens are no longer valid:
41 |
42 | - Select all issues, and click Scrub cookies... Remove any session cookies from the requests.
43 | - Click Empty cookie jar
44 | - Select all the issues and click Clear status
45 | - Click Test all and verify that all issues report as vulnerable.
46 |
47 |
48 | - If some particular Burp configuration is needed, use the Config... button to include this in the Replicator file. On the Configuration dialog you may want to use the Import... button to assist you.
49 | - Clear the status before sending the file. Select all the issues, click Clear status, and save the file.
50 | - Send the Replicator file to the client, using the same delivery mechanism as the report.
51 |
52 |
--------------------------------------------------------------------------------
/BappManifest.bmf:
--------------------------------------------------------------------------------
1 | Uuid: 56cf924977874104ac35e52962a9a553
2 | ExtensionType: 1
3 | Name: Replicator
4 | RepoName: replicator
5 | ScreenVersion: 1.0.3
6 | SerialVersion: 7
7 | MinPlatformVersion: 0
8 | ProOnly: False
9 | Author: Paul Johnston, PortSwigger
10 | ShortDescription: Helps developers replicate findings discovered in pen tests.
11 | EntryPoint: build/libs/replicator.jar
12 | BuildCommand: ./gradlew jar
13 | SupportedProducts: Pro, Community
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Replicator
2 |
3 | Replicator is a Burp extension that helps developers to reproduce issues discovered by pen testers. The pen tester produces a Replicator file
4 | which contains the findings in the report. Each finding includes a request, associated session rules or macros, and
5 | logic to detect presence of the vulnerability. The tester sends the Replicator file to the client alongside the report.
6 | Developers can then open the file within Burp and replicate the issues. When vulnerabilities have been fixed, Replicator
7 | provides confirmation that the attack vector used in the pen test is now blocked. A retest is still recommended, in
8 | case alternative attack vectors remain exploitable.
9 |
10 | For further details, look in BappDescription.html
11 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | java {
4 | sourceCompatibility = JavaVersion.VERSION_1_8
5 | targetCompatibility = JavaVersion.VERSION_1_8
6 | }
7 |
8 | repositories {
9 | mavenCentral()
10 | }
11 |
12 | dependencies {
13 | compileOnly 'net.portswigger.burp.extender:burp-extender-api:1.7.22'
14 | implementation 'org.json:json:20231013'
15 | }
16 |
17 | sourceSets {
18 | main {
19 | java {
20 | srcDir 'src'
21 | }
22 | resources {
23 | srcDir 'src'
24 | }
25 | }
26 | }
27 |
28 | jar {
29 | archiveBaseName = project.name
30 | from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
31 | }
32 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/replicator/f02cd93c992dd27412523ccfc730a3b0169a8198/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
5 | networkTimeout=10000
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 | # Collect all arguments for the java command;
201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202 | # shell script including quotes and variable substitutions, so put them in
203 | # double quotes to make sure that they get re-expanded; and
204 | # * put everything else in single quotes, so that it's not re-expanded.
205 |
206 | set -- \
207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
208 | -classpath "$CLASSPATH" \
209 | org.gradle.wrapper.GradleWrapperMain \
210 | "$@"
211 |
212 | # Stop when "xargs" is not available.
213 | if ! command -v xargs >/dev/null 2>&1
214 | then
215 | die "xargs is not available"
216 | fi
217 |
218 | # Use "xargs" to parse quoted args.
219 | #
220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
221 | #
222 | # In Bash we could simply go:
223 | #
224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
225 | # set -- "${ARGS[@]}" "$@"
226 | #
227 | # but POSIX shell has neither arrays nor command substitution, so instead we
228 | # post-process each arg (as a line of input to sed) to backslash-escape any
229 | # character that might be a shell metacharacter, then use eval to reverse
230 | # that process (while maintaining the separation between arguments), and wrap
231 | # the whole thing up as a single "set" statement.
232 | #
233 | # This will of course break if any of these variables contains a newline or
234 | # an unmatched quote.
235 | #
236 |
237 | eval "set -- $(
238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
239 | xargs -n1 |
240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
241 | tr '\n' ' '
242 | )" '"$@"'
243 |
244 | exec "$JAVACMD" "$@"
245 |
--------------------------------------------------------------------------------
/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 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'replicator'
2 |
--------------------------------------------------------------------------------
/src/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 |
4 | import javax.swing.*;
5 | import java.awt.*;
6 | import java.awt.event.*;
7 | import java.util.Arrays;
8 | import java.util.List;
9 | import java.util.regex.Pattern;
10 |
11 | import static java.awt.EventQueue.invokeLater;
12 |
13 | public class BurpExtender implements IBurpExtender, IExtensionStateListener, ITab, IContextMenuFactory, IHttpListener
14 | {
15 | static final String name = "Replicator";
16 | static final byte TESTER_VIEW = 1;
17 | static final byte DEVELOPER_VIEW = 0;
18 |
19 | ReplicatorPanel replicatorPanel;
20 | static IBurpExtenderCallbacks callbacks;
21 | JMenuBar burpMenuBar;
22 | burp.Menu replicatorMenu;
23 | byte viewType = DEVELOPER_VIEW;
24 | IssueChecker issueChecker;
25 |
26 | @Override
27 | public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
28 | {
29 | BurpExtender.callbacks = callbacks;
30 | replicatorPanel = new ReplicatorPanel(this);
31 | callbacks.setExtensionName(BurpExtender.name);
32 | callbacks.registerContextMenuFactory(this);
33 | callbacks.registerExtensionStateListener(this);
34 | callbacks.addSuiteTab(this);
35 | callbacks.registerHttpListener(this);
36 |
37 | try
38 | {
39 | viewType = (byte) Integer.parseInt(callbacks.loadExtensionSetting("viewType"));
40 | }
41 | catch(Exception ex)
42 | {
43 | // ignore
44 | }
45 |
46 | replicatorPanel.addComponentListener(new ComponentAdapter() {
47 | @Override
48 | public void componentResized(ComponentEvent e) {
49 | if (replicatorPanel.getWidth() > 0)
50 | {
51 | replicatorPanel.removeComponentListener(this);
52 |
53 | invokeLater(() -> {
54 | replicatorMenu = new Menu(BurpExtender.this);
55 | replicatorMenu.setViewType(viewType);
56 |
57 | burpMenuBar = getBurpFrame().getJMenuBar();
58 | burpMenuBar.add(replicatorMenu);
59 | burpMenuBar.validate();
60 |
61 | setViewType(viewType);
62 | });
63 | }
64 | }
65 | });
66 | }
67 |
68 | @Override
69 | public void extensionUnloaded() {
70 | if(issueChecker != null)
71 | {
72 | issueChecker.terminating = true;
73 | }
74 |
75 | replicatorPanel.removeChangeListener();
76 |
77 | invokeLater(() -> {
78 | burpMenuBar.remove(replicatorMenu);
79 | burpMenuBar.repaint();
80 | });
81 | }
82 |
83 | @Override
84 | public String getTabCaption() {
85 | return BurpExtender.name;
86 | }
87 |
88 | @Override
89 | public Component getUiComponent() {
90 | return replicatorPanel;
91 | }
92 |
93 | @Override
94 | public List createMenuItems(IContextMenuInvocation invocation)
95 | {
96 | if(viewType == DEVELOPER_VIEW)
97 | {
98 | return null;
99 | }
100 | else
101 | {
102 | JMenuItem menuItem = new JMenuItem("Send to Replicator");
103 | menuItem.addActionListener(new ContextMenuListener(invocation));
104 | return Arrays.asList(menuItem);
105 | }
106 | }
107 |
108 | @Override
109 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)
110 | {
111 | if(replicatorPanel.trace != null)
112 | {
113 | replicatorPanel.trace.add(new TraceItem(messageInfo, messageIsRequest));
114 | }
115 |
116 | if (!messageIsRequest)
117 | {
118 | return;
119 | }
120 |
121 | byte[] request = messageInfo.getRequest();
122 | IRequestInfo requestInfo = callbacks.getHelpers().analyzeRequest(request);
123 | byte[] body = Arrays.copyOfRange(request, requestInfo.getBodyOffset(), request.length);
124 | List headers = requestInfo.getHeaders();
125 | for (int i = 0; i < headers.size(); i++)
126 | {
127 | if (headers.get(i).startsWith(Issue.IDENTITY_HEADER))
128 | {
129 | String uniqueId = headers.get(i).substring(Issue.IDENTITY_HEADER.length());
130 | headers.remove(i);
131 | request = BurpExtender.callbacks.getHelpers().buildHttpMessage(headers, body);
132 | messageInfo.setRequest(request);
133 | for(Issue issue : replicatorPanel.issueTableModel.issues)
134 | {
135 | if(uniqueId.equals(issue.uniqueId))
136 | {
137 | issue.request = request;
138 | if(replicatorPanel.optionsPanel.currentIssue == issue)
139 | {
140 | replicatorPanel.requestEditor.setMessage(request, true);
141 | }
142 | }
143 | }
144 | break;
145 | }
146 | }
147 | }
148 |
149 | class ContextMenuListener implements ActionListener
150 | {
151 | IContextMenuInvocation invocation;
152 | ContextMenuListener(IContextMenuInvocation invocation)
153 | {
154 | this.invocation = invocation;
155 | }
156 |
157 | @Override
158 | public void actionPerformed(ActionEvent e)
159 | {
160 | if(invocation.getSelectedMessages() != null)
161 | {
162 | replicatorPanel.acceptSendTo(invocation.getSelectedMessages());
163 | }
164 | if(invocation.getSelectedIssues() != null)
165 | {
166 | replicatorPanel.acceptSendTo(invocation.getSelectedIssues());
167 | }
168 | }
169 | }
170 |
171 | static JFrame getBurpFrame()
172 | {
173 | for(Frame f : Frame.getFrames())
174 | {
175 | if(f.isVisible() && f.getTitle().startsWith(("Burp Suite")))
176 | {
177 | return (JFrame) f;
178 | }
179 | }
180 | return null;
181 | }
182 |
183 | void setViewType(byte viewType)
184 | {
185 | this.viewType = viewType;
186 | replicatorPanel.setViewType(viewType);
187 | }
188 |
189 | static String escapeRegex(String regex)
190 | {
191 | Pattern regexMetaChars = Pattern.compile("[\\\\\\[\\](){}.\\^$?*+|]");
192 | return regexMetaChars.matcher(regex).find() ? Pattern.quote(regex) : regex;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/burp/ConfigDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
114 |
--------------------------------------------------------------------------------
/src/burp/ConfigDialog.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 | import org.json.JSONTokener;
6 |
7 | import javax.swing.*;
8 |
9 | /**
10 | *
11 | * @author paul
12 | */
13 | public class ConfigDialog extends javax.swing.JDialog {
14 | ReplicatorPanel replicatorPanel;
15 |
16 | /**
17 | * Creates new form ConfigDialog
18 | */
19 | public ConfigDialog(java.awt.Frame parent, ReplicatorPanel replicatorPanel) {
20 | super(parent, true);
21 | this.replicatorPanel = replicatorPanel;
22 | initComponents();
23 | SwingUtilities.invokeLater(new Runnable()
24 | {
25 | @Override
26 | public void run()
27 | {
28 | jTextArea1.setText(replicatorPanel.config);
29 | }
30 | });
31 | }
32 |
33 | /**
34 | * This method is called from within the constructor to initialize the form.
35 | * WARNING: Do NOT modify this code. The content of this method is always
36 | * regenerated by the Form Editor.
37 | */
38 | @SuppressWarnings("unchecked")
39 | // //GEN-BEGIN:initComponents
40 | private void initComponents() {
41 |
42 | jLabel1 = new javax.swing.JLabel();
43 | jScrollPane1 = new javax.swing.JScrollPane();
44 | jTextArea1 = new javax.swing.JTextArea();
45 | jButtonCancel = new javax.swing.JButton();
46 | jButtonSave = new javax.swing.JButton();
47 | jButtonImport = new javax.swing.JButton();
48 |
49 | setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
50 | setTitle("Configuration");
51 |
52 | jLabel1.setText("Specify Burp JSON configuration here. It will be applied to users' Burp projects when opening this Replicator file.");
53 |
54 | jTextArea1.setColumns(20);
55 | jTextArea1.setRows(5);
56 | jScrollPane1.setViewportView(jTextArea1);
57 |
58 | jButtonCancel.setText("Cancel");
59 | jButtonCancel.addActionListener(new java.awt.event.ActionListener() {
60 | public void actionPerformed(java.awt.event.ActionEvent evt) {
61 | jButtonCancelActionPerformed(evt);
62 | }
63 | });
64 |
65 | jButtonSave.setText("Save");
66 | jButtonSave.addActionListener(new java.awt.event.ActionListener() {
67 | public void actionPerformed(java.awt.event.ActionEvent evt) {
68 | jButtonSaveActionPerformed(evt);
69 | }
70 | });
71 |
72 | jButtonImport.setText("Import...");
73 | jButtonImport.addActionListener(new java.awt.event.ActionListener() {
74 | public void actionPerformed(java.awt.event.ActionEvent evt) {
75 | jButtonImportActionPerformed(evt);
76 | }
77 | });
78 |
79 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
80 | getContentPane().setLayout(layout);
81 | layout.setHorizontalGroup(
82 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
83 | .addGroup(layout.createSequentialGroup()
84 | .addContainerGap()
85 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
86 | .addComponent(jScrollPane1)
87 | .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
88 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
89 | .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
90 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
91 | .addComponent(jButtonImport)
92 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
93 | .addComponent(jButtonSave)
94 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
95 | .addComponent(jButtonCancel)
96 | .addContainerGap())
97 | );
98 | layout.setVerticalGroup(
99 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
100 | .addGroup(layout.createSequentialGroup()
101 | .addGap(12, 12, 12)
102 | .addComponent(jLabel1)
103 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
104 | .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 487, javax.swing.GroupLayout.PREFERRED_SIZE)
105 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
106 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
107 | .addComponent(jButtonCancel)
108 | .addComponent(jButtonSave)
109 | .addComponent(jButtonImport))
110 | .addContainerGap(12, Short.MAX_VALUE))
111 | );
112 |
113 | pack();
114 | }// //GEN-END:initComponents
115 |
116 | private void jButtonSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonSaveActionPerformed
117 | try
118 | {
119 | new JSONObject(new JSONTokener(jTextArea1.getText()));
120 | replicatorPanel.config = jTextArea1.getText();
121 | dispose();
122 | }
123 | catch(Exception ex)
124 | {
125 | JOptionPane.showMessageDialog(this, "The configuration must be valid JSON", BurpExtender.name, JOptionPane.WARNING_MESSAGE);
126 | }
127 | }//GEN-LAST:event_jButtonSaveActionPerformed
128 |
129 | private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCancelActionPerformed
130 | dispose();
131 | }//GEN-LAST:event_jButtonCancelActionPerformed
132 |
133 | private void jButtonImportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonImportActionPerformed
134 | try
135 | {
136 | new JSONObject(new JSONTokener(jTextArea1.getText()));
137 | }
138 | catch (JSONException e)
139 | {
140 | JOptionPane.showMessageDialog(this, "The configuration must be valid JSON", BurpExtender.name, JOptionPane.WARNING_MESSAGE);
141 | return;
142 | }
143 | JDialog dialog = new ConfigImport(this);
144 | dialog.setLocationRelativeTo(replicatorPanel);
145 | dialog.setVisible(true);
146 | }//GEN-LAST:event_jButtonImportActionPerformed
147 |
148 | // Variables declaration - do not modify//GEN-BEGIN:variables
149 | private javax.swing.JButton jButtonCancel;
150 | private javax.swing.JButton jButtonImport;
151 | private javax.swing.JButton jButtonSave;
152 | private javax.swing.JLabel jLabel1;
153 | private javax.swing.JScrollPane jScrollPane1;
154 | protected javax.swing.JTextArea jTextArea1;
155 | // End of variables declaration//GEN-END:variables
156 | }
157 |
--------------------------------------------------------------------------------
/src/burp/ConfigImport.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
102 |
--------------------------------------------------------------------------------
/src/burp/ConfigImport.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 | import org.json.JSONTokener;
6 |
7 | import javax.swing.*;
8 | import javax.swing.tree.DefaultMutableTreeNode;
9 | import javax.swing.tree.DefaultTreeModel;
10 | import javax.swing.tree.MutableTreeNode;
11 | import javax.swing.tree.TreePath;
12 | import java.io.PrintWriter;
13 | import java.util.Iterator;
14 |
15 | /**
16 | *
17 | * @author paul
18 | */
19 | public class ConfigImport extends javax.swing.JDialog {
20 | ConfigDialog configDialog;
21 | JSONObject jsonBurp;
22 |
23 | /**
24 | * Creates new form ConfigImport
25 | */
26 | public ConfigImport(ConfigDialog configDialog) {
27 | super(configDialog, true);
28 | this.configDialog = configDialog;
29 | initComponents();
30 | SwingUtilities.invokeLater(new Runnable()
31 | {
32 | @Override
33 | public void run()
34 | {
35 | try
36 | {
37 | String jsonString = BurpExtender.callbacks.saveConfigAsJson("");
38 | jsonBurp = new JSONObject(new JSONTokener(jsonString));
39 | DefaultTreeModel dtm = (DefaultTreeModel) jTree1.getModel();
40 | fillTree(jsonBurp, dtm, (MutableTreeNode) dtm.getRoot(), 1);
41 | dtm.reload();
42 | } catch (JSONException e)
43 | {
44 | e.printStackTrace();
45 | }
46 |
47 |
48 | }
49 | });
50 | }
51 |
52 | void fillTree(JSONObject json, DefaultTreeModel dtm, MutableTreeNode parent, int level) throws JSONException
53 | {
54 | for(Iterator iterator = json.keys(); iterator.hasNext();)
55 | {
56 | String key = iterator.next();
57 | MutableTreeNode node = new DefaultMutableTreeNode(key);
58 | dtm.insertNodeInto(node, parent, parent.getChildCount());
59 | if(json.get(key) instanceof JSONObject)
60 | {
61 | if(level == 1 && key.equals("project_options"))
62 | {
63 | // Don't increment level; display tree one level deeper
64 | fillTree(json.getJSONObject(key), dtm, node, level);
65 | }
66 | else if(level <= 1)
67 | {
68 | fillTree(json.getJSONObject(key), dtm, node, level + 1);
69 | }
70 | }
71 | }
72 | }
73 |
74 | /**
75 | * This method is called from within the constructor to initialize the form.
76 | * WARNING: Do NOT modify this code. The content of this method is always
77 | * regenerated by the Form Editor.
78 | */
79 | @SuppressWarnings("unchecked")
80 | // //GEN-BEGIN:initComponents
81 | private void initComponents() {
82 |
83 | jScrollPane1 = new javax.swing.JScrollPane();
84 | jTree1 = new javax.swing.JTree();
85 | jLabel1 = new javax.swing.JLabel();
86 | jButtonCancel = new javax.swing.JButton();
87 | jButtonOk = new javax.swing.JButton();
88 |
89 | setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
90 | setTitle("Import Config");
91 |
92 | javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("root");
93 | jTree1.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
94 | jTree1.setRootVisible(false);
95 | jScrollPane1.setViewportView(jTree1);
96 |
97 | jLabel1.setText("Select which config to import from your current config");
98 |
99 | jButtonCancel.setText("Cancel");
100 | jButtonCancel.addActionListener(new java.awt.event.ActionListener() {
101 | public void actionPerformed(java.awt.event.ActionEvent evt) {
102 | jButtonCancelActionPerformed(evt);
103 | }
104 | });
105 |
106 | jButtonOk.setText("Ok");
107 | jButtonOk.addActionListener(new java.awt.event.ActionListener() {
108 | public void actionPerformed(java.awt.event.ActionEvent evt) {
109 | jButtonOkActionPerformed(evt);
110 | }
111 | });
112 |
113 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
114 | getContentPane().setLayout(layout);
115 | layout.setHorizontalGroup(
116 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
117 | .addGroup(layout.createSequentialGroup()
118 | .addContainerGap()
119 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
120 | .addComponent(jScrollPane1)
121 | .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
122 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
123 | .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
124 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
125 | .addComponent(jButtonOk)
126 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
127 | .addComponent(jButtonCancel)
128 | .addContainerGap())
129 | );
130 | layout.setVerticalGroup(
131 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
132 | .addGroup(layout.createSequentialGroup()
133 | .addContainerGap()
134 | .addComponent(jLabel1)
135 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
136 | .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 257, javax.swing.GroupLayout.PREFERRED_SIZE)
137 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
138 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
139 | .addComponent(jButtonCancel)
140 | .addComponent(jButtonOk))
141 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
142 | );
143 |
144 | pack();
145 | }// //GEN-END:initComponents
146 |
147 | private void jButtonOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonOkActionPerformed
148 | try
149 | {
150 | if(jTree1.getSelectionPaths() == null)
151 | {
152 | dispose();
153 | return;
154 | }
155 | JSONObject jsonReplicator = new JSONObject(new JSONTokener(configDialog.jTextArea1.getText()));
156 |
157 | for (TreePath path : jTree1.getSelectionPaths())
158 | {
159 | JSONObject nodeBurp = jsonBurp;
160 |
161 | JSONObject nodeReplicator = jsonReplicator;
162 | // skip root and leaf
163 | for (int i = 1; i < path.getPathCount() - 1; i++)
164 | {
165 | String key = path.getPathComponent(i).toString();
166 | nodeBurp = nodeBurp.getJSONObject(key);
167 | if(!nodeReplicator.has(key) || !(nodeReplicator.get(key) instanceof JSONObject))
168 | {
169 | nodeReplicator.put(key, new JSONObject());
170 | }
171 | nodeReplicator = nodeReplicator.getJSONObject(key);
172 | }
173 |
174 | String key = path.getPathComponent(path.getPathCount() - 1).toString();
175 | nodeReplicator.put(key, nodeBurp.get(key));
176 | }
177 | configDialog.jTextArea1.setText(jsonReplicator.toString(4));
178 | dispose();
179 | }
180 | catch (Exception e)
181 | {
182 | BurpExtender.callbacks.printError(e.toString());
183 | e.printStackTrace(new PrintWriter(BurpExtender.callbacks.getStderr()));
184 | }
185 | }//GEN-LAST:event_jButtonOkActionPerformed
186 |
187 | private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCancelActionPerformed
188 | dispose();
189 | }//GEN-LAST:event_jButtonCancelActionPerformed
190 |
191 | // Variables declaration - do not modify//GEN-BEGIN:variables
192 | private javax.swing.JButton jButtonCancel;
193 | private javax.swing.JButton jButtonOk;
194 | private javax.swing.JLabel jLabel1;
195 | private javax.swing.JScrollPane jScrollPane1;
196 | private javax.swing.JTree jTree1;
197 | // End of variables declaration//GEN-END:variables
198 | }
199 |
--------------------------------------------------------------------------------
/src/burp/CookieJar.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
130 |
--------------------------------------------------------------------------------
/src/burp/CookieJar.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import javax.swing.table.DefaultTableModel;
5 | import java.util.ArrayList;
6 | import java.util.HashSet;
7 | import java.util.List;
8 | import java.util.Set;
9 |
10 | /**
11 | *
12 | * @author paul
13 | */
14 | public class CookieJar extends javax.swing.JDialog
15 | {
16 | ReplicatorPanel replicatorPanel;
17 | List cookies;
18 | DefaultTableModel tableModel;
19 | /**
20 | * Creates new form CookieJar
21 | */
22 | public CookieJar(java.awt.Frame parent, ReplicatorPanel replicatorPanel) {
23 | super(parent, true);
24 | this.replicatorPanel = replicatorPanel;
25 | initComponents();
26 |
27 | SwingUtilities.invokeLater(new Runnable()
28 | {
29 | @Override
30 | public void run()
31 | {
32 | BurpExtender.callbacks.customizeUiComponent(CookieJar.this);
33 | loadCookies();
34 | }
35 | });
36 | }
37 |
38 | void loadCookies()
39 | {
40 | tableModel = (DefaultTableModel) jTable1.getModel();
41 | Set hosts = new HashSet<>();
42 | for(Issue issue : replicatorPanel.issueTableModel.issues)
43 | {
44 | hosts.add(issue.url.getHost());
45 | }
46 | cookies = new ArrayList<>();
47 | for(ICookie cookie : BurpExtender.callbacks.getCookieJarContents())
48 | {
49 | if(hosts.contains(cookie.getDomain()))
50 | {
51 | cookies.add(cookie);
52 | String expiration = cookie.getExpiration() == null ? "" : cookie.getExpiration().toString();
53 | tableModel.addRow(new String[]{cookie.getDomain(), cookie.getPath(), cookie.getName(), cookie.getValue(), expiration});
54 | }
55 | }
56 | }
57 |
58 | /**
59 | * This method is called from within the constructor to initialize the form.
60 | * WARNING: Do NOT modify this code. The content of this method is always
61 | * regenerated by the Form Editor.
62 | */
63 | @SuppressWarnings("unchecked")
64 | // //GEN-BEGIN:initComponents
65 | private void initComponents() {
66 |
67 | jScrollPane1 = new javax.swing.JScrollPane();
68 | jTable1 = new javax.swing.JTable();
69 | jButtonEmpty = new javax.swing.JButton();
70 | jButtonClose = new javax.swing.JButton();
71 |
72 | setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
73 | setTitle("Cookie jar viewer");
74 | setResizable(false);
75 |
76 | jTable1.setModel(new javax.swing.table.DefaultTableModel(
77 | new Object [][] {
78 |
79 | },
80 | new String [] {
81 | "Domain", "Path", "Name", "Value", "Expires"
82 | }
83 | ) {
84 | Class[] types = new Class [] {
85 | java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class
86 | };
87 | boolean[] canEdit = new boolean [] {
88 | false, false, true, false, false
89 | };
90 |
91 | public Class getColumnClass(int columnIndex) {
92 | return types [columnIndex];
93 | }
94 |
95 | public boolean isCellEditable(int rowIndex, int columnIndex) {
96 | return canEdit [columnIndex];
97 | }
98 | });
99 | jTable1.getTableHeader().setReorderingAllowed(false);
100 | jScrollPane1.setViewportView(jTable1);
101 | if (jTable1.getColumnModel().getColumnCount() > 0) {
102 | jTable1.getColumnModel().getColumn(0).setPreferredWidth(100);
103 | jTable1.getColumnModel().getColumn(1).setPreferredWidth(100);
104 | jTable1.getColumnModel().getColumn(2).setPreferredWidth(100);
105 | jTable1.getColumnModel().getColumn(3).setPreferredWidth(300);
106 | jTable1.getColumnModel().getColumn(4).setPreferredWidth(100);
107 | }
108 |
109 | jButtonEmpty.setText("Empty cookie jar");
110 | jButtonEmpty.addActionListener(new java.awt.event.ActionListener() {
111 | public void actionPerformed(java.awt.event.ActionEvent evt) {
112 | jButtonEmptyActionPerformed(evt);
113 | }
114 | });
115 |
116 | jButtonClose.setText("Close");
117 | jButtonClose.addActionListener(new java.awt.event.ActionListener() {
118 | public void actionPerformed(java.awt.event.ActionEvent evt) {
119 | jButtonCloseActionPerformed(evt);
120 | }
121 | });
122 |
123 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
124 | getContentPane().setLayout(layout);
125 | layout.setHorizontalGroup(
126 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
127 | .addGroup(layout.createSequentialGroup()
128 | .addContainerGap()
129 | .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 566, javax.swing.GroupLayout.PREFERRED_SIZE)
130 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
131 | .addComponent(jButtonEmpty)
132 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
133 | .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
134 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
135 | .addComponent(jButtonClose)
136 | .addContainerGap())
137 | );
138 | layout.setVerticalGroup(
139 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
140 | .addGroup(layout.createSequentialGroup()
141 | .addContainerGap()
142 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
143 | .addComponent(jButtonEmpty)
144 | .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 275, javax.swing.GroupLayout.PREFERRED_SIZE))
145 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
146 | .addComponent(jButtonClose)
147 | .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
148 | );
149 |
150 | pack();
151 | }// //GEN-END:initComponents
152 |
153 | private void jButtonCloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCloseActionPerformed
154 | dispose();
155 | }//GEN-LAST:event_jButtonCloseActionPerformed
156 |
157 | private void jButtonEmptyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonEmptyActionPerformed
158 | int confirm = JOptionPane.showConfirmDialog(this, "Are you sure you want to empty the cookie jar for these hosts?", BurpExtender.name, JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE);
159 | if(confirm == JOptionPane.NO_OPTION)
160 | {
161 | return;
162 | }
163 | replicatorPanel.loggedIn = false;
164 | for(ICookie cookie : cookies)
165 | {
166 | ICookie deleteCookie = new DeleteCookie(cookie.getDomain(), cookie.getName(), cookie.getPath());
167 | BurpExtender.callbacks.updateCookieJar(deleteCookie);
168 | }
169 | cookies = new ArrayList<>();
170 | while(tableModel.getRowCount() > 0)
171 | {
172 | tableModel.removeRow(0);
173 | }
174 | }//GEN-LAST:event_jButtonEmptyActionPerformed
175 |
176 | // Variables declaration - do not modify//GEN-BEGIN:variables
177 | private javax.swing.JButton jButtonClose;
178 | private javax.swing.JButton jButtonEmpty;
179 | private javax.swing.JScrollPane jScrollPane1;
180 | private javax.swing.JTable jTable1;
181 | // End of variables declaration//GEN-END:variables
182 | }
183 |
--------------------------------------------------------------------------------
/src/burp/CookieSelector.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
110 |
--------------------------------------------------------------------------------
/src/burp/CookieSelector.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 |
5 | /**
6 | *
7 | * @author paul
8 | */
9 | public class CookieSelector extends javax.swing.JDialog {
10 | DefaultListModel cookieListModel = new DefaultListModel<>();
11 | ReplicatorPanel replicatorPanel;
12 |
13 | /**
14 | * Creates new form CookieSelector
15 | */
16 | public CookieSelector(java.awt.Frame parent, ReplicatorPanel replicatorPanel)
17 | {
18 | super(parent, true);
19 | this.replicatorPanel = replicatorPanel;
20 | initComponents();
21 | for(String cookieName : replicatorPanel.issueTableModel.getCookiesNames())
22 | {
23 | cookieListModel.addElement(cookieName);
24 | }
25 | }
26 |
27 | /**
28 | * This method is called from within the constructor to initialize the form.
29 | * WARNING: Do NOT modify this code. The content of this method is always
30 | * regenerated by the Form Editor.
31 | */
32 | @SuppressWarnings("unchecked")
33 | // //GEN-BEGIN:initComponents
34 | private void initComponents() {
35 |
36 | jLabel1 = new javax.swing.JLabel();
37 | jScrollPane1 = new javax.swing.JScrollPane();
38 | jListCookies = new javax.swing.JList<>();
39 | jButtonOk = new javax.swing.JButton();
40 | jButtonCancel = new javax.swing.JButton();
41 |
42 | setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
43 | setTitle("Select Cookies to Scrub");
44 | setAutoRequestFocus(false);
45 | setResizable(false);
46 |
47 | jLabel1.setText("Which cookies would you like to scrub from the selected requests?");
48 |
49 | jListCookies.setModel(cookieListModel);
50 | jScrollPane1.setViewportView(jListCookies);
51 |
52 | jButtonOk.setText("Ok");
53 | jButtonOk.addActionListener(new java.awt.event.ActionListener() {
54 | public void actionPerformed(java.awt.event.ActionEvent evt) {
55 | jButtonOkActionPerformed(evt);
56 | }
57 | });
58 |
59 | jButtonCancel.setText("Cancel");
60 | jButtonCancel.addActionListener(new java.awt.event.ActionListener() {
61 | public void actionPerformed(java.awt.event.ActionEvent evt) {
62 | jButtonCancelActionPerformed(evt);
63 | }
64 | });
65 |
66 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
67 | getContentPane().setLayout(layout);
68 | layout.setHorizontalGroup(
69 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
70 | .addGroup(layout.createSequentialGroup()
71 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
72 | .addGroup(layout.createSequentialGroup()
73 | .addGap(107, 107, 107)
74 | .addComponent(jButtonOk)
75 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
76 | .addComponent(jButtonCancel))
77 | .addGroup(layout.createSequentialGroup()
78 | .addGap(29, 29, 29)
79 | .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 190, javax.swing.GroupLayout.PREFERRED_SIZE))
80 | .addGroup(layout.createSequentialGroup()
81 | .addGap(16, 16, 16)
82 | .addComponent(jLabel1)))
83 | .addContainerGap(24, Short.MAX_VALUE))
84 | );
85 | layout.setVerticalGroup(
86 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
87 | .addGroup(layout.createSequentialGroup()
88 | .addGap(16, 16, 16)
89 | .addComponent(jLabel1)
90 | .addGap(18, 18, 18)
91 | .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
92 | .addGap(18, 18, 18)
93 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
94 | .addComponent(jButtonOk)
95 | .addComponent(jButtonCancel))
96 | .addContainerGap(56, Short.MAX_VALUE))
97 | );
98 |
99 | pack();
100 | }// //GEN-END:initComponents
101 |
102 | private void jButtonOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonOkActionPerformed
103 | replicatorPanel.issueTableModel.scrubCookies(jListCookies.getSelectedValuesList());
104 | replicatorPanel.requestEditor.setMessage(replicatorPanel.optionsPanel.currentIssue.request, true);
105 | dispose();
106 | }//GEN-LAST:event_jButtonOkActionPerformed
107 |
108 | private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCancelActionPerformed
109 | dispose();
110 | }//GEN-LAST:event_jButtonCancelActionPerformed
111 |
112 | // Variables declaration - do not modify//GEN-BEGIN:variables
113 | private javax.swing.JButton jButtonCancel;
114 | private javax.swing.JButton jButtonOk;
115 | private javax.swing.JLabel jLabel1;
116 | private javax.swing.JList jListCookies;
117 | private javax.swing.JScrollPane jScrollPane1;
118 | // End of variables declaration//GEN-END:variables
119 | }
120 |
--------------------------------------------------------------------------------
/src/burp/DeleteCookie.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.Date;
4 |
5 |
6 | public class DeleteCookie implements ICookie
7 | {
8 | String domain;
9 | String name;
10 | String path;
11 |
12 | public DeleteCookie(String domain, String name, String path)
13 | {
14 | this.domain = domain;
15 | this.name = name;
16 | this.path = path;
17 | }
18 |
19 | @Override
20 | public String getDomain()
21 | {
22 | return domain;
23 | }
24 |
25 | @Override
26 | public String getPath()
27 | {
28 | return path;
29 | }
30 |
31 | @Override
32 | public Date getExpiration()
33 | {
34 | return null;
35 | }
36 |
37 | @Override
38 | public String getName()
39 | {
40 | return name;
41 | }
42 |
43 | @Override
44 | public String getValue()
45 | {
46 | return null;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/burp/Issue.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import java.io.PrintStream;
5 | import java.io.PrintWriter;
6 | import java.net.MalformedURLException;
7 | import java.net.URL;
8 | import java.util.*;
9 |
10 |
11 | public class Issue
12 | {
13 | static final String IDENTITY_HEADER = "Replicator-identity: ";
14 |
15 | String id;
16 | String issue;
17 | String path;
18 | String parameter;
19 | byte status = STATUS_NOT_TESTED;
20 | boolean everVulnerable = false;
21 | String notes;
22 | List sessionHandlingRules = new ArrayList<>();
23 | String uniqueId;
24 | byte detectionMethod = DETECTION_GREP;
25 | String grepExpression;
26 | String collaboratorReplace;
27 | int collaboratorTimeout = DEFAULT_COLLABORATOR_TIMEOUT;
28 |
29 | final static int DEFAULT_COLLABORATOR_TIMEOUT = 10;
30 |
31 | final static byte STATUS_NOT_TESTED = 0;
32 | final static byte STATUS_WORKING = 1;
33 | final static byte STATUS_ERROR = 2;
34 | final static byte STATUS_VULN = 3;
35 | final static byte STATUS_FIXED = 4;
36 |
37 | final static byte TEST_DETECTED = 0;
38 | final static byte TEST_NOT_DETECTED = 1;
39 | final static byte TEST_ERROR = 2;
40 |
41 | final static byte DETECTION_GREP = 1;
42 | final static byte DETECTION_COLLABORATOR = 2;
43 |
44 | URL url;
45 | byte[] request;
46 | byte[] response;
47 |
48 | IssueTableModel issueTableModel;
49 | int row;
50 |
51 | Issue()
52 | {
53 | }
54 |
55 | public Issue(IHttpRequestResponse request)
56 | {
57 | this.request = request.getRequest();
58 | this.response = request.getResponse();
59 | try
60 | {
61 | IRequestInfo requestInfo = BurpExtender.callbacks.getHelpers().analyzeRequest(request.getHttpService(), request.getRequest());
62 |
63 | SessionRulesMarshaller sessionHandlingRules = new SessionRulesMarshaller();
64 | this.sessionHandlingRules = sessionHandlingRules.getRulesForRequest(requestInfo);
65 | url = requestInfo.getUrl();
66 | path = url.getPath();
67 | }
68 | catch(Exception ex)
69 | {
70 | ex.printStackTrace(new PrintStream(BurpExtender.callbacks.getStderr()));
71 | }
72 | }
73 |
74 | public void startTest()
75 | {
76 | status = STATUS_WORKING;
77 | fireUpdate();
78 | }
79 |
80 | public void finishTest(byte testStatus)
81 | {
82 | if(testStatus == TEST_DETECTED)
83 | {
84 | status = STATUS_VULN;
85 | everVulnerable = true;
86 | }
87 | else if(testStatus == TEST_ERROR)
88 | {
89 | status = STATUS_ERROR;
90 | }
91 | else
92 | {
93 | if(everVulnerable)
94 | {
95 | status = STATUS_FIXED;
96 | }
97 | else
98 | {
99 | status = STATUS_ERROR;
100 | }
101 | }
102 | fireUpdate();
103 | }
104 |
105 | public void fireUpdate()
106 | {
107 | SwingUtilities.invokeLater(new Runnable()
108 | {
109 | @Override
110 | public void run()
111 | {
112 | issueTableModel.fireTableCellUpdated(row, 4);
113 | }
114 | });
115 | }
116 |
117 | public String getStatus()
118 | {
119 | switch(status)
120 | {
121 | case STATUS_NOT_TESTED:
122 | return "";
123 | case STATUS_WORKING:
124 | return "Working...";
125 | case STATUS_ERROR:
126 | return "Unable to replicate";
127 | case STATUS_VULN:
128 | return "Vulnerable";
129 | case STATUS_FIXED:
130 | return "Resolved (tentative)";
131 | default:
132 | return "";
133 | }
134 | }
135 |
136 | String getSessionRules()
137 | {
138 | StringBuilder rc = new StringBuilder();
139 | for(String rule : sessionHandlingRules)
140 | {
141 | if(!rule.equals(("Use cookies from Burp's cookie jar")))
142 | {
143 | rc.append(rule).append(", ");
144 | }
145 | }
146 | return rc.length() < 2 ? "" : rc.substring(0, rc.length() - 2);
147 | }
148 |
149 | IHttpService getHttpService()
150 | {
151 | return BurpExtender.callbacks.getHelpers().buildHttpService(url.getHost(), url.getPort(), url.getProtocol());
152 | }
153 |
154 | String getHost()
155 | {
156 | String host = url.getHost();
157 | // TBD: don't include 80/443 ?
158 | host = host + String.format(":%d", url.getPort());
159 | return host;
160 | }
161 |
162 | void getCookieNames(Set cookiesNames)
163 | {
164 | IRequestInfo requestInfo = BurpExtender.callbacks.getHelpers().analyzeRequest(request);
165 | for(IParameter parameter : requestInfo.getParameters())
166 | {
167 | if(parameter.getType() == IParameter.PARAM_COOKIE)
168 | {
169 | cookiesNames.add(parameter.getName());
170 | }
171 | }
172 | }
173 |
174 | void scrubCookies(Collection cookieNames)
175 | {
176 | IExtensionHelpers helpers = BurpExtender.callbacks.getHelpers();
177 | for (String cookieName : cookieNames)
178 | {
179 | IParameter cookie = helpers.buildParameter(cookieName, "", IParameter.PARAM_COOKIE);
180 | request = helpers.removeParameter(request, cookie);
181 | }
182 |
183 | // If Cookie: header is empty, remove
184 | IRequestInfo requestInfo = BurpExtender.callbacks.getHelpers().analyzeRequest(request);
185 | byte[] body = Arrays.copyOfRange(request, requestInfo.getBodyOffset(), request.length);
186 | List headers = requestInfo.getHeaders();
187 | for (int i = 0; i < headers.size(); i++)
188 | {
189 | if (headers.get(i).equals("Cookie: "))
190 | {
191 | headers.remove(i);
192 | request = BurpExtender.callbacks.getHelpers().buildHttpMessage(headers, body);
193 | }
194 | }
195 | }
196 |
197 | void setHost(String host, int port)
198 | {
199 | try
200 | {
201 | url = new URL(url.getProtocol(), host, port, url.getPath());
202 | } catch (MalformedURLException ex)
203 | {
204 | ex.printStackTrace(new PrintWriter(BurpExtender.callbacks.getStderr()));
205 | }
206 | request = Utils.changeHost(request, host, port);
207 | clearStatus();
208 | }
209 |
210 | void clearStatus()
211 | {
212 | everVulnerable = false;
213 | status = STATUS_NOT_TESTED;
214 | fireUpdate();
215 | }
216 |
217 | byte[] getRequestWithIdentity()
218 | {
219 | uniqueId = Long.toHexString(new Random().nextLong());
220 | IRequestInfo requestInfo = BurpExtender.callbacks.getHelpers().analyzeRequest(request);
221 | List headers = requestInfo.getHeaders();
222 | byte[] body = Arrays.copyOfRange(request, requestInfo.getBodyOffset(), request.length);
223 | headers.add(IDENTITY_HEADER + uniqueId);
224 | return BurpExtender.callbacks.getHelpers().buildHttpMessage(headers, body);
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/burp/IssueChecker.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.io.PrintWriter;
4 | import java.time.Instant;
5 | import java.util.ArrayList;
6 | import java.util.Iterator;
7 | import java.util.List;
8 | import java.util.regex.Matcher;
9 | import java.util.regex.Pattern;
10 |
11 |
12 | class IssueChecker implements Runnable
13 | {
14 | List issues;
15 | ReplicatorPanel replicatorPanel;
16 | boolean terminating = false;
17 | IBurpCollaboratorClientContext collaborator;
18 | final static int POLL_INTERVAL = 5000;
19 |
20 | IssueChecker(List issues, ReplicatorPanel replicatorPanel)
21 | {
22 | this.issues = issues;
23 | this.replicatorPanel = replicatorPanel;
24 | }
25 |
26 | class PendingCollaboratorIssue
27 | {
28 | Issue issue;
29 | Instant probeTime;
30 | String interactionId;
31 |
32 | public PendingCollaboratorIssue(Issue issue)
33 | {
34 | this.issue = issue;
35 | this.probeTime = Instant.now();
36 | }
37 |
38 | boolean isExpired()
39 | {
40 | return probeTime.plusSeconds(issue.collaboratorTimeout).compareTo(Instant.now()) > 0;
41 | }
42 |
43 | String getPayload()
44 | {
45 | interactionId = collaborator.generatePayload(false);
46 | return interactionId + "." + collaborator.getCollaboratorServerLocation();
47 | }
48 | }
49 |
50 | @Override
51 | public void run() {
52 | List pendingCollaboratorIssues = new ArrayList<>();
53 |
54 | if(!replicatorPanel.loggedIn)
55 | {
56 | try
57 | {
58 | MacrosMarshaller.Macro macro = new MacrosMarshaller().getMacroByName(replicatorPanel.optionsPanel.getLoginMacro());
59 | MacroRunner.runMacro(macro, replicatorPanel);
60 | replicatorPanel.loggedIn = true;
61 | } catch (Exception ex)
62 | {
63 | ex.printStackTrace(new PrintWriter(BurpExtender.callbacks.getStderr()));
64 | }
65 | }
66 |
67 | for(Issue issue : issues)
68 | {
69 | if(terminating)
70 | {
71 | break;
72 | }
73 |
74 | issue.startTest();
75 | byte[] request = issue.getRequestWithIdentity();
76 | if(issue.detectionMethod == Issue.DETECTION_COLLABORATOR)
77 | {
78 | if(collaborator == null)
79 | {
80 | collaborator = BurpExtender.callbacks.createBurpCollaboratorClientContext();
81 | }
82 | PendingCollaboratorIssue pendingCollaboratorIssue = new PendingCollaboratorIssue(issue);
83 | String requestString = new String(request);
84 | Matcher matcher = Pattern.compile(issue.collaboratorReplace).matcher(requestString);
85 | if(!matcher.find())
86 | {
87 | issue.finishTest(Issue.TEST_ERROR);
88 | continue;
89 | }
90 | String payload = pendingCollaboratorIssue.getPayload();
91 | request = requestString.replaceAll(issue.collaboratorReplace, payload).getBytes();
92 | pendingCollaboratorIssues.add(pendingCollaboratorIssue);
93 | }
94 |
95 | IHttpRequestResponse response = BurpExtender.callbacks.makeHttpRequest(issue.getHttpService(), request);
96 | issue.response = response.getResponse();
97 |
98 | if(issue == replicatorPanel.optionsPanel.currentIssue)
99 | {
100 | replicatorPanel.responseEditor.setMessage(issue.response == null ? OptionsPanel.EMPTY_MESSAGE : issue.response, false);
101 | }
102 |
103 | byte testStatus;
104 | if(issue.response == null)
105 | {
106 | testStatus = Issue.TEST_ERROR;
107 | issue.finishTest(testStatus);
108 | continue;
109 | }
110 |
111 | if(issue.detectionMethod == Issue.DETECTION_GREP)
112 | {
113 | String strResponse = new String(issue.response);
114 | if (issue.grepExpression == null || issue.grepExpression.isEmpty())
115 | {
116 | testStatus = Issue.TEST_ERROR;
117 | }
118 | else
119 | {
120 | Matcher matcher = Pattern.compile(issue.grepExpression).matcher(strResponse);
121 | testStatus = matcher.find() ? Issue.TEST_DETECTED : Issue.TEST_NOT_DETECTED;
122 | }
123 | issue.finishTest(testStatus);
124 | }
125 | }
126 |
127 | while(!pendingCollaboratorIssues.isEmpty())
128 | {
129 | try
130 | {
131 | Thread.sleep(POLL_INTERVAL);
132 | }
133 | catch (InterruptedException e)
134 | {
135 | // do nothing
136 | }
137 |
138 | for (IBurpCollaboratorInteraction interaction : collaborator.fetchAllCollaboratorInteractions())
139 | {
140 | String interactionId = interaction.getProperty("interaction_id");
141 | for (Iterator iterator = pendingCollaboratorIssues.iterator(); iterator.hasNext();)
142 | {
143 | PendingCollaboratorIssue pendingCollaboratorIssue = iterator.next();
144 | if(pendingCollaboratorIssue.interactionId.equals(interactionId))
145 | {
146 | pendingCollaboratorIssue.issue.finishTest(Issue.TEST_DETECTED);
147 | iterator.remove();
148 | }
149 | }
150 | }
151 |
152 | for (Iterator iterator = pendingCollaboratorIssues.iterator(); iterator.hasNext();)
153 | {
154 | PendingCollaboratorIssue pendingCollaboratorIssue = iterator.next();
155 | if(pendingCollaboratorIssue.isExpired())
156 | {
157 | pendingCollaboratorIssue.issue.finishTest(Issue.TEST_NOT_DETECTED);
158 | iterator.remove();
159 | }
160 | }
161 | }
162 |
163 | replicatorPanel.burpExtender.issueChecker = null;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/burp/IssueTableModel.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import javax.swing.event.TableModelListener;
5 | import javax.swing.table.AbstractTableModel;
6 | import java.util.*;
7 |
8 |
9 | public class IssueTableModel extends AbstractTableModel
10 | {
11 | static List columns = Arrays.asList("ID", "Issue", "Path", "Parameter", "Status");
12 | List issues = new ArrayList();
13 | boolean editable;
14 | int idCounter = 1;
15 |
16 | @Override
17 | public int getRowCount()
18 | {
19 | return issues.size();
20 | }
21 |
22 | @Override
23 | public int getColumnCount()
24 | {
25 | return columns.size();
26 | }
27 |
28 | @Override
29 | public String getColumnName(int column)
30 | {
31 | return columns.get(column);
32 | }
33 |
34 | @Override
35 | public Object getValueAt(int rowIndex, int columnIndex)
36 | {
37 | Issue issue = issues.get(rowIndex);
38 | switch(columnIndex)
39 | {
40 | case 0:
41 | return issue.id;
42 | case 1:
43 | return issue.issue;
44 | case 2:
45 | return issue.path;
46 | case 3:
47 | return issue.parameter;
48 | case 4:
49 | return issue.getStatus();
50 | default:
51 | return null;
52 | }
53 | }
54 |
55 | @Override
56 | public void setValueAt(Object value, int rowIndex, int columnIndex)
57 | {
58 | Issue issue = issues.get(rowIndex);
59 | switch(columnIndex)
60 | {
61 | case 0:
62 | issue.id = (String) value;
63 | break;
64 | case 1:
65 | issue.issue = (String) value;
66 | break;
67 | case 2:
68 | issue.path = (String) value;
69 | break;
70 | case 3:
71 | issue.parameter = (String) value;
72 | break;
73 | }
74 |
75 | }
76 |
77 | @Override
78 | public boolean isCellEditable(int rowIndex, int columnIndex)
79 | {
80 | return editable && columnIndex != 4;
81 | }
82 |
83 | public void addIssue(Issue issue)
84 | {
85 | if(issue.id == null)
86 | {
87 | issue.id = Integer.toString(idCounter);
88 | idCounter++;
89 | }
90 | issue.issueTableModel = this;
91 | issue.row = issues.size();
92 | issues.add(issue);
93 | fireTableRowsInserted(issue.row, issue.row);
94 | }
95 |
96 | public Issue getIssue(int index)
97 | {
98 | try
99 | {
100 | return issues.get(index);
101 | }
102 | catch(ArrayIndexOutOfBoundsException ex)
103 | {
104 | return null;
105 | }
106 | }
107 |
108 | public void deleteIssues(int[] indexes)
109 | {
110 | List indexList = new ArrayList<>();
111 | for(int index : indexes)
112 | {
113 | indexList.add(index);
114 | }
115 | Collections.sort(indexList, Collections.reverseOrder());
116 | for(int index : indexList)
117 | {
118 | issues.remove(index);
119 | for (int j = index; j < issues.size(); j++)
120 | {
121 | issues.get(j).row -= 1;
122 | }
123 | fireTableRowsDeleted(index, index);
124 | }
125 | }
126 |
127 | void setViewType(byte viewType)
128 | {
129 | editable = (viewType == BurpExtender.TESTER_VIEW);
130 | }
131 |
132 | Set getCookiesNames()
133 | {
134 | Set cookiesNames = new HashSet<>();
135 | for(Issue issue : getSelectedIssues())
136 | {
137 | issue.getCookieNames(cookiesNames);
138 | }
139 | return cookiesNames;
140 | }
141 |
142 | void scrubCookies(Collection cookieNames)
143 | {
144 | for(Issue issue : getSelectedIssues())
145 | {
146 | issue.scrubCookies(cookieNames);
147 | }
148 | }
149 |
150 | List getSelectedIssues()
151 | {
152 | for (TableModelListener tml : getTableModelListeners())
153 | {
154 | if (tml instanceof JTable)
155 | {
156 | List selectedIssues = new ArrayList<>();
157 | for (int row : ((JTable) tml).getSelectedRows())
158 | {
159 | selectedIssues.add(issues.get(row));
160 | }
161 | return selectedIssues;
162 | }
163 |
164 | }
165 | return Collections.EMPTY_LIST;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/burp/JsonMarshaller.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 |
7 | import javax.imageio.ImageIO;
8 | import java.io.ByteArrayInputStream;
9 | import java.io.ByteArrayOutputStream;
10 | import java.io.PrintStream;
11 | import java.io.PrintWriter;
12 | import java.net.MalformedURLException;
13 | import java.net.URL;
14 | import java.util.*;
15 |
16 |
17 | public class JsonMarshaller
18 | {
19 | static int JSON_FILE_VERSION = 1;
20 | List warnings = new ArrayList<>();
21 | SessionRulesMarshaller sessionRulesMarshaller = new SessionRulesMarshaller();
22 | MacrosMarshaller macrosMarshaller = new MacrosMarshaller();
23 |
24 | JSONObject marshall(ReplicatorPanel replicatorPanel) throws JSONException
25 | {
26 | JSONObject json = new JSONObject();
27 | json.put("burp_replicator_file_version", JSON_FILE_VERSION);
28 | String loginMacro = replicatorPanel.optionsPanel.getLoginMacro();
29 | json.put("login_macro", loginMacro);
30 | json.put("notes", replicatorPanel.optionsPanel.jNotes.getText());
31 | json.put("config", replicatorPanel.config);
32 |
33 | if(replicatorPanel.logo != null)
34 | {
35 | try
36 | {
37 | ByteArrayOutputStream logoStream = new ByteArrayOutputStream();
38 | ImageIO.write(replicatorPanel.logo, "png", logoStream);
39 | json.put("logo", Base64.getEncoder().encodeToString(logoStream.toByteArray()));
40 | }
41 | catch (Exception e)
42 | {
43 | e.printStackTrace(new PrintWriter(BurpExtender.callbacks.getStderr()));
44 | }
45 | }
46 |
47 | Collection rules = getAllRules(replicatorPanel.issueTableModel.issues);
48 | Collection macros = getAllMacros(rules);
49 | MacrosMarshaller.Macro jsonLoginMacro = macrosMarshaller.getMacroByName(loginMacro);
50 | if(jsonLoginMacro != null)
51 | {
52 | macros.add(jsonLoginMacro.getSerial());
53 | }
54 |
55 | json.put("session_rules", marshallRules(rules));
56 | json.put("macros", marshallMacros(macros));
57 | json.put("issues", marshallIssueTableModel(replicatorPanel.issueTableModel));
58 | return json;
59 | }
60 |
61 | Collection getAllRules(Iterable issues)
62 | {
63 | Set rules = new HashSet<>();
64 | for(Issue issue : issues)
65 | {
66 | for(String ruleName : issue.sessionHandlingRules)
67 | {
68 | rules.add(ruleName);
69 | }
70 | }
71 | return rules;
72 | }
73 |
74 | private JSONArray marshallRules(Iterable rules) throws JSONException
75 | {
76 | JSONArray rc = new JSONArray();
77 | for(String ruleName : rules)
78 | {
79 | rc.put(sessionRulesMarshaller.getRuleByName(ruleName).json);
80 | }
81 | return rc;
82 | }
83 |
84 | Collection getAllMacros(Iterable rules) throws JSONException
85 | {
86 | Set macros = new HashSet<>();
87 | for(String ruleName : rules)
88 | {
89 | SessionRulesMarshaller.Rule rule = sessionRulesMarshaller.getRuleByName(ruleName);
90 | rule.extractMacros(macros);
91 | }
92 | return macros;
93 | }
94 |
95 | private JSONArray marshallMacros(Iterable macros) throws JSONException
96 | {
97 | JSONArray rc = new JSONArray();
98 | for(Long macroSerial : macros)
99 | {
100 | rc.put(macrosMarshaller.getMacroBySerial(macroSerial).json);
101 | }
102 | return rc;
103 | }
104 |
105 |
106 | private JSONArray marshallIssueTableModel(IssueTableModel issueTableModel)
107 | {
108 | JSONArray rc = new JSONArray();
109 | for(Issue issue : issueTableModel.issues)
110 | {
111 | try
112 | {
113 | rc.put(marshallIssue(issue));
114 | }
115 | catch(Exception ex)
116 | {
117 | warnings.add(ex.toString());
118 | ex.printStackTrace(new PrintStream(BurpExtender.callbacks.getStderr()));
119 | }
120 | }
121 | return rc;
122 | }
123 |
124 | private JSONObject marshallIssue(Issue issue) throws JSONException
125 | {
126 | JSONObject data = new JSONObject();
127 | data.put("id", issue.id);
128 | data.put("issue", issue.issue);
129 | data.put("path", issue.path);
130 | data.put("parameter", issue.parameter);
131 | data.put("status", issue.status);
132 | data.put("everVulnerable", issue.everVulnerable);
133 | data.put("notes", issue.notes);
134 | data.put("detection_method", issue.detectionMethod);
135 | if(issue.detectionMethod == Issue.DETECTION_GREP)
136 | {
137 | data.put("grep_expression", issue.grepExpression);
138 | }
139 | if(issue.detectionMethod == Issue.DETECTION_COLLABORATOR);
140 | {
141 | data.put("collaborator_replace", issue.collaboratorReplace);
142 | data.put("collaborator_timeout", issue.collaboratorTimeout);
143 | }
144 | data.put("request", new String(issue.request));
145 | data.put("response", new String(issue.response));
146 | data.put("url", issue.url);
147 | JSONArray sessionRules = new JSONArray();
148 | for(String rule : issue.sessionHandlingRules)
149 | {
150 | sessionRules.put(rule);
151 | }
152 | data.put("session_rules", sessionRules);
153 | return data;
154 | }
155 |
156 | void unmarshall(JSONObject data, ReplicatorPanel replicatorPanel) throws Exception
157 | {
158 | if(!data.has("burp_replicator_file_version"))
159 | {
160 | throw new Exception("This is not a Replicator file.");
161 | }
162 | if(data.getInt("burp_replicator_file_version") > JSON_FILE_VERSION)
163 | {
164 | warnings.add("Replicator file was created with a newer version of Replicator. Some features may not work.");
165 | }
166 | unmarshallIssueTableModel(replicatorPanel.issueTableModel, data.getJSONArray("issues"));
167 |
168 | replicatorPanel.optionsPanel.setLoginMacro(data.optString("login_macro"));
169 | replicatorPanel.optionsPanel.jNotes.setText(data.optString("notes"));
170 | replicatorPanel.config = data.optString("config");
171 |
172 | if(data.has("logo"))
173 | {
174 | byte[] logoBytes = Base64.getDecoder().decode(data.getString("logo"));
175 | replicatorPanel.optionsPanel.setLogo(new ByteArrayInputStream(logoBytes));
176 | }
177 | else
178 | {
179 | replicatorPanel.logo = null;
180 | replicatorPanel.optionsPanel.setViewType(replicatorPanel.viewType);
181 | }
182 |
183 | new MacrosMarshaller().setProjectMacros(data.getJSONArray("macros"));
184 | new SessionRulesMarshaller().setProjectSessionRules(data.getJSONArray("session_rules"));
185 | }
186 |
187 | void unmarshallIssueTableModel(IssueTableModel issueTableModel, JSONArray data) throws JSONException
188 | {
189 | issueTableModel.issues = new ArrayList();
190 | for(int i = 0; i < data.length(); i++)
191 | {
192 | try
193 | {
194 | issueTableModel.addIssue(unmarshallIssue(data.getJSONObject(i)));
195 | }
196 | catch(Exception ex)
197 | {
198 | warnings.add(ex.toString());
199 | ex.printStackTrace(new PrintStream(BurpExtender.callbacks.getStderr()));
200 | }
201 | }
202 | issueTableModel.fireTableDataChanged();
203 | }
204 |
205 |
206 | private Issue unmarshallIssue(JSONObject data) throws JSONException, MalformedURLException
207 | {
208 | Issue issue = new Issue();
209 | issue.id = data.optString("id");
210 | issue.issue = data.optString("issue");
211 | issue.path = data.optString("path");
212 | issue.parameter = data.optString("parameter");
213 | issue.status = (byte) data.optInt("status");
214 | issue.everVulnerable = data.optBoolean("everVulnerable");
215 | issue.notes = data.optString("notes");
216 | issue.detectionMethod = (byte) data.optInt("detection_method");
217 | if(issue.detectionMethod == Issue.DETECTION_GREP)
218 | {
219 | issue.grepExpression = data.optString("grep_expression");
220 | }
221 | if(issue.detectionMethod == Issue.DETECTION_COLLABORATOR)
222 | {
223 | issue.collaboratorReplace = data.optString("collaborator_replace");
224 | issue.collaboratorTimeout = data.optInt("collaborator_timeout");
225 | }
226 |
227 | issue.url = new URL(data.getString("url")); // mandatory
228 | issue.request = data.getString("request").getBytes(); // mandatory
229 | if(data.has("response"))
230 | {
231 | issue.response = data.getString("response").getBytes();
232 | }
233 | JSONArray sessionRules = data.getJSONArray("session_rules");
234 | for(int i = 0; i < sessionRules.length(); i++)
235 | {
236 | issue.sessionHandlingRules.add(sessionRules.getString(i));
237 | }
238 | return issue;
239 | }
240 |
241 | }
242 |
--------------------------------------------------------------------------------
/src/burp/LoginSessionRule.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [
3 | {
4 | "enabled": true,
5 | "invoke_extension_action": false,
6 | "macro_serial_number": 0,
7 | "match_cookies": "all_except",
8 | "match_params": "all_except",
9 | "tolerate_url_mismatch": false,
10 | "type": "run_macro",
11 | "update_with_cookies": true,
12 | "update_with_params": true
13 | }
14 | ],
15 | "description": "Replicator login",
16 | "enabled": true,
17 | "exclude_from_scope": [],
18 | "include_in_scope": [
19 | {
20 | "enabled": true,
21 | "file": "/replicator-login",
22 | "host": "",
23 | "protocol": "any"
24 | }
25 | ],
26 | "named_params": [],
27 | "restrict_scope_to_named_params": false,
28 | "tools_scope": [
29 | "Extensions"
30 | ],
31 | "url_scope": "custom"
32 | }
33 |
--------------------------------------------------------------------------------
/src/burp/MacroRunner.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.io.PrintStream;
4 | import java.net.URL;
5 | import java.util.Arrays;
6 | import java.util.List;
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 |
11 | class MacroRunner implements Runnable
12 | {
13 | MacrosMarshaller.Macro macro;
14 | ReplicatorPanel replicatorPanel;
15 |
16 | MacroRunner(MacrosMarshaller.Macro macro, ReplicatorPanel replicatorPanel)
17 | {
18 | this.macro = macro;
19 | this.replicatorPanel = replicatorPanel;
20 | }
21 |
22 | @Override
23 | public void run()
24 | {
25 | try
26 | {
27 | runMacro(macro, replicatorPanel);
28 | }
29 | catch(Exception ex)
30 | {
31 | ex.printStackTrace(new PrintStream(BurpExtender.callbacks.getStderr()));
32 | }
33 | }
34 |
35 | static void runMacro(MacrosMarshaller.Macro macro, ReplicatorPanel replicatorPanel) throws Exception
36 | {
37 | // TBD: this will trigger the session rule, then cause an unwanted request to /replicator-login
38 | IExtensionHelpers helpers = BurpExtender.callbacks.getHelpers();
39 | URL url = macro.getURL();
40 | IHttpService httpService = helpers.buildHttpService(url.getHost(), url.getPort(), url.getProtocol());
41 | List headers = Arrays.asList("GET /replicator-login HTTP/1.0", "Host: " + url.getHost());
42 | byte[] request = helpers.buildHttpMessage(headers, new byte[0]);
43 | IHttpRequestResponse response = BurpExtender.callbacks.makeHttpRequest(httpService, request);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/burp/MacrosMarshaller.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 | import org.json.JSONTokener;
7 |
8 | import java.io.PrintStream;
9 | import java.io.PrintWriter;
10 | import java.net.MalformedURLException;
11 | import java.net.URL;
12 | import java.util.ArrayList;
13 | import java.util.HashMap;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | public class MacrosMarshaller
18 | {
19 | JSONObject root;
20 | JSONArray macros;
21 |
22 | MacrosMarshaller()
23 | {
24 | try
25 | {
26 | String json = BurpExtender.callbacks.saveConfigAsJson("project_options.sessions.macros");
27 | root = new JSONObject(new JSONTokener(json));
28 | macros = root.getJSONObject("project_options")
29 | .getJSONObject("sessions")
30 | .getJSONObject("macros")
31 | .getJSONArray("macros");
32 | }
33 | catch(JSONException ex)
34 | {
35 | ex.printStackTrace(new PrintWriter(BurpExtender.callbacks.getStderr()));
36 | }
37 | }
38 |
39 | List getMacroNames() throws JSONException
40 | {
41 | List rc = new ArrayList<>();
42 | for(int i = 0; i < macros.length(); i++)
43 | {
44 | rc.add(macros.getJSONObject(i).getString("description"));
45 | }
46 | return rc;
47 | }
48 |
49 | Macro getMacroByName(String name) throws JSONException
50 | {
51 | for(int i = 0; i < macros.length(); i++)
52 | {
53 | if(macros.getJSONObject(i).getString("description").equals(name))
54 | {
55 | return new Macro(macros.getJSONObject(i));
56 | }
57 | }
58 | return null;
59 | }
60 |
61 | Macro getMacroBySerial(Long serial) throws JSONException
62 | {
63 | for(int i = 0; i < macros.length(); i++)
64 | {
65 | if(macros.getJSONObject(i).getLong("serial_number") == serial)
66 | {
67 | return new Macro(macros.getJSONObject(i));
68 | }
69 | }
70 | return null;
71 | }
72 |
73 | void setProjectMacros(JSONArray newMacros) throws JSONException
74 | {
75 | Map macroMap = new HashMap<>();
76 |
77 | for(int i = 0; i < macros.length(); i++)
78 | {
79 | JSONObject macro = macros.getJSONObject(i);
80 | macroMap.put(macro.getLong("serial_number"), macro);
81 | }
82 | for(int i = 0; i < newMacros.length(); i++)
83 | {
84 | JSONObject macro = newMacros.getJSONObject(i);
85 | macroMap.put(macro.getLong("serial_number"), macro);
86 | }
87 |
88 | while(macros.length() > 0)
89 | {
90 | macros.remove(0);
91 | }
92 | for(JSONObject macro : macroMap.values())
93 | {
94 | macros.put(macro);
95 | }
96 |
97 | String json = root.toString(4);
98 | BurpExtender.callbacks.loadConfigFromJson(json);
99 | }
100 |
101 | class Macro
102 | {
103 | JSONObject json;
104 |
105 | Macro(JSONObject json)
106 | {
107 | this.json = json;
108 | }
109 |
110 | @Override
111 | public String toString()
112 | {
113 | try
114 | {
115 | return json.getString("description");
116 | }
117 | catch(Exception ex)
118 | {
119 | ex.printStackTrace(new PrintStream(BurpExtender.callbacks.getStderr()));
120 | return "";
121 | }
122 | }
123 |
124 | Long getSerial() throws JSONException
125 | {
126 | return json.getLong("serial_number");
127 | }
128 |
129 | URL getURL() throws JSONException, MalformedURLException
130 | {
131 | String url = json.getJSONArray("items").getJSONObject(0).getString("url");
132 | return new URL(url);
133 | }
134 |
135 | void changeTarget(String oldHost, int oldPort, String newHost, int newPort)
136 | {
137 | try
138 | {
139 | JSONArray items = json.getJSONArray("items");
140 | for(int i = 0; i < items.length(); i++)
141 | {
142 | JSONObject item = items.getJSONObject(i);
143 | if(item.getString("url").contains(String.format("://%s:%d", oldHost, oldPort)))
144 | {
145 | URL url = new URL(item.getString("url"));
146 | URL newUrl = new URL(url.getProtocol(), newHost, newPort, url.getFile());
147 | item.put("url", newUrl.toString());
148 |
149 | byte[] request = item.getString("request").getBytes();
150 | request = Utils.changeHost(request, newHost, newPort);
151 | item.put("request", new String(request));
152 | }
153 | }
154 | }
155 | catch (JSONException | MalformedURLException e)
156 | {
157 | e.printStackTrace(new PrintWriter(BurpExtender.callbacks.getStderr()));
158 | }
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/src/burp/Menu.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import java.awt.event.ItemEvent;
5 | import java.awt.event.ItemListener;
6 |
7 | public class Menu extends JMenu
8 | {
9 | JMenuItem developerView;
10 | JMenuItem testerView;
11 |
12 | public Menu(BurpExtender burpExtender)
13 | {
14 | super("Replicator");
15 |
16 | ItemListener itemListener = new ItemListener()
17 | {
18 | @Override
19 | public void itemStateChanged(ItemEvent e)
20 | {
21 | if(e.getStateChange() == ItemEvent.SELECTED)
22 | {
23 | for(int i = 0; i < getItemCount(); i++)
24 | {
25 | JMenuItem menuItem = getItem(i);
26 | if(menuItem != e.getSource())
27 | {
28 | menuItem.setSelected(false);
29 | }
30 | }
31 | byte viewType = developerView.isSelected() ? BurpExtender.DEVELOPER_VIEW : BurpExtender.TESTER_VIEW;
32 | burpExtender.callbacks.saveExtensionSetting("viewType", Integer.toString(viewType));
33 | burpExtender.setViewType(viewType);
34 | }
35 | }
36 | };
37 |
38 | developerView = new JCheckBoxMenuItem("Developer view", true);
39 | developerView.addItemListener(itemListener);
40 | add(developerView);
41 |
42 | testerView = new JCheckBoxMenuItem("Tester view");
43 | testerView.addItemListener(itemListener);
44 | add(testerView);
45 | }
46 |
47 | void setViewType(byte viewType)
48 | {
49 | developerView.setSelected(viewType == BurpExtender.DEVELOPER_VIEW);
50 | testerView.setSelected(viewType == BurpExtender.TESTER_VIEW);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/burp/OptionsPanel.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
536 |
--------------------------------------------------------------------------------
/src/burp/ReplicatorPanel.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import javax.swing.event.ChangeEvent;
5 | import javax.swing.event.ChangeListener;
6 | import javax.swing.event.ListSelectionEvent;
7 | import javax.swing.event.ListSelectionListener;
8 | import java.awt.*;
9 | import java.awt.event.ActionEvent;
10 | import java.awt.event.ActionListener;
11 | import java.awt.event.HierarchyEvent;
12 | import java.awt.event.HierarchyListener;
13 | import java.awt.image.BufferedImage;
14 | import java.io.File;
15 | import java.util.List;
16 | import java.util.regex.Pattern;
17 |
18 |
19 | public class ReplicatorPanel extends JPanel implements HierarchyListener
20 | {
21 | JTable issueTable;
22 | OptionsPanel optionsPanel;
23 | IMessageEditor requestEditor;
24 | IMessageEditor responseEditor;
25 | IssueTableModel issueTableModel;
26 | MessageEditorController messageEditorController = new MessageEditorController();
27 | File currentFile;
28 | boolean loggedIn = false;
29 | BurpExtender burpExtender;
30 | byte viewType;
31 | BufferedImage logo;
32 | JSplitPane topSplit;
33 | JSplitPane bottomSplit;
34 | String config = "{\n}";
35 | List trace;
36 |
37 | ReplicatorPanel(BurpExtender burpExtender)
38 | {
39 | this.burpExtender = burpExtender;
40 |
41 | issueTableModel = new IssueTableModel();
42 | issueTable = new JTable(issueTableModel);
43 | issueTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
44 | issueTable.getColumnModel().getColumn(0).setPreferredWidth(50);
45 | issueTable.getColumnModel().getColumn(1).setPreferredWidth(250);
46 | issueTable.getColumnModel().getColumn(2).setPreferredWidth(150);
47 | issueTable.getColumnModel().getColumn(3).setPreferredWidth(100);
48 | issueTable.getColumnModel().getColumn(4).setPreferredWidth(150);
49 | issueTable.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
50 | public void valueChanged(ListSelectionEvent event)
51 | {
52 | Issue issue = issueTableModel.getIssue(issueTable.getSelectedRow());
53 | optionsPanel.setCurrentIssue(issue);
54 | }
55 | });
56 |
57 | optionsPanel = new OptionsPanel(this);
58 | JPanel panel = new JPanel();
59 | panel.setLayout(new BorderLayout());
60 | panel.add(new JScrollPane(optionsPanel), BorderLayout.WEST);
61 | topSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(issueTable), panel);
62 | topSplit.setResizeWeight(0.5);
63 |
64 | requestEditor = BurpExtender.callbacks.createMessageEditor(messageEditorController, false);
65 | responseEditor = BurpExtender.callbacks.createMessageEditor(messageEditorController, false);
66 | bottomSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, requestEditor.getComponent(), responseEditor.getComponent());
67 | bottomSplit.setResizeWeight(0.5);
68 |
69 | JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topSplit, bottomSplit);
70 |
71 | this.setLayout(new BorderLayout());
72 | this.add(splitPane, BorderLayout.CENTER);
73 |
74 | BurpExtender.callbacks.customizeUiComponent(this);
75 | addHierarchyListener(this);
76 | }
77 |
78 | void acceptSendTo(IHttpRequestResponse[] requests)
79 | {
80 | for (IHttpRequestResponse request : requests)
81 | {
82 | if (request.getRequest() != null)
83 | {
84 | Issue issue = new Issue(request);
85 | issueTableModel.addIssue(issue);
86 | issueTable.setRowSelectionInterval(issue.row, issue.row);
87 | optionsPanel.addHost(issue.getHost()); // should be within addIssue?
88 | }
89 | }
90 | highlightTab();
91 | }
92 |
93 | void acceptSendTo(IScanIssue[] scanIssues)
94 | {
95 | for (IScanIssue scanIssue : scanIssues)
96 | {
97 | IHttpRequestResponse[] httpMessages = scanIssue.getHttpMessages();
98 | if(httpMessages.length > 0)
99 | {
100 | Issue issue = new Issue(httpMessages[0]);
101 | issue.issue = scanIssue.getIssueName();
102 | if(httpMessages[0] instanceof IHttpRequestResponseWithMarkers)
103 | {
104 | IHttpRequestResponseWithMarkers message = (IHttpRequestResponseWithMarkers) httpMessages[0];
105 | issue.parameter = getParameter(message);
106 | List markers = message.getRequestMarkers();
107 | if(markers != null && !markers.isEmpty())
108 | {
109 | int[] marker = markers.get(0);
110 | String payload = new String(message.getRequest()).substring(marker[0], marker[1]);
111 | String collaboratorRegex = "\\w{30}\\." + BurpExtender.callbacks.createBurpCollaboratorClientContext().getCollaboratorServerLocation();
112 | if(Pattern.compile(collaboratorRegex).matcher(payload).find())
113 | {
114 | issue.detectionMethod = Issue.DETECTION_COLLABORATOR;
115 | issue.collaboratorReplace = collaboratorRegex;
116 | }
117 | }
118 |
119 | markers = message.getResponseMarkers();
120 | if(markers != null && !markers.isEmpty())
121 | {
122 | int[] marker = markers.get(0);
123 | issue.grepExpression = BurpExtender.escapeRegex(new String(message.getResponse()).substring(marker[0], marker[1]));
124 | }
125 | }
126 | issueTableModel.addIssue(issue);
127 | issueTable.setRowSelectionInterval(issue.row, issue.row);
128 | optionsPanel.addHost(issue.getHost());
129 | }
130 | }
131 | highlightTab();
132 | }
133 |
134 | String getParameter(IHttpRequestResponseWithMarkers message)
135 | {
136 | IRequestInfo requestInfo = BurpExtender.callbacks.getHelpers().analyzeRequest(message.getRequest());
137 | List markers = message.getRequestMarkers();
138 | if(markers != null && !markers.isEmpty())
139 | {
140 | int[] marker = markers.get(0);
141 | for(IParameter parameter : requestInfo.getParameters())
142 | {
143 | if(marker[0] >= parameter.getValueStart() && marker[1] <= parameter.getValueEnd())
144 | {
145 | return parameter.getName();
146 | }
147 | }
148 | }
149 | return null;
150 | }
151 |
152 | void highlightTab()
153 | {
154 | if(tabbedPane != null)
155 | {
156 | for(int i = 0; i < tabbedPane.getTabCount(); i++)
157 | {
158 | if(tabbedPane.getComponentAt(i) == this)
159 | {
160 | tabbedPane.setBackgroundAt(i, new Color(0xff6633));
161 | Timer timer = new Timer(3000, new ActionListener()
162 | {
163 | @Override
164 | public void actionPerformed(ActionEvent e)
165 | {
166 | for(int j = 0; j < tabbedPane.getTabCount(); j++)
167 | {
168 | if (tabbedPane.getComponentAt(j) == ReplicatorPanel.this)
169 | {
170 | tabbedPane.setBackgroundAt(j, Color.BLACK);
171 | break;
172 | }
173 | }
174 | }
175 | });
176 | timer.setRepeats(false);
177 | timer.start();
178 | break;
179 | }
180 | }
181 | }
182 |
183 | }
184 |
185 | JTabbedPane tabbedPane;
186 | ChangeListener changeListener;
187 |
188 | @Override
189 | public void hierarchyChanged(HierarchyEvent e)
190 | {
191 | tabbedPane = (JTabbedPane) getParent();
192 | changeListener = new ChangeListener() {
193 | public void stateChanged(ChangeEvent e) {
194 | if(tabbedPane.getSelectedComponent() == ReplicatorPanel.this)
195 | {
196 | tabbedPane.setBackgroundAt(tabbedPane.getSelectedIndex(), Color.BLACK);
197 | optionsPanel.loadMacros();
198 | }
199 | }
200 | };
201 | tabbedPane.addChangeListener(changeListener);
202 | removeHierarchyListener(this);
203 | }
204 |
205 | void removeChangeListener()
206 | {
207 | if (changeListener != null)
208 | {
209 | tabbedPane.removeChangeListener(changeListener);
210 | }
211 | }
212 |
213 | class MessageEditorController implements IMessageEditorController
214 | {
215 | @Override
216 | public IHttpService getHttpService() {
217 | return optionsPanel.currentIssue.getHttpService();
218 | }
219 |
220 | @Override
221 | public byte[] getRequest() {
222 | return optionsPanel.currentIssue.request;
223 | }
224 |
225 | @Override
226 | public byte[] getResponse() {
227 | return optionsPanel.currentIssue.response;
228 | }
229 | }
230 |
231 | void setViewType(byte viewType)
232 | {
233 | requestEditor = BurpExtender.callbacks.createMessageEditor(messageEditorController, viewType == BurpExtender.TESTER_VIEW);
234 | bottomSplit.setLeftComponent(requestEditor.getComponent());
235 | if(optionsPanel.currentIssue != null)
236 | {
237 | requestEditor.setMessage(optionsPanel.currentIssue.request == null ? OptionsPanel.EMPTY_MESSAGE : optionsPanel.currentIssue.request, true);
238 | }
239 |
240 | this.viewType = viewType;
241 | optionsPanel.setViewType(viewType);
242 | issueTableModel.setViewType(viewType);
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/src/burp/SessionRulesMarshaller.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 | import org.json.JSONTokener;
7 |
8 | import java.io.InputStream;
9 | import java.io.PrintStream;
10 | import java.net.URL;
11 | import java.util.*;
12 | import java.util.regex.Pattern;
13 |
14 | public class SessionRulesMarshaller
15 | {
16 | List rules = new ArrayList<>();
17 | JSONObject root;
18 | JSONArray sessionHandlingRules;
19 | JSONObject targetScope;
20 |
21 | void setProjectSessionRule(JSONObject newSessionRule) throws JSONException
22 | {
23 | JSONArray newSessionRules = new JSONArray();
24 | newSessionRules.put(newSessionRule);
25 | newSessionRules.put(genereateUseCookiesSessionRule());
26 | setProjectSessionRules(newSessionRules);
27 | }
28 |
29 | void setProjectSessionRules(JSONArray newSessionRules) throws JSONException
30 | {
31 | Map ruleMap = new HashMap<>();
32 |
33 | for(int i = 0; i < sessionHandlingRules.length(); i++)
34 | {
35 | JSONObject rule = sessionHandlingRules.getJSONObject(i);
36 | ruleMap.put(rule.getString("description"), rule);
37 | }
38 | for(int i = 0; i < newSessionRules.length(); i++)
39 | {
40 | JSONObject rule = newSessionRules.getJSONObject(i);
41 | ruleMap.put(rule.getString("description"), rule);
42 | }
43 |
44 | while(sessionHandlingRules.length() > 0)
45 | {
46 | sessionHandlingRules.remove(0);
47 | }
48 | for(JSONObject rule : ruleMap.values())
49 | {
50 | sessionHandlingRules.put(rule);
51 | }
52 |
53 | String json = root.toString(4);
54 | BurpExtender.callbacks.loadConfigFromJson(json);
55 | }
56 |
57 | static JSONObject genereateLoginSessionRule(Long serial, URL url) throws JSONException
58 | {
59 | InputStream inputStream = SessionRulesMarshaller.class.getResourceAsStream("LoginSessionRule.json");
60 | Scanner s = new Scanner(inputStream).useDelimiter("\\A");
61 | String ruleString = s.hasNext() ? s.next() : "";
62 | JSONObject sessionrule = new JSONObject(new JSONTokener(ruleString));
63 | sessionrule.getJSONArray("actions").getJSONObject(0).put("macro_serial_number", serial);
64 | sessionrule.getJSONArray("include_in_scope").getJSONObject(0).put("host", url.getHost());
65 | return sessionrule;
66 | }
67 |
68 | static JSONObject genereateUseCookiesSessionRule() throws JSONException
69 | {
70 | InputStream inputStream = SessionRulesMarshaller.class.getResourceAsStream("UseCookiesSessionRule.json");
71 | Scanner s = new Scanner(inputStream).useDelimiter("\\A");
72 | String ruleString = s.hasNext() ? s.next() : "";
73 | return new JSONObject(new JSONTokener(ruleString));
74 | }
75 |
76 | SessionRulesMarshaller()
77 | {
78 | try
79 | {
80 | String json = BurpExtender.callbacks.saveConfigAsJson("target.scope");
81 | targetScope = new JSONObject(new JSONTokener(json))
82 | .getJSONObject("target")
83 | .getJSONObject("scope");
84 |
85 | json = BurpExtender.callbacks.saveConfigAsJson("project_options.sessions.session_handling_rules");
86 | root = new JSONObject(new JSONTokener(json));
87 | sessionHandlingRules = root.getJSONObject("project_options")
88 | .getJSONObject("sessions")
89 | .getJSONObject("session_handling_rules")
90 | .getJSONArray("rules");
91 | for (int i = 0; i < sessionHandlingRules.length(); i++)
92 | {
93 | rules.add(new Rule(sessionHandlingRules.getJSONObject(i)));
94 | }
95 | }
96 | catch(JSONException ex)
97 | {
98 | ex.printStackTrace(new PrintStream(BurpExtender.callbacks.getStderr()));
99 | }
100 | }
101 |
102 | List getRulesForRequest(IRequestInfo request)
103 | {
104 | List rc = new ArrayList<>();
105 | for (Rule rule : rules)
106 | {
107 | try
108 | {
109 | if(rule.getName().equals("Replicator use cookies"))
110 | {
111 | continue;
112 | }
113 | if (rule.matchesRequest(request))
114 | {
115 | rc.add(rule.getName());
116 | }
117 | }
118 | catch(JSONException ex)
119 | {
120 | ex.printStackTrace(new PrintStream(BurpExtender.callbacks.getStderr()));
121 | }
122 | }
123 | return rc;
124 | }
125 |
126 | Rule getRuleByName(String name) throws JSONException
127 | {
128 | for (Rule rule : rules)
129 | {
130 | if(rule.getName().equals(name))
131 | {
132 | return rule;
133 | }
134 | }
135 | return null;
136 | }
137 |
138 | class Rule
139 | {
140 | JSONObject json;
141 |
142 | Rule(JSONObject json) throws JSONException
143 | {
144 | this.json = json;
145 | }
146 |
147 | String getName() throws JSONException {
148 | return json.getString("description");
149 | }
150 |
151 | boolean matchesRequest(IRequestInfo request) throws JSONException
152 | {
153 | return isEnabled()
154 | && matchesTool("Extender")
155 | && matchesIParams(request.getParameters())
156 | && matchesUrl(request.getUrl());
157 | }
158 |
159 | boolean isEnabled() throws JSONException
160 | {
161 | return json.getBoolean("enabled");
162 | }
163 |
164 | boolean matchesTool(String tool) throws JSONException
165 | {
166 | JSONArray toolsScope = (JSONArray) json.get("tools_scope");
167 | for (int i = 0; i < toolsScope.length(); i++)
168 | {
169 | if (toolsScope.get(i).equals(tool))
170 | {
171 | return true;
172 | }
173 | }
174 | return false;
175 | }
176 |
177 | boolean matchesIParams(List params) throws JSONException
178 | {
179 | List paramNames = new ArrayList<>();
180 | for(IParameter param : params)
181 | {
182 | paramNames.add(param.getName());
183 | }
184 | return matchesParams(paramNames);
185 | }
186 |
187 | boolean matchesParams(List params) throws JSONException
188 | {
189 | if(!json.getBoolean("restrict_scope_to_named_params"))
190 | {
191 | return true;
192 | }
193 | JSONArray namedParams = json.getJSONArray("named_params");
194 | for (int i = 0; i < namedParams.length(); i++)
195 | {
196 | if (params.contains(namedParams.get(i)))
197 | {
198 | return true;
199 | }
200 | }
201 | return false;
202 | }
203 |
204 | boolean matchesUrl(URL url) throws JSONException
205 | {
206 | JSONArray includeInScope;
207 | JSONArray excludeFromScope;
208 |
209 | String urlScope = json.getString("url_scope");
210 | if(urlScope.equals("all"))
211 | {
212 | return true;
213 | }
214 | else if(urlScope.equals("target"))
215 | {
216 | includeInScope = targetScope.getJSONArray("include");
217 | excludeFromScope = targetScope.getJSONArray("exclude");
218 | }
219 | else if(urlScope.equals("custom"))
220 | {
221 | includeInScope = json.getJSONArray("include_in_scope");
222 | excludeFromScope = json.getJSONArray("exclude_from_scope");
223 | }
224 | else
225 | {
226 | throw new JSONException("Invalid url_scope: " + urlScope);
227 | }
228 |
229 | boolean any_include_matches = false;
230 | for (int i = 0; i < includeInScope.length(); i++)
231 | {
232 | if(scopeMatchesUrl(includeInScope.getJSONObject(i), url))
233 | {
234 | any_include_matches = true;
235 | break;
236 | }
237 | }
238 | if(!any_include_matches)
239 | {
240 | return false;
241 | }
242 |
243 | for (int i = 0; i < excludeFromScope.length(); i++)
244 | {
245 | if(scopeMatchesUrl(excludeFromScope.getJSONObject(i), url))
246 | {
247 | return false;
248 | }
249 | }
250 |
251 | return true;
252 | }
253 |
254 | boolean scopeMatchesUrl(JSONObject scope, URL url) throws JSONException
255 | {
256 | if(!scope.getBoolean("enabled"))
257 | {
258 | return false;
259 | }
260 |
261 | // Simplified scope
262 | if(scope.has("prefix"))
263 | {
264 | return url.toString().startsWith(scope.getString("prefix"));
265 | }
266 |
267 | // Advanced scope
268 | if(scope.has("host"))
269 | {
270 | String host = scope.getString("host");
271 | if (isIpAddress(host))
272 | {
273 | // TBD: handle IP address
274 | }
275 | else if (!Pattern.compile(host).matcher(url.getHost()).matches())
276 | {
277 | return false;
278 | }
279 | }
280 |
281 | if(scope.has("port") && !Pattern.compile(scope.getString("port")).matcher(Integer.toString(url.getPort())).matches())
282 | {
283 | return false;
284 | }
285 |
286 | if(scope.has("file") && !Pattern.compile(scope.getString("file")).matcher(url.getFile()).matches())
287 | {
288 | return false;
289 | }
290 |
291 | if(scope.has("protocol"))
292 | {
293 | String protocol = scope.getString("protocol");
294 | if (!protocol.equals("any") && !protocol.equals(url.getProtocol()))
295 | {
296 | return false;
297 | }
298 | }
299 |
300 | return true;
301 | }
302 |
303 | boolean isIpAddress(String input)
304 | {
305 | return Pattern.compile("^\\d+\\.\\d+\\.\\d+\\.\\d+$").matcher(input).matches();
306 | }
307 |
308 | void extractMacros(Set macros) throws JSONException
309 | {
310 | JSONArray actions = json.getJSONArray("actions");
311 | for(int i = 0; i < actions.length(); i++)
312 | {
313 | JSONObject action = actions.getJSONObject(i);
314 | if(action.has("macro_serial_number"))
315 | {
316 | macros.add(action.getLong("macro_serial_number"));
317 | }
318 | }
319 | }
320 |
321 | void changeTarget(String oldHost, int oldPort, String newHost, int newPort) throws JSONException
322 | {
323 | if(!json.getString("url_scope").equals("custom"))
324 | {
325 | return;
326 | }
327 |
328 | JSONArray includeInScope = json.getJSONArray("include_in_scope");
329 | for (int i = 0; i < includeInScope.length(); i++)
330 | {
331 | changeScopeTarget(includeInScope.getJSONObject(i), oldHost, oldPort, newHost, newPort);
332 | }
333 |
334 | JSONArray excludeFromScope = json.getJSONArray("exclude_from_scope");
335 | for (int i = 0; i < excludeFromScope.length(); i++)
336 | {
337 | changeScopeTarget(excludeFromScope.getJSONObject(i), oldHost, oldPort, newHost, newPort);
338 | }
339 | }
340 |
341 | void changeScopeTarget(JSONObject scope, String oldHost, int oldPort, String newHost, int newPort) throws JSONException
342 | {
343 | // Simplified scope
344 | if(scope.has("prefix"))
345 | {
346 | scope.put("prefix", scope.getString("prefix").replace(String.format("://%s:%d", oldHost, oldPort), String.format("://%s:%d", newHost, newPort)));
347 | return;
348 | }
349 |
350 | // Advanced scope
351 | if(scope.getString("host").equals(oldHost) && scope.getInt("port") == oldPort)
352 | {
353 | scope.put("host", newHost);
354 | scope.put("port", newPort);
355 | }
356 | }
357 |
358 | }
359 |
360 | }
361 |
--------------------------------------------------------------------------------
/src/burp/TraceItem.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | import java.net.MalformedURLException;
7 | import java.net.URL;
8 |
9 | public class TraceItem
10 | {
11 | boolean isRequest;
12 | byte[] message;
13 | URL url;
14 |
15 | TraceItem(IHttpRequestResponse messageInfo, boolean messageIsRequest)
16 | {
17 | isRequest = messageIsRequest;
18 |
19 | if(messageIsRequest)
20 | {
21 | try
22 | {
23 | IHttpService service = messageInfo.getHttpService();
24 | url = new URL(service.getProtocol(), service.getHost(), service.getPort(), "");
25 | }
26 | catch (MalformedURLException e)
27 | {
28 | // do nothing
29 | }
30 | message = messageInfo.getRequest();
31 | }
32 | else
33 | {
34 | message = messageInfo.getResponse();
35 | }
36 | }
37 |
38 | JSONObject marshall() throws JSONException
39 | {
40 | JSONObject rc = new JSONObject();
41 | rc.put("is_request", isRequest);
42 | rc.put("message", new String(message));
43 | if(isRequest)
44 | {
45 | rc.put("url", url.toString());
46 | }
47 | return rc;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/burp/UseCookiesSessionRule.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions":[
3 | {
4 | "enabled":true,
5 | "match_cookies":"all_except",
6 | "type":"use_cookies"
7 | }
8 | ],
9 | "description":"Replicator use cookies",
10 | "enabled":true,
11 | "exclude_from_scope":[],
12 | "include_in_scope":[],
13 | "named_params":[],
14 | "restrict_scope_to_named_params":false,
15 | "tools_scope":[
16 | "Extensions"
17 | ],
18 | "url_scope":"all",
19 | "url_scope_advanced_mode":false
20 | }
21 |
--------------------------------------------------------------------------------
/src/burp/Utils.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.Arrays;
4 | import java.util.List;
5 |
6 | public class Utils
7 | {
8 |
9 | static byte[] changeHost(byte[] request, String host, int port)
10 | {
11 | IRequestInfo requestInfo = BurpExtender.callbacks.getHelpers().analyzeRequest(request);
12 | List headers = requestInfo.getHeaders();
13 | byte[] body = Arrays.copyOfRange(request, requestInfo.getBodyOffset(), request.length);
14 | for (int i = 0; i < headers.size(); i++)
15 | {
16 | if (headers.get(i).startsWith("Host:"))
17 | {
18 | headers.set(i, String.format("Host: %s:%d", host, port));
19 | break;
20 | }
21 | }
22 | return BurpExtender.callbacks.getHelpers().buildHttpMessage(headers, body);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/burp/your-logo-here.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/replicator/f02cd93c992dd27412523ccfc730a3b0169a8198/src/burp/your-logo-here.png
--------------------------------------------------------------------------------