├── .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 |
  1. Load the Replicator file.
  2. 12 |
  3. If you want to test a different application instance (perhaps a development instance) edit the Hosts section to point to the instance.
  4. 13 |
  5. 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.
  6. 14 |
  7. Save the file. This is important for confirming fixes later.
  8. 15 |
  9. Identify an issue to work on. Consult the pen test report for a full description.
  10. 16 |
  11. When the application has been updated, click Test to see if it's still vulnerable.
  12. 17 |
18 | 19 |

Issues can have the following status:

20 | 21 | 26 | 27 | 28 |

Tester workflow

29 | 30 |
    31 |
  1. Put Replicator in Tester mode using the menu.
  2. 32 |
  3. It is recommended to add issues to Replicator when they are discovered. This will assist with report writing. 33 |
      34 |
    1. Issues detected by Scanner can be sent to Replicator, using the context menu.
    2. 35 |
    3. Other issues can be sent from the relevant tool to Replicator. You need to complete the issue details, including grep expression.
    4. 36 |
    5. If any issues require a login session, you must create a login macro, and select this in Replicator.
    6. 37 |
    7. 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.
    8. 38 |
    39 |
  4. 40 |
  5. 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 |
    1. Select all issues, and click Scrub cookies... Remove any session cookies from the requests.
    2. 43 |
    3. Click Empty cookie jar
    4. 44 |
    5. Select all the issues and click Clear status
    6. 45 |
    7. Click Test all and verify that all issues report as vulnerable.
    8. 46 |
    47 |
  6. 48 |
  7. 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.
  8. 49 |
  9. Clear the status before sending the file. Select all the issues, click Clear status, and save the file.
  10. 50 |
  11. Send the Replicator file to the client, using the same delivery mechanism as the report.
  12. 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 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
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 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
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 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 |
76 | 77 | 78 | 79 | 80 | <Editor/> 81 | <Renderer/> 82 | </Column> 83 | <Column maxWidth="-1" minWidth="-1" prefWidth="100" resizable="true"> 84 | <Title/> 85 | <Editor/> 86 | <Renderer/> 87 | </Column> 88 | <Column maxWidth="-1" minWidth="-1" prefWidth="100" resizable="true"> 89 | <Title/> 90 | <Editor/> 91 | <Renderer/> 92 | </Column> 93 | <Column maxWidth="-1" minWidth="-1" prefWidth="300" resizable="true"> 94 | <Title/> 95 | <Editor/> 96 | <Renderer/> 97 | </Column> 98 | <Column maxWidth="-1" minWidth="-1" prefWidth="100" resizable="true"> 99 | <Title/> 100 | <Editor/> 101 | <Renderer/> 102 | </Column> 103 | </TableColumnModel> 104 | </Property> 105 | <Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor"> 106 | <TableHeader reorderingAllowed="false" resizingAllowed="true"/> 107 | </Property> 108 | </Properties> 109 | </Component> 110 | </SubComponents> 111 | </Container> 112 | <Component class="javax.swing.JButton" name="jButtonEmpty"> 113 | <Properties> 114 | <Property name="text" type="java.lang.String" value="Empty cookie jar"/> 115 | </Properties> 116 | <Events> 117 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonEmptyActionPerformed"/> 118 | </Events> 119 | </Component> 120 | <Component class="javax.swing.JButton" name="jButtonClose"> 121 | <Properties> 122 | <Property name="text" type="java.lang.String" value="Close"/> 123 | </Properties> 124 | <Events> 125 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonCloseActionPerformed"/> 126 | </Events> 127 | </Component> 128 | </SubComponents> 129 | </Form> 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<ICookie> 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<String> 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 | // <editor-fold defaultstate="collapsed" desc="Generated Code">//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 | }// </editor-fold>//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 | <?xml version="1.0" encoding="UTF-8" ?> 2 | 3 | <Form version="1.3" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo"> 4 | <Properties> 5 | <Property name="defaultCloseOperation" type="int" value="2"/> 6 | <Property name="title" type="java.lang.String" value="Select Cookies to Scrub"/> 7 | <Property name="autoRequestFocus" type="boolean" value="false"/> 8 | <Property name="resizable" type="boolean" value="false"/> 9 | </Properties> 10 | <SyntheticProperties> 11 | <SyntheticProperty name="formSizePolicy" type="int" value="1"/> 12 | <SyntheticProperty name="generateCenter" type="boolean" value="false"/> 13 | </SyntheticProperties> 14 | <AuxValues> 15 | <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> 16 | <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> 17 | <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> 18 | <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> 19 | <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> 20 | <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> 21 | <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> 22 | <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> 23 | <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> 24 | </AuxValues> 25 | 26 | <Layout> 27 | <DimensionLayout dim="0"> 28 | <Group type="103" groupAlignment="0" attributes="0"> 29 | <Group type="102" attributes="0"> 30 | <Group type="103" groupAlignment="0" attributes="0"> 31 | <Group type="102" alignment="0" attributes="0"> 32 | <EmptySpace min="-2" pref="107" max="-2" attributes="0"/> 33 | <Component id="jButtonOk" min="-2" max="-2" attributes="0"/> 34 | <EmptySpace max="-2" attributes="0"/> 35 | <Component id="jButtonCancel" min="-2" max="-2" attributes="0"/> 36 | </Group> 37 | <Group type="102" alignment="0" attributes="0"> 38 | <EmptySpace min="-2" pref="29" max="-2" attributes="0"/> 39 | <Component id="jScrollPane1" min="-2" pref="190" max="-2" attributes="0"/> 40 | </Group> 41 | <Group type="102" alignment="0" attributes="0"> 42 | <EmptySpace min="-2" pref="16" max="-2" attributes="0"/> 43 | <Component id="jLabel1" min="-2" max="-2" attributes="0"/> 44 | </Group> 45 | </Group> 46 | <EmptySpace pref="24" max="32767" attributes="0"/> 47 | </Group> 48 | </Group> 49 | </DimensionLayout> 50 | <DimensionLayout dim="1"> 51 | <Group type="103" groupAlignment="0" attributes="0"> 52 | <Group type="102" alignment="0" attributes="0"> 53 | <EmptySpace min="-2" pref="16" max="-2" attributes="0"/> 54 | <Component id="jLabel1" min="-2" max="-2" attributes="0"/> 55 | <EmptySpace type="separate" max="-2" attributes="0"/> 56 | <Component id="jScrollPane1" min="-2" max="-2" attributes="0"/> 57 | <EmptySpace min="-2" pref="18" max="-2" attributes="0"/> 58 | <Group type="103" groupAlignment="3" attributes="0"> 59 | <Component id="jButtonOk" alignment="3" min="-2" max="-2" attributes="0"/> 60 | <Component id="jButtonCancel" alignment="3" min="-2" max="-2" attributes="0"/> 61 | </Group> 62 | <EmptySpace pref="56" max="32767" attributes="0"/> 63 | </Group> 64 | </Group> 65 | </DimensionLayout> 66 | </Layout> 67 | <SubComponents> 68 | <Component class="javax.swing.JLabel" name="jLabel1"> 69 | <Properties> 70 | <Property name="text" type="java.lang.String" value="Which cookies would you like to scrub from the selected requests?"/> 71 | </Properties> 72 | </Component> 73 | <Container class="javax.swing.JScrollPane" name="jScrollPane1"> 74 | <AuxValues> 75 | <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/> 76 | </AuxValues> 77 | 78 | <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> 79 | <SubComponents> 80 | <Component class="javax.swing.JList" name="jListCookies"> 81 | <Properties> 82 | <Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> 83 | <Connection code="cookieListModel" type="code"/> 84 | </Property> 85 | </Properties> 86 | <AuxValues> 87 | <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/> 88 | </AuxValues> 89 | </Component> 90 | </SubComponents> 91 | </Container> 92 | <Component class="javax.swing.JButton" name="jButtonOk"> 93 | <Properties> 94 | <Property name="text" type="java.lang.String" value="Ok"/> 95 | </Properties> 96 | <Events> 97 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonOkActionPerformed"/> 98 | </Events> 99 | </Component> 100 | <Component class="javax.swing.JButton" name="jButtonCancel"> 101 | <Properties> 102 | <Property name="text" type="java.lang.String" value="Cancel"/> 103 | </Properties> 104 | <Events> 105 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonCancelActionPerformed"/> 106 | </Events> 107 | </Component> 108 | </SubComponents> 109 | </Form> 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<String> 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 | // <editor-fold defaultstate="collapsed" desc="Generated Code">//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 | }// </editor-fold>//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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<Issue> issues; 15 | ReplicatorPanel replicatorPanel; 16 | boolean terminating = false; 17 | IBurpCollaboratorClientContext collaborator; 18 | final static int POLL_INTERVAL = 5000; 19 | 20 | IssueChecker(List<Issue> 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<PendingCollaboratorIssue> 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<PendingCollaboratorIssue> 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<PendingCollaboratorIssue> 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<String> columns = Arrays.asList("ID", "Issue", "Path", "Parameter", "Status"); 12 | List<Issue> issues = new ArrayList<Issue>(); 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<Integer> 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<String> getCookiesNames() 133 | { 134 | Set<String> cookiesNames = new HashSet<>(); 135 | for(Issue issue : getSelectedIssues()) 136 | { 137 | issue.getCookieNames(cookiesNames); 138 | } 139 | return cookiesNames; 140 | } 141 | 142 | void scrubCookies(Collection<String> cookieNames) 143 | { 144 | for(Issue issue : getSelectedIssues()) 145 | { 146 | issue.scrubCookies(cookieNames); 147 | } 148 | } 149 | 150 | List<Issue> getSelectedIssues() 151 | { 152 | for (TableModelListener tml : getTableModelListeners()) 153 | { 154 | if (tml instanceof JTable) 155 | { 156 | List<Issue> 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<String> 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<String> rules = getAllRules(replicatorPanel.issueTableModel.issues); 48 | Collection<Long> 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<String> getAllRules(Iterable<Issue> issues) 62 | { 63 | Set<String> 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<String> 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<Long> getAllMacros(Iterable<String> rules) throws JSONException 85 | { 86 | Set<Long> 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<Long> 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<Issue>(); 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<String> 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<String> getMacroNames() throws JSONException 40 | { 41 | List<String> 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<Long, JSONObject> 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 | <?xml version="1.0" encoding="UTF-8" ?> 2 | 3 | <Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> 4 | <NonVisualComponents> 5 | <Component class="javax.swing.JComboBox" name="jComboBox1"> 6 | <Properties> 7 | <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor"> 8 | <StringArray count="4"> 9 | <StringItem index="0" value="Item 1"/> 10 | <StringItem index="1" value="Item 2"/> 11 | <StringItem index="2" value="Item 3"/> 12 | <StringItem index="3" value="Item 4"/> 13 | </StringArray> 14 | </Property> 15 | </Properties> 16 | <AuxValues> 17 | <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/> 18 | </AuxValues> 19 | </Component> 20 | <Component class="javax.swing.ButtonGroup" name="buttonGroup1"> 21 | </Component> 22 | </NonVisualComponents> 23 | <AuxValues> 24 | <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> 25 | <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> 26 | <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> 27 | <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> 28 | <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> 29 | <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> 30 | <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> 31 | <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> 32 | <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> 33 | </AuxValues> 34 | 35 | <Layout> 36 | <DimensionLayout dim="0"> 37 | <Group type="103" groupAlignment="0" attributes="0"> 38 | <Group type="102" alignment="0" attributes="0"> 39 | <EmptySpace max="32767" attributes="0"/> 40 | <Group type="103" groupAlignment="0" attributes="0"> 41 | <Component id="jPanelIssue" min="-2" max="-2" attributes="0"/> 42 | <Group type="103" groupAlignment="0" max="-2" attributes="0"> 43 | <Group type="102" alignment="0" attributes="0"> 44 | <Group type="103" groupAlignment="0" attributes="0"> 45 | <Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/> 46 | <Component id="jLabel4" alignment="0" min="-2" max="-2" attributes="0"/> 47 | </Group> 48 | <EmptySpace min="-2" pref="42" max="-2" attributes="0"/> 49 | <Group type="103" groupAlignment="0" attributes="0"> 50 | <Group type="102" attributes="0"> 51 | <Component id="jComboBoxLoginMacro" min="-2" pref="159" max="-2" attributes="0"/> 52 | <EmptySpace max="-2" attributes="0"/> 53 | <Component id="jButtonLogin" min="-2" max="-2" attributes="0"/> 54 | <EmptySpace max="-2" attributes="0"/> 55 | <Component id="jButtonCookieJar" min="-2" max="-2" attributes="0"/> 56 | <EmptySpace max="-2" attributes="0"/> 57 | <Component id="jButtonTrace" min="-2" max="-2" attributes="0"/> 58 | </Group> 59 | <Component id="jScrollPaneHosts" min="-2" pref="468" max="-2" attributes="0"/> 60 | </Group> 61 | </Group> 62 | <Group type="102" alignment="0" attributes="0"> 63 | <Group type="103" groupAlignment="0" attributes="0"> 64 | <Component id="jLabel3" min="-2" max="-2" attributes="0"/> 65 | <Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/> 66 | </Group> 67 | <EmptySpace min="-2" pref="33" max="-2" attributes="0"/> 68 | <Group type="103" groupAlignment="0" attributes="0"> 69 | <Group type="102" attributes="0"> 70 | <Group type="103" groupAlignment="0" attributes="0"> 71 | <Group type="102" attributes="0"> 72 | <Component id="jButtonLoad" min="-2" max="-2" attributes="0"/> 73 | <EmptySpace max="-2" attributes="0"/> 74 | <Component id="jButtonSave" min="-2" max="-2" attributes="0"/> 75 | <EmptySpace max="-2" attributes="0"/> 76 | <Component id="jButtonTestAll" min="-2" max="-2" attributes="0"/> 77 | </Group> 78 | <Group type="102" attributes="0"> 79 | <EmptySpace min="6" pref="6" max="-2" attributes="0"/> 80 | <Component id="jLabelFile" min="-2" pref="162" max="-2" attributes="0"/> 81 | </Group> 82 | </Group> 83 | <EmptySpace max="-2" attributes="0"/> 84 | <Component id="jLabelLogo" max="32767" attributes="0"/> 85 | </Group> 86 | <Component id="jScrollPane1" max="32767" attributes="0"/> 87 | </Group> 88 | </Group> 89 | </Group> 90 | </Group> 91 | </Group> 92 | </Group> 93 | </DimensionLayout> 94 | <DimensionLayout dim="1"> 95 | <Group type="103" groupAlignment="0" attributes="0"> 96 | <Group type="102" alignment="0" attributes="0"> 97 | <EmptySpace max="32767" attributes="0"/> 98 | <Group type="103" groupAlignment="0" max="-2" attributes="0"> 99 | <Component id="jLabelLogo" alignment="0" min="-2" pref="60" max="-2" attributes="0"/> 100 | <Group type="102" alignment="0" attributes="0"> 101 | <Group type="103" groupAlignment="3" attributes="0"> 102 | <Component id="jButtonLoad" alignment="3" min="-2" max="-2" attributes="0"/> 103 | <Component id="jButtonSave" alignment="3" min="-2" max="-2" attributes="0"/> 104 | <Component id="jLabel2" alignment="3" min="-2" max="-2" attributes="0"/> 105 | <Component id="jButtonTestAll" alignment="3" min="-2" max="-2" attributes="0"/> 106 | </Group> 107 | <EmptySpace max="32767" attributes="0"/> 108 | <Component id="jLabelFile" min="-2" pref="25" max="-2" attributes="0"/> 109 | </Group> 110 | </Group> 111 | <EmptySpace min="-2" pref="10" max="-2" attributes="0"/> 112 | <Group type="103" groupAlignment="0" attributes="0"> 113 | <Component id="jLabel3" min="-2" max="-2" attributes="0"/> 114 | <Component id="jScrollPane1" min="-2" max="-2" attributes="0"/> 115 | </Group> 116 | <EmptySpace max="-2" attributes="0"/> 117 | <Group type="103" groupAlignment="0" attributes="0"> 118 | <Component id="jLabel4" min="-2" max="-2" attributes="0"/> 119 | <Component id="jScrollPaneHosts" min="-2" pref="63" max="-2" attributes="0"/> 120 | </Group> 121 | <EmptySpace max="-2" attributes="0"/> 122 | <Group type="103" groupAlignment="3" attributes="0"> 123 | <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/> 124 | <Component id="jComboBoxLoginMacro" alignment="3" min="-2" max="-2" attributes="0"/> 125 | <Component id="jButtonLogin" alignment="3" min="-2" max="-2" attributes="0"/> 126 | <Component id="jButtonCookieJar" alignment="3" min="-2" max="-2" attributes="0"/> 127 | <Component id="jButtonTrace" alignment="3" min="-2" max="-2" attributes="0"/> 128 | </Group> 129 | <EmptySpace max="-2" attributes="0"/> 130 | <Component id="jPanelIssue" min="-2" max="-2" attributes="0"/> 131 | </Group> 132 | </Group> 133 | </DimensionLayout> 134 | </Layout> 135 | <SubComponents> 136 | <Component class="javax.swing.JLabel" name="jLabel1"> 137 | <Properties> 138 | <Property name="text" type="java.lang.String" value="Login macro"/> 139 | </Properties> 140 | </Component> 141 | <Component class="javax.swing.JComboBox" name="jComboBoxLoginMacro"> 142 | <Properties> 143 | <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor"> 144 | <StringArray count="0"/> 145 | </Property> 146 | </Properties> 147 | <Events> 148 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jComboBoxLoginMacroActionPerformed"/> 149 | </Events> 150 | <AuxValues> 151 | <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/> 152 | </AuxValues> 153 | </Component> 154 | <Component class="javax.swing.JButton" name="jButtonLogin"> 155 | <Properties> 156 | <Property name="text" type="java.lang.String" value="Login"/> 157 | <Property name="enabled" type="boolean" value="false"/> 158 | </Properties> 159 | <Events> 160 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonLoginActionPerformed"/> 161 | </Events> 162 | </Component> 163 | <Component class="javax.swing.JLabel" name="jLabel2"> 164 | <Properties> 165 | <Property name="text" type="java.lang.String" value="Replicator file"/> 166 | </Properties> 167 | </Component> 168 | <Component class="javax.swing.JButton" name="jButtonLoad"> 169 | <Properties> 170 | <Property name="text" type="java.lang.String" value="Load"/> 171 | </Properties> 172 | <Events> 173 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonLoadActionPerformed"/> 174 | </Events> 175 | </Component> 176 | <Component class="javax.swing.JButton" name="jButtonSave"> 177 | <Properties> 178 | <Property name="text" type="java.lang.String" value="Save"/> 179 | </Properties> 180 | <Events> 181 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonSaveActionPerformed"/> 182 | </Events> 183 | </Component> 184 | <Component class="javax.swing.JLabel" name="jLabel3"> 185 | <Properties> 186 | <Property name="text" type="java.lang.String" value="Tester notes"/> 187 | </Properties> 188 | </Component> 189 | <Container class="javax.swing.JScrollPane" name="jScrollPane1"> 190 | <AuxValues> 191 | <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/> 192 | </AuxValues> 193 | 194 | <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> 195 | <SubComponents> 196 | <Component class="javax.swing.JTextArea" name="jNotes"> 197 | <Properties> 198 | <Property name="columns" type="int" value="20"/> 199 | <Property name="rows" type="int" value="5"/> 200 | </Properties> 201 | <AuxValues> 202 | <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> 203 | </AuxValues> 204 | </Component> 205 | </SubComponents> 206 | </Container> 207 | <Component class="javax.swing.JLabel" name="jLabelFile"> 208 | </Component> 209 | <Component class="javax.swing.JLabel" name="jLabel4"> 210 | <Properties> 211 | <Property name="text" type="java.lang.String" value="Hosts"/> 212 | </Properties> 213 | </Component> 214 | <Container class="javax.swing.JScrollPane" name="jScrollPaneHosts"> 215 | <AuxValues> 216 | <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/> 217 | </AuxValues> 218 | 219 | <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> 220 | <SubComponents> 221 | <Component class="javax.swing.JTable" name="jTableHosts"> 222 | <Properties> 223 | <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor"> 224 | <Table columnCount="1" rowCount="0"> 225 | <Column editable="true" title="Host" type="java.lang.String"/> 226 | </Table> 227 | </Property> 228 | <Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor"> 229 | <TableColumnModel selectionModel="0"> 230 | <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> 231 | <Title/> 232 | <Editor/> 233 | <Renderer/> 234 | </Column> 235 | </TableColumnModel> 236 | </Property> 237 | <Property name="surrendersFocusOnKeystroke" type="boolean" value="true"/> 238 | <Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> 239 | <Connection code="null" type="code"/> 240 | </Property> 241 | </Properties> 242 | </Component> 243 | </SubComponents> 244 | </Container> 245 | <Component class="javax.swing.JLabel" name="jLabelLogo"> 246 | <Properties> 247 | <Property name="horizontalAlignment" type="int" value="4"/> 248 | </Properties> 249 | <Events> 250 | <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="jLabelLogoMouseClicked"/> 251 | </Events> 252 | </Component> 253 | <Container class="javax.swing.JPanel" name="jPanelIssue"> 254 | 255 | <Layout> 256 | <DimensionLayout dim="0"> 257 | <Group type="103" groupAlignment="0" attributes="0"> 258 | <Group type="102" attributes="0"> 259 | <Group type="103" groupAlignment="0" attributes="0"> 260 | <Group type="102" alignment="0" attributes="0"> 261 | <Group type="103" groupAlignment="0" attributes="0"> 262 | <Component id="jLabel8" min="-2" max="-2" attributes="0"/> 263 | <Component id="jLabel6" alignment="0" min="-2" max="-2" attributes="0"/> 264 | </Group> 265 | <Group type="103" groupAlignment="0" attributes="0"> 266 | <Group type="102" attributes="0"> 267 | <EmptySpace min="-2" pref="24" max="-2" attributes="0"/> 268 | <Component id="jRadioButtonGrep" min="-2" max="-2" attributes="0"/> 269 | <EmptySpace type="unrelated" max="-2" attributes="0"/> 270 | <Component id="jRadioButtonCollaborator" min="-2" max="-2" attributes="0"/> 271 | <EmptySpace min="0" pref="0" max="32767" attributes="0"/> 272 | </Group> 273 | <Group type="102" attributes="0"> 274 | <EmptySpace min="-2" pref="20" max="-2" attributes="0"/> 275 | <Component id="jScrollPane2" max="32767" attributes="0"/> 276 | </Group> 277 | </Group> 278 | </Group> 279 | <Group type="102" attributes="0"> 280 | <Group type="103" groupAlignment="0" attributes="0"> 281 | <Group type="102" attributes="0"> 282 | <Group type="103" groupAlignment="0" attributes="0"> 283 | <Component id="jLabelGrep" alignment="0" min="-2" max="-2" attributes="0"/> 284 | <Component id="jLabel5" alignment="0" min="-2" max="-2" attributes="0"/> 285 | <Component id="jLabelCollaborator" alignment="0" min="-2" max="-2" attributes="0"/> 286 | </Group> 287 | <EmptySpace type="separate" max="-2" attributes="0"/> 288 | <Group type="103" groupAlignment="0" attributes="0"> 289 | <Component id="jTextFieldSessionRules" alignment="0" min="-2" pref="318" max="-2" attributes="0"/> 290 | <Group type="102" alignment="0" attributes="0"> 291 | <Group type="103" groupAlignment="1" max="-2" attributes="0"> 292 | <Component id="jTextFieldCollaboratorReplace" alignment="0" max="32767" attributes="0"/> 293 | <Component id="jTextFieldGrepExpression" alignment="0" pref="184" max="32767" attributes="0"/> 294 | </Group> 295 | <Group type="103" groupAlignment="0" max="-2" attributes="0"> 296 | <Group type="102" attributes="0"> 297 | <EmptySpace max="-2" attributes="0"/> 298 | <Component id="jButtonUseExpression" min="-2" max="-2" attributes="0"/> 299 | </Group> 300 | <Group type="102" alignment="0" attributes="0"> 301 | <EmptySpace min="-2" pref="22" max="-2" attributes="0"/> 302 | <Component id="jLabelCollaboratorTimeout" min="-2" max="-2" attributes="0"/> 303 | <EmptySpace type="unrelated" max="-2" attributes="0"/> 304 | <Component id="jTextFieldCollaboratorTimeout" max="32767" attributes="0"/> 305 | </Group> 306 | </Group> 307 | </Group> 308 | </Group> 309 | </Group> 310 | <Group type="102" alignment="0" attributes="0"> 311 | <Component id="jButtonTest" min="-2" max="-2" attributes="0"/> 312 | <EmptySpace max="-2" attributes="0"/> 313 | <Component id="jButtonClearStatus" min="-2" max="-2" attributes="0"/> 314 | <EmptySpace max="-2" attributes="0"/> 315 | <Component id="jButtonDelete" min="-2" max="-2" attributes="0"/> 316 | <EmptySpace max="-2" attributes="0"/> 317 | <Component id="jButtonScrub" min="-2" max="-2" attributes="0"/> 318 | <EmptySpace max="-2" attributes="0"/> 319 | <Component id="jButtonConfig" min="-2" max="-2" attributes="0"/> 320 | </Group> 321 | </Group> 322 | <EmptySpace min="0" pref="19" max="32767" attributes="0"/> 323 | </Group> 324 | </Group> 325 | <EmptySpace max="-2" attributes="0"/> 326 | </Group> 327 | </Group> 328 | </DimensionLayout> 329 | <DimensionLayout dim="1"> 330 | <Group type="103" groupAlignment="0" attributes="0"> 331 | <Group type="102" alignment="0" attributes="0"> 332 | <EmptySpace max="-2" attributes="0"/> 333 | <Group type="103" groupAlignment="0" attributes="0"> 334 | <Component id="jScrollPane2" min="-2" pref="50" max="-2" attributes="0"/> 335 | <Component id="jLabel6" min="-2" max="-2" attributes="0"/> 336 | </Group> 337 | <EmptySpace max="-2" attributes="0"/> 338 | <Group type="103" groupAlignment="3" attributes="0"> 339 | <Component id="jLabel8" alignment="3" min="-2" max="-2" attributes="0"/> 340 | <Component id="jRadioButtonGrep" alignment="3" min="-2" max="-2" attributes="0"/> 341 | <Component id="jRadioButtonCollaborator" alignment="3" min="-2" max="-2" attributes="0"/> 342 | </Group> 343 | <EmptySpace max="-2" attributes="0"/> 344 | <Group type="103" groupAlignment="3" attributes="0"> 345 | <Component id="jLabelGrep" alignment="3" min="-2" max="-2" attributes="0"/> 346 | <Component id="jTextFieldGrepExpression" alignment="3" min="-2" max="-2" attributes="0"/> 347 | <Component id="jButtonUseExpression" alignment="3" min="-2" max="-2" attributes="0"/> 348 | </Group> 349 | <EmptySpace max="-2" attributes="0"/> 350 | <Group type="103" groupAlignment="3" attributes="0"> 351 | <Component id="jLabelCollaborator" alignment="3" min="-2" max="-2" attributes="0"/> 352 | <Component id="jTextFieldCollaboratorReplace" alignment="3" min="-2" max="-2" attributes="0"/> 353 | <Component id="jLabelCollaboratorTimeout" alignment="3" min="-2" max="-2" attributes="0"/> 354 | <Component id="jTextFieldCollaboratorTimeout" alignment="3" min="-2" max="-2" attributes="0"/> 355 | </Group> 356 | <EmptySpace max="-2" attributes="0"/> 357 | <Group type="103" groupAlignment="3" attributes="0"> 358 | <Component id="jTextFieldSessionRules" alignment="3" min="-2" max="-2" attributes="0"/> 359 | <Component id="jLabel5" alignment="3" min="-2" max="-2" attributes="0"/> 360 | </Group> 361 | <EmptySpace max="-2" attributes="0"/> 362 | <Group type="103" groupAlignment="3" attributes="0"> 363 | <Component id="jButtonTest" alignment="3" min="-2" max="-2" attributes="0"/> 364 | <Component id="jButtonClearStatus" alignment="3" min="-2" max="-2" attributes="0"/> 365 | <Component id="jButtonScrub" alignment="3" min="-2" max="-2" attributes="0"/> 366 | <Component id="jButtonDelete" alignment="3" min="-2" max="-2" attributes="0"/> 367 | <Component id="jButtonConfig" alignment="3" min="-2" max="-2" attributes="0"/> 368 | </Group> 369 | <EmptySpace max="32767" attributes="0"/> 370 | </Group> 371 | </Group> 372 | </DimensionLayout> 373 | </Layout> 374 | <SubComponents> 375 | <Component class="javax.swing.JTextField" name="jTextFieldSessionRules"> 376 | <Properties> 377 | <Property name="editable" type="boolean" value="false"/> 378 | </Properties> 379 | </Component> 380 | <Component class="javax.swing.JButton" name="jButtonTest"> 381 | <Properties> 382 | <Property name="text" type="java.lang.String" value="Test"/> 383 | </Properties> 384 | <Events> 385 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonTestActionPerformed"/> 386 | </Events> 387 | </Component> 388 | <Component class="javax.swing.JButton" name="jButtonDelete"> 389 | <Properties> 390 | <Property name="text" type="java.lang.String" value="Delete"/> 391 | </Properties> 392 | <Events> 393 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonDeleteActionPerformed"/> 394 | </Events> 395 | </Component> 396 | <Component class="javax.swing.JButton" name="jButtonClearStatus"> 397 | <Properties> 398 | <Property name="text" type="java.lang.String" value="Clear status"/> 399 | </Properties> 400 | <Events> 401 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonClearStatusActionPerformed"/> 402 | </Events> 403 | </Component> 404 | <Component class="javax.swing.JLabel" name="jLabel8"> 405 | <Properties> 406 | <Property name="text" type="java.lang.String" value="Detection mode"/> 407 | </Properties> 408 | </Component> 409 | <Container class="javax.swing.JScrollPane" name="jScrollPane2"> 410 | <AuxValues> 411 | <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/> 412 | </AuxValues> 413 | 414 | <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> 415 | <SubComponents> 416 | <Component class="javax.swing.JTextArea" name="jNotes1"> 417 | <Properties> 418 | <Property name="columns" type="int" value="20"/> 419 | <Property name="rows" type="int" value="2"/> 420 | </Properties> 421 | </Component> 422 | </SubComponents> 423 | </Container> 424 | <Component class="javax.swing.JLabel" name="jLabelGrep"> 425 | <Properties> 426 | <Property name="text" type="java.lang.String" value="Grep expression"/> 427 | </Properties> 428 | </Component> 429 | <Component class="javax.swing.JTextField" name="jTextFieldGrepExpression"> 430 | <Events> 431 | <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="jTextFieldGrepExpressionFocusLost"/> 432 | </Events> 433 | </Component> 434 | <Component class="javax.swing.JButton" name="jButtonUseExpression"> 435 | <Properties> 436 | <Property name="text" type="java.lang.String" value="Use selection"/> 437 | </Properties> 438 | <Events> 439 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonUseExpressionActionPerformed"/> 440 | </Events> 441 | </Component> 442 | <Component class="javax.swing.JButton" name="jButtonScrub"> 443 | <Properties> 444 | <Property name="text" type="java.lang.String" value="Scrub cookies..."/> 445 | </Properties> 446 | <Events> 447 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonScrubActionPerformed"/> 448 | </Events> 449 | </Component> 450 | <Component class="javax.swing.JLabel" name="jLabel5"> 451 | <Properties> 452 | <Property name="text" type="java.lang.String" value="Session rules"/> 453 | </Properties> 454 | </Component> 455 | <Component class="javax.swing.JRadioButton" name="jRadioButtonGrep"> 456 | <Properties> 457 | <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor"> 458 | <ComponentRef name="buttonGroup1"/> 459 | </Property> 460 | <Property name="selected" type="boolean" value="true"/> 461 | <Property name="text" type="java.lang.String" value="Grep expression"/> 462 | </Properties> 463 | <Events> 464 | <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="jRadioButtonGrepItemStateChanged"/> 465 | </Events> 466 | </Component> 467 | <Component class="javax.swing.JLabel" name="jLabel6"> 468 | <Properties> 469 | <Property name="text" type="java.lang.String" value="Issue notes"/> 470 | </Properties> 471 | </Component> 472 | <Component class="javax.swing.JRadioButton" name="jRadioButtonCollaborator"> 473 | <Properties> 474 | <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor"> 475 | <ComponentRef name="buttonGroup1"/> 476 | </Property> 477 | <Property name="text" type="java.lang.String" value="Collaborator"/> 478 | </Properties> 479 | <Events> 480 | <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="jRadioButtonCollaboratorItemStateChanged"/> 481 | </Events> 482 | </Component> 483 | <Component class="javax.swing.JLabel" name="jLabelCollaborator"> 484 | <Properties> 485 | <Property name="text" type="java.lang.String" value="Collab. replace"/> 486 | </Properties> 487 | </Component> 488 | <Component class="javax.swing.JTextField" name="jTextFieldCollaboratorReplace"> 489 | </Component> 490 | <Component class="javax.swing.JLabel" name="jLabelCollaboratorTimeout"> 491 | <Properties> 492 | <Property name="text" type="java.lang.String" value="Timeout"/> 493 | </Properties> 494 | </Component> 495 | <Component class="javax.swing.JTextField" name="jTextFieldCollaboratorTimeout"> 496 | <Properties> 497 | <Property name="horizontalAlignment" type="int" value="4"/> 498 | </Properties> 499 | </Component> 500 | <Component class="javax.swing.JButton" name="jButtonConfig"> 501 | <Properties> 502 | <Property name="text" type="java.lang.String" value="Config..."/> 503 | </Properties> 504 | <Events> 505 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonConfigActionPerformed"/> 506 | </Events> 507 | </Component> 508 | </SubComponents> 509 | </Container> 510 | <Component class="javax.swing.JButton" name="jButtonTestAll"> 511 | <Properties> 512 | <Property name="text" type="java.lang.String" value="Test all"/> 513 | </Properties> 514 | <Events> 515 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonTestAllActionPerformed"/> 516 | </Events> 517 | </Component> 518 | <Component class="javax.swing.JButton" name="jButtonCookieJar"> 519 | <Properties> 520 | <Property name="text" type="java.lang.String" value="Cookie jar..."/> 521 | </Properties> 522 | <Events> 523 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonCookieJarActionPerformed"/> 524 | </Events> 525 | </Component> 526 | <Component class="javax.swing.JButton" name="jButtonTrace"> 527 | <Properties> 528 | <Property name="text" type="java.lang.String" value="Start trace"/> 529 | </Properties> 530 | <Events> 531 | <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonTraceActionPerformed"/> 532 | </Events> 533 | </Component> 534 | </SubComponents> 535 | </Form> 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<TraceItem> 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<int[]> 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<int[]> 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<Rule> 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<String, JSONObject> 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<String> getRulesForRequest(IRequestInfo request) 103 | { 104 | List<String> 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<IParameter> params) throws JSONException 178 | { 179 | List<String> 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<String> 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<Long> 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<String> 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 --------------------------------------------------------------------------------