├── .gitignore ├── settings.gradle ├── distributeDamage.jar ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── BappManifest.bmf ├── README.md ├── BappDescription.html ├── LICENSE ├── src └── burp │ ├── Throttler.java │ ├── BurpExtender.java │ └── Utilities.java ├── gradlew.bat └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'distribute-damage' 2 | -------------------------------------------------------------------------------- /distributeDamage.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/distribute-damage/HEAD/distributeDamage.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/distribute-damage/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: 543ab7a08d954390bd1a5f4253d3763b 2 | ExtensionType: 1 3 | Name: Distribute Damage 4 | RepoName: distribute-damage 5 | ScreenVersion: 1.01 6 | SerialVersion: 13 7 | MinPlatformVersion: 0 8 | ProOnly: True 9 | Author: James Kettle, PortSwigger 10 | ShortDescription: Evenly distributes scanner load across targets. 11 | EntryPoint: build/libs/distribute-damage-all.jar 12 | BuildCommand: ./gradlew fatJar 13 | SupportedProducts: Pro 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Designed to make Burp evenly distribute load across multiple scanner targets, this extension introduces a per-host throttle, and a context menu to trigger scans from. It may also come in useful for avoiding detection. 2 | 3 | For more information, please refer to the whitepaper at http://blog.portswigger.net/2016/11/backslash-powered-scanning-hunting.html 4 | 5 | The code can be found at https://github.com/portswigger/distribute-damage 6 | 7 | Contributions and feature requests are welcome. 8 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

Designed to make Burp evenly distribute load across multiple scanner targets, this extension introduces a per-host throttle, a global pause button, 2 | and a context menu to trigger scans from. It may also come in useful for avoiding detection.

3 | 4 |

For more information, please refer to the whitepaper at http://blog.portswigger.net/2016/11/backslash-powered-scanning-hunting.html

5 | 6 |

Contributions and feature requests are welcome.

7 | 8 |

Requires Java version 8.

9 | 10 |

Copyright © 2016-2023 PortSwigger Ltd.

11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 PortSwigger Web Security 2 | Copyright 2016 James Kettle 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /src/burp/Throttler.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | import java.util.concurrent.locks.Lock; 6 | 7 | class Throttler implements IHttpListener { 8 | private HashMap locks = new HashMap<>(); 9 | String instanceCacheBust; 10 | 11 | Throttler() { 12 | instanceCacheBust = Utilities.generateCanary(); 13 | } 14 | 15 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { 16 | Lock spiderLock = null; 17 | if (toolFlag == IBurpExtenderCallbacks.TOOL_SPIDER) { 18 | spiderLock = Utilities.spiderLock.readLock(); 19 | spiderLock.lock(); 20 | } 21 | try { 22 | if (messageIsRequest && Utilities.THROTTLED_COMPONENTS.contains(toolFlag)) { 23 | String hostname = messageInfo.getHttpService().getHost(); 24 | delayRequest(hostname); 25 | } 26 | } 27 | finally { 28 | if (spiderLock != null) { 29 | spiderLock.unlock(); 30 | } 31 | } 32 | } 33 | 34 | private void suspendRequest() { 35 | while (Utilities.globalSettings.getBoolean("pause all traffic")) { 36 | try { 37 | Thread.sleep(Utilities.globalSettings.getInt("throttle")); 38 | } catch (java.lang.InterruptedException e) { 39 | Utilities.err("Interrupted while sleeping, aborting suspension"); 40 | return; 41 | } 42 | } 43 | } 44 | 45 | public void delayRequest(String hostname){ 46 | if (hostname.equals("bwapps") || hostname.equals("labs-linux")) { 47 | return; 48 | } 49 | 50 | suspendRequest(); 51 | 52 | synchronized(hostname.intern()) { 53 | if (locks.containsKey(hostname)) { 54 | long waitFor = Utilities.globalSettings.getInt("throttle") - (new Date().getTime() - locks.get(hostname)); 55 | if (waitFor > 0) { 56 | try { 57 | Thread.sleep(waitFor); 58 | } catch (java.lang.InterruptedException e) { 59 | Utilities.err("Interrupted while sleeping"); 60 | } 61 | } 62 | } 63 | locks.put(hostname, new Date().getTime()); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /src/burp/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | import burp.*; 3 | 4 | import java.awt.event.ActionListener; 5 | import java.awt.event.ItemListener; 6 | import java.io.FileNotFoundException; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.URL; 9 | import java.util.*; 10 | import java.io.PrintWriter; 11 | import java.util.List; 12 | 13 | import javax.swing.*; 14 | import java.awt.event.ActionEvent; 15 | import java.awt.event.ItemEvent; 16 | import java.util.concurrent.locks.Lock; 17 | import java.util.stream.Collectors; 18 | 19 | public class BurpExtender implements IBurpExtender { 20 | private static final String name = "distributeDamage"; 21 | private static final String version = "1.01"; 22 | public static final boolean clientSideOnly = false; 23 | public static HashSet scanned = new HashSet<>(); 24 | 25 | @Override 26 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) { 27 | new Utilities(callbacks); 28 | Utilities.out("Loaded " + name + " v" + version ); 29 | SwingUtilities.invokeLater(new ConfigMenu()); 30 | Utilities.globalSettings.printSettings(); 31 | callbacks.setExtensionName(name); 32 | callbacks.registerHttpListener(new Throttler()); 33 | callbacks.registerContextMenuFactory(new OfferDistributedScan(callbacks)); 34 | } 35 | } 36 | 37 | class OfferDistributedScan implements IContextMenuFactory { 38 | 39 | private IBurpExtenderCallbacks callbacks; 40 | 41 | public OfferDistributedScan(final IBurpExtenderCallbacks callbacks) { 42 | this.callbacks = callbacks; 43 | } 44 | 45 | @Override 46 | public List createMenuItems(IContextMenuInvocation invocation) { 47 | List options = new ArrayList<>(); 48 | JMenuItem button = new JMenuItem("Distribute Damage: Launch distributed active scan on " + invocation.getSelectedMessages().length + " items"); 49 | // button.addActionListener(new TriggerDistributedAction(callbacks, invocation, (request, callbacks) -> new ScannerDripFeeder(request, callbacks))); 50 | button.addActionListener(new TriggerDistributedAction(callbacks, invocation, ScannerDripFeeder::new)); 51 | options.add(button); 52 | 53 | JMenuItem passive_button = new JMenuItem("Distribute Damage: Launch passive scan on " + invocation.getSelectedMessages().length + " items"); 54 | passive_button.addActionListener(new TriggerDistributedAction(callbacks, invocation, PassiveScanner::new)); 55 | options.add(passive_button); 56 | 57 | /*JMenuItem spider_button = new JMenuItem("Distribute Damage: Launch distributed spider on " + invocation.getSelectedMessages().length + " items"); 58 | spider_button.addActionListener(new TriggerDistributedAction(callbacks, invocation, SpiderDripFeeder::new)); 59 | options.add(spider_button);*/ 60 | 61 | JMenuItem extract_button = new JMenuItem("Distribute Damage: Extract unfetched URLs of " + invocation.getSelectedMessages().length + " items"); 62 | extract_button.addActionListener(new TriggerDistributedAction(callbacks, invocation, ExtractToFile::new)); 63 | options.add(extract_button); 64 | 65 | return options; 66 | } 67 | } 68 | 69 | @FunctionalInterface 70 | interface ProcessorFactory 71 | { 72 | Runnable create(IHttpRequestResponse[] requestResponses, IBurpExtenderCallbacks callbacks); 73 | } 74 | 75 | 76 | class TriggerDistributedAction implements ActionListener, ItemListener { 77 | private IContextMenuInvocation invocation; 78 | private IBurpExtenderCallbacks callbacks; 79 | private ProcessorFactory processor; 80 | 81 | public TriggerDistributedAction(final IBurpExtenderCallbacks callbacks, IContextMenuInvocation invocation, ProcessorFactory processor) { 82 | this.callbacks = callbacks; 83 | this.invocation = invocation; 84 | this.processor = processor; 85 | } 86 | 87 | public void actionPerformed(ActionEvent e) { 88 | IHttpRequestResponse[] requests = invocation.getSelectedMessages(); 89 | Runnable runnable = processor.create(requests, callbacks); 90 | (new Thread(runnable)).start(); 91 | } 92 | 93 | public void itemStateChanged(ItemEvent e) { 94 | 95 | } 96 | } 97 | 98 | abstract class InterruptableTask implements Runnable, IExtensionStateListener { 99 | protected IHttpRequestResponse[] requests; 100 | protected IBurpExtenderCallbacks callbacks; 101 | protected boolean unloaded = false; 102 | protected boolean completed = false; 103 | 104 | public InterruptableTask(IHttpRequestResponse[] requests, final IBurpExtenderCallbacks callbacks) { 105 | this.requests = requests; 106 | this.callbacks = callbacks; 107 | callbacks.registerExtensionStateListener(this); 108 | } 109 | 110 | public void extensionUnloaded() { 111 | if (!completed) { 112 | Utilities.log("Extension unloading - triggering abort"); 113 | unloaded = true; 114 | Thread.currentThread().interrupt(); 115 | } 116 | } 117 | 118 | } 119 | 120 | class PassiveScanner extends InterruptableTask { 121 | 122 | public PassiveScanner(IHttpRequestResponse[] requests, final IBurpExtenderCallbacks callbacks) { 123 | super(requests, callbacks); 124 | } 125 | 126 | public void run() { 127 | int i = 0; 128 | for (IHttpRequestResponse req: requests) { 129 | i += 1; 130 | 131 | if (req.getResponse() != null && req.getResponse().length < 4000000) { 132 | IHttpService service = req.getHttpService(); 133 | boolean using_https = service.getProtocol().equals("https"); 134 | String host = service.getHost(); 135 | callbacks.doPassiveScan(host, service.getPort(), using_https, req.getRequest(), req.getResponse()); 136 | } 137 | 138 | if(i % 1000 == 0) { 139 | Utilities.log(i + " of " + requests.length + " items processed"); 140 | } 141 | } 142 | } 143 | } 144 | 145 | abstract class DamageDistributer extends InterruptableTask { 146 | 147 | public DamageDistributer(IHttpRequestResponse[] requests, final IBurpExtenderCallbacks callbacks) { 148 | super(requests, callbacks); 149 | } 150 | 151 | public void run() { 152 | HashMap> itemsByHost = splitItemsByHost(); 153 | distributeWork(itemsByHost); 154 | completed = true; 155 | requests = null; 156 | } 157 | 158 | abstract HashMap> splitItemsByHost(); 159 | 160 | abstract void launchTask(WorkTarget item); 161 | 162 | protected void distributeWork(HashMap> itemsByHost) { 163 | 164 | int launched = 0; 165 | Set hosts = itemsByHost.keySet(); 166 | while (!hosts.isEmpty()) { 167 | Iterator hostIterator = hosts.iterator(); 168 | while (hostIterator.hasNext()) { 169 | String host = hostIterator.next(); 170 | ArrayDeque host_queue = itemsByHost.get(host); 171 | if (host_queue.isEmpty()) { 172 | hostIterator.remove(); 173 | continue; 174 | } 175 | 176 | launchTask(itemsByHost.get(host).pop()); 177 | 178 | if (unloaded) { 179 | Utilities.log("Scan feed interrupted by extension unload, aborting"); 180 | return; 181 | } 182 | launched +=1 ; 183 | } 184 | } 185 | Utilities.log("Launched " + launched + " tasks"); 186 | } 187 | } 188 | 189 | 190 | class ScannerDripFeeder extends DamageDistributer { 191 | 192 | public ScannerDripFeeder(IHttpRequestResponse[] requests, final IBurpExtenderCallbacks callbacks) { 193 | super(requests, callbacks); 194 | } 195 | 196 | public HashMap> splitItemsByHost() { 197 | Collections.shuffle(Arrays.asList(requests)); 198 | 199 | 200 | HashSet mimetypes = new HashSet<>(); 201 | mimetypes.addAll(Arrays.asList(Utilities.globalSettings.getString("header target mime types").split(","))); 202 | HashSet statuscodes = new HashSet<>(); 203 | statuscodes.addAll(Arrays.asList(Utilities.globalSettings.getString("header target status codes").split(","))); 204 | 205 | HashMap> scanItemsByHost = new HashMap<>(); 206 | int i = 0; 207 | for (IHttpRequestResponse request : requests) { 208 | 209 | i += 1; 210 | if(i % 1000 == 0) { 211 | Utilities.log(i + " of " + requests.length + " items processed"); 212 | } 213 | 214 | String host = request.getHttpService().getHost(); 215 | IRequestInfo info = callbacks.getHelpers().analyzeRequest(request); 216 | String request_id = host; 217 | if (Utilities.globalSettings.getBoolean("include content type in key")) { 218 | request_id += info.getContentType(); 219 | } 220 | 221 | List params = new ArrayList<>(); 222 | if (Utilities.globalSettings.getBoolean("scan params")) { 223 | params.addAll(info.getParameters()); 224 | } 225 | 226 | String param_names = params.stream().map(IParameter::getName).collect(Collectors.toList()).toString(); 227 | 228 | boolean suitableForPerHostScans = true; 229 | 230 | // only scan 'extra insertion points' once per host/status-code/mimetype 231 | byte[] response = request.getResponse(); 232 | 233 | // if (response != null) { 234 | // IResponseInfo respInfo = callbacks.getHelpers().analyzeResponse(response); 235 | // String mime_type = respInfo.getStatedMimeType(); 236 | // request_id += respInfo.getStatusCode() + mime_type; 237 | // 238 | // if (mimetypes.contains(mime_type) && statuscodes.contains(String.valueOf(respInfo.getStatusCode()))) { 239 | // suitableForPerHostScans = true; 240 | // if (!BurpExtender.scanned.contains(request_id+"Host")) { 241 | // BurpExtender.scanned.add(request_id+"Host"); 242 | // params.addAll(Utilities.getExtraInsertionPoints(request.getRequest())); 243 | // } 244 | // } 245 | // } 246 | 247 | params.addAll(Utilities.getExtraInsertionPoints(request.getRequest())); 248 | 249 | List insertionPoints = new ArrayList<>(); 250 | for (IParameter param: params) { 251 | 252 | byte type = param.getType(); 253 | String param_id; 254 | if (type == IParameter.PARAM_COOKIE) { 255 | if (!suitableForPerHostScans || !Utilities.globalSettings.getBoolean("scan cookies")) { 256 | continue; 257 | } 258 | param_id = request_id+'_'+param.getName(); 259 | } 260 | else if (type == PartialParam.PARAM_PATH && suitableForPerHostScans) { 261 | param_id = request_id+'_'+param.getName(); 262 | } 263 | else { 264 | param_id = request_id+'_'+param_names+param.getType()+'_'+param.getName(); // + info.getUrl().getPath(); 265 | } 266 | 267 | if (param.getName().length() > Utilities.globalSettings.getInt("max param length")) { 268 | continue; 269 | } 270 | 271 | if (!BurpExtender.scanned.contains(param_id)) { 272 | insertionPoints.add(new int[]{param.getValueStart(),param.getValueEnd()}); 273 | BurpExtender.scanned.add(param_id); 274 | } 275 | } 276 | 277 | if (insertionPoints.isEmpty()) { 278 | continue; 279 | } 280 | 281 | if (scanItemsByHost.containsKey(host)) { 282 | scanItemsByHost.get(host).add(new WorkTarget(request, insertionPoints)); 283 | } 284 | else { 285 | ArrayDeque newQueue = new ArrayDeque<>(); 286 | newQueue.add(new WorkTarget(request, insertionPoints)); 287 | scanItemsByHost.put(host, newQueue); 288 | } 289 | } 290 | 291 | return scanItemsByHost; 292 | } 293 | 294 | public void launchTask(WorkTarget next) { 295 | IHttpRequestResponse itemToScanNext = next.req; 296 | IHttpService service = itemToScanNext.getHttpService(); 297 | boolean using_https = service.getProtocol().equals("https"); 298 | String host = service.getHost(); 299 | 300 | IScanQueueItem scanItem = callbacks.doActiveScan(host, service.getPort(), using_https, itemToScanNext.getRequest(), next.offsets); 301 | // Utilities.log("Launched scan on "+itemToScanNext.getHttpService().getHost()); 302 | if (Utilities.THROTTLE_SCANITEM_CREATION) { 303 | while ( scanItem.getStatus().equals("waiting")) { 304 | try { 305 | Thread.sleep(50); 306 | } catch (InterruptedException z) { 307 | Utilities.log("Scan feed interrupted, aborting"); 308 | return; 309 | } 310 | } 311 | } 312 | } 313 | 314 | } 315 | 316 | class SpiderDripFeeder extends DamageDistributer { 317 | 318 | public SpiderDripFeeder(IHttpRequestResponse[] requests, final IBurpExtenderCallbacks callbacks) { 319 | super(requests, callbacks); 320 | } 321 | 322 | public void run() { 323 | HashMap> itemsByHost = splitItemsByHost(); 324 | Lock haltSpider = Utilities.spiderLock.writeLock(); 325 | haltSpider.lock(); 326 | try { 327 | distributeWork(itemsByHost); 328 | } finally { 329 | haltSpider.unlock(); 330 | } 331 | completed = true; 332 | requests = null; 333 | } 334 | 335 | public HashMap> splitItemsByHost() { 336 | HashMap> scanItemsByHost = new HashMap<>(); 337 | int i = 0; 338 | for (IHttpRequestResponse request : requests) { 339 | 340 | i += 1; 341 | if(i % 1000 == 0) { 342 | Utilities.log(i + " of " + requests.length + " items processed"); 343 | } 344 | 345 | if (request.getResponse() != null ) { 346 | continue; 347 | } 348 | 349 | String host = request.getHttpService().getHost(); 350 | 351 | if (scanItemsByHost.containsKey(host)) { 352 | scanItemsByHost.get(host).add(new WorkTarget(Utilities.getURL(request))); 353 | } 354 | else { 355 | ArrayDeque newQueue = new ArrayDeque<>(); 356 | newQueue.add(new WorkTarget(Utilities.getURL(request))); 357 | scanItemsByHost.put(host, newQueue); 358 | } 359 | } 360 | 361 | return scanItemsByHost; 362 | } 363 | 364 | 365 | public void launchTask(WorkTarget next) { 366 | try { 367 | Thread.sleep(50); 368 | callbacks.sendToSpider(next.url); 369 | } catch (InterruptedException z) { 370 | Utilities.log("Scan feed interrupted, aborting"); 371 | } 372 | } 373 | } 374 | 375 | class ExtractToFile extends SpiderDripFeeder { 376 | private PrintWriter to_spider; 377 | 378 | public ExtractToFile(IHttpRequestResponse[] requests, final IBurpExtenderCallbacks callbacks) { 379 | super(requests, callbacks); 380 | } 381 | 382 | public void run() { 383 | HashMap> itemsByHost = splitItemsByHost(); 384 | try { 385 | to_spider = new PrintWriter("to_spider", "UTF-8"); 386 | Utilities.out("File will be created at "+System.getProperty("user.dir")+"/to_spider"); 387 | distributeWork(itemsByHost); 388 | } catch (FileNotFoundException e) { 389 | Utilities.err(e.getMessage()); 390 | } catch (UnsupportedEncodingException e) { 391 | Utilities.err(e.getMessage()); 392 | } finally { 393 | to_spider.close(); 394 | } 395 | completed = true; 396 | requests = null; 397 | } 398 | 399 | public void launchTask(WorkTarget next) { 400 | to_spider.println(Utilities.sensibleURL(next.url)); 401 | } 402 | 403 | } 404 | 405 | class WorkTarget { 406 | public IHttpRequestResponse req; 407 | public List offsets; 408 | public URL url; 409 | 410 | public WorkTarget(IHttpRequestResponse req, List offsets) { 411 | this.req = req; 412 | this.offsets = offsets; 413 | } 414 | 415 | public WorkTarget(URL url) { 416 | this.url = url; 417 | } 418 | } -------------------------------------------------------------------------------- /src/burp/Utilities.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | import burp.*; 3 | 4 | import javax.swing.*; 5 | import javax.swing.event.MenuEvent; 6 | import javax.swing.event.MenuListener; 7 | import javax.swing.text.NumberFormatter; 8 | import java.awt.*; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.PrintWriter; 12 | import java.net.URL; 13 | import java.text.NumberFormat; 14 | import java.util.*; 15 | import java.util.List; 16 | import java.util.concurrent.locks.ReadWriteLock; 17 | import java.util.concurrent.locks.ReentrantReadWriteLock; 18 | 19 | class ConfigMenu implements Runnable, MenuListener, IExtensionStateListener{ 20 | private JMenu menuButton; 21 | 22 | ConfigMenu() { 23 | Utilities.callbacks.registerExtensionStateListener(this); 24 | } 25 | 26 | public void run() 27 | { 28 | menuButton = new JMenu("Distribute Damage"); 29 | menuButton.addMenuListener(this); 30 | JMenuBar burpMenuBar = Utilities.getBurpFrame().getJMenuBar(); 31 | burpMenuBar.add(menuButton); 32 | } 33 | 34 | public void menuSelected(MenuEvent e) { 35 | SwingUtilities.invokeLater(new Runnable() { 36 | public void run(){ 37 | Utilities.globalSettings.showSettings(); 38 | } 39 | }); 40 | } 41 | 42 | public void menuDeselected(MenuEvent e) { } 43 | 44 | public void menuCanceled(MenuEvent e) { } 45 | 46 | public void extensionUnloaded() { 47 | Utilities.getBurpFrame().getJMenuBar().remove(menuButton); 48 | } 49 | } 50 | 51 | class ConfigurableSettings { 52 | private LinkedHashMap settings; 53 | private NumberFormatter onlyInt; 54 | 55 | ConfigurableSettings() { 56 | settings = new LinkedHashMap<>(); 57 | put("throttle", 1000); 58 | put("pause all traffic", false); 59 | put("max param length", 30); 60 | put("scan params", true); 61 | put("scan path start", false); 62 | put("scan path end", false); 63 | put("scan root folder", true); 64 | put("scan other folders", false); 65 | put("scan cookies", true); 66 | put("scan headers", true); 67 | put("target headers", "User-Agent,Referer"); 68 | put("include content type in key", true); 69 | put("header target mime types", "HTML,JSON"); 70 | put("header target status codes", "200,302,307"); 71 | 72 | for(String key: settings.keySet()) { 73 | //Utilities.callbacks.saveExtensionSetting(key, null); // purge saved settings 74 | String value = Utilities.callbacks.loadExtensionSetting(key); 75 | if (Utilities.callbacks.loadExtensionSetting(key) != null) { 76 | putRaw(key, value); 77 | } 78 | } 79 | 80 | NumberFormat format = NumberFormat.getInstance(); 81 | onlyInt = new NumberFormatter(format); 82 | onlyInt.setValueClass(Integer.class); 83 | onlyInt.setMinimum(-1); 84 | onlyInt.setMaximum(Integer.MAX_VALUE); 85 | onlyInt.setAllowsInvalid(false); 86 | 87 | } 88 | 89 | private ConfigurableSettings(ConfigurableSettings base) { 90 | settings = new LinkedHashMap<>(base.settings); 91 | onlyInt = base.onlyInt; 92 | } 93 | 94 | void printSettings() { 95 | for(String key: settings.keySet()) { 96 | Utilities.out(key + ": "+settings.get(key)); 97 | } 98 | } 99 | 100 | static JFrame getBurpFrame() 101 | { 102 | for(Frame f : Frame.getFrames()) 103 | { 104 | if(f.isVisible() && f.getTitle().startsWith(("Burp Suite"))) 105 | { 106 | return (JFrame) f; 107 | } 108 | } 109 | return null; 110 | } 111 | 112 | private String encode(Object value) { 113 | String encoded; 114 | if (value instanceof Boolean) { 115 | encoded = String.valueOf(value); 116 | } 117 | else if (value instanceof Integer) { 118 | encoded = String.valueOf(value); 119 | } 120 | else { 121 | encoded = "\"" + ((String) value).replace("\\", "\\\\").replace("\"", "\\\"") + "\""; 122 | } 123 | return encoded; 124 | } 125 | 126 | private void putRaw(String key, String value) { 127 | settings.put(key, value); 128 | } 129 | 130 | private void put(String key, Object value) { 131 | settings.put(key, encode(value)); 132 | } 133 | 134 | String getString(String key) { 135 | String decoded = settings.get(key); 136 | decoded = decoded.substring(1, decoded.length()-1).replace("\\\"", "\"").replace("\\\\", "\\"); 137 | return decoded; 138 | } 139 | 140 | int getInt(String key) { 141 | return Integer.parseInt(settings.get(key)); 142 | } 143 | 144 | boolean getBoolean(String key) { 145 | String val = settings.get(key); 146 | if (val.equals("true") ) { 147 | return true; 148 | } 149 | else if (val.equals("false")){ 150 | return false; 151 | } 152 | throw new RuntimeException(); 153 | } 154 | 155 | String getType(String key) { 156 | String val = settings.get(key); 157 | if (val.equals("true") || val.equals("false")) { 158 | return "boolean"; 159 | } 160 | else if (val.startsWith("\"")) { 161 | return "string"; 162 | } 163 | else { 164 | return "number"; 165 | } 166 | } 167 | 168 | ConfigurableSettings showSettings() { 169 | JPanel panel = new JPanel(); 170 | panel.setLayout(new GridLayout(0, 2)); 171 | 172 | HashMap configured = new HashMap<>(); 173 | 174 | for(String key: settings.keySet()) { 175 | String type = getType(key); 176 | panel.add(new JLabel("\n"+key+": ")); 177 | 178 | if (type.equals("boolean")) { 179 | JCheckBox box = new JCheckBox(); 180 | box.setSelected(getBoolean(key)); 181 | panel.add(box); 182 | configured.put(key, box); 183 | } 184 | else if (type.equals("number")){ 185 | JTextField box = new JFormattedTextField(onlyInt); 186 | box.setText(String.valueOf(getInt(key))); 187 | panel.add(box); 188 | configured.put(key, box); 189 | } 190 | else { 191 | JTextField box = new JTextField(getString(key)); 192 | panel.add(box); 193 | configured.put(key, box); 194 | } 195 | } 196 | 197 | int result = JOptionPane.showConfirmDialog(Utilities.getBurpFrame(), panel, "Attack Config", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); 198 | if (result == JOptionPane.OK_OPTION) { 199 | for(String key: configured.keySet()) { 200 | Object val = configured.get(key); 201 | if (val instanceof JCheckBox) { 202 | val = ((JCheckBox) val).isSelected(); 203 | } 204 | else if (val instanceof JFormattedTextField) { 205 | val = Integer.parseInt(((JFormattedTextField) val).getText().replace(",", "")); 206 | } 207 | else { 208 | val = ((JTextField) val).getText(); 209 | } 210 | put(key, val); 211 | Utilities.callbacks.saveExtensionSetting(key, encode(val)); 212 | } 213 | 214 | return new ConfigurableSettings(this); 215 | } 216 | 217 | return null; 218 | } 219 | 220 | 221 | 222 | } 223 | 224 | public class Utilities { 225 | 226 | private static PrintWriter stdout; 227 | private static PrintWriter stderr; 228 | public static final boolean DEBUG = true; 229 | public static final boolean THROTTLE_SCANITEM_CREATION = false; 230 | public static Set THROTTLED_COMPONENTS = new HashSet<>(); 231 | public static ReadWriteLock spiderLock = new ReentrantReadWriteLock(); 232 | public static IBurpExtenderCallbacks callbacks; 233 | public static IExtensionHelpers helpers; 234 | 235 | private static final String CHARSET = "0123456789abcdefghijklmnopqrstuvwxyz"; // ABCDEFGHIJKLMNOPQRSTUVWXYZ 236 | private static final String START_CHARSET = "ghijklmnopqrstuvwxyz"; 237 | static Random rnd = new Random(); 238 | static ConfigurableSettings globalSettings; 239 | 240 | 241 | public Utilities(final IBurpExtenderCallbacks incallbacks) { 242 | callbacks = incallbacks; 243 | stdout = new PrintWriter(callbacks.getStdout(), true); 244 | stderr = new PrintWriter(callbacks.getStderr(), true); 245 | globalSettings = new ConfigurableSettings(); 246 | helpers = callbacks.getHelpers(); 247 | Integer[] to_throttle = {IBurpExtenderCallbacks.TOOL_TARGET, IBurpExtenderCallbacks.TOOL_SPIDER, IBurpExtenderCallbacks.TOOL_SCANNER, IBurpExtenderCallbacks.TOOL_INTRUDER, IBurpExtenderCallbacks.TOOL_SEQUENCER, IBurpExtenderCallbacks.TOOL_EXTENDER}; 248 | Collections.addAll(THROTTLED_COMPONENTS, to_throttle); 249 | 250 | } 251 | 252 | static String generateCanary() { 253 | return randomString(4+rnd.nextInt(7)) + Integer.toString(rnd.nextInt(9)); 254 | } 255 | 256 | static JFrame getBurpFrame() 257 | { 258 | for(Frame f : Frame.getFrames()) 259 | { 260 | if(f.isVisible() && f.getTitle().startsWith(("Burp Suite"))) 261 | { 262 | return (JFrame) f; 263 | } 264 | } 265 | return null; 266 | } 267 | 268 | static String randomString(int len) { 269 | StringBuilder sb = new StringBuilder(len); 270 | sb.append(START_CHARSET.charAt(rnd.nextInt(START_CHARSET.length()))); 271 | for (int i = 1; i < len; i++) 272 | sb.append(CHARSET.charAt(rnd.nextInt(CHARSET.length()))); 273 | return sb.toString(); 274 | } 275 | 276 | public static void out(String message) { 277 | stdout.println(message); 278 | } 279 | public static void err(String message) { 280 | stderr.println(message); 281 | } 282 | 283 | public static void log(String message) { 284 | if (DEBUG) { 285 | stdout.println(message); 286 | } 287 | } 288 | 289 | public static String sensibleURL(URL url) { 290 | String out = url.toString(); 291 | if (url.getDefaultPort() == url.getPort()) { 292 | out = out.replaceFirst(":" + Integer.toString(url.getPort()), ""); 293 | } 294 | return out; 295 | } 296 | 297 | public static URL getURL(IHttpRequestResponse request) { 298 | IHttpService service = request.getHttpService(); 299 | URL url; 300 | try { 301 | url = new URL(service.getProtocol(), service.getHost(), service.getPort(), getPathFromRequest(request.getRequest())); 302 | } catch (java.net.MalformedURLException e) { 303 | url = null; 304 | } 305 | return url; 306 | } 307 | 308 | // records from the first space to the second space 309 | public static String getPathFromRequest(byte[] request) { 310 | int i = 0; 311 | boolean recording = false; 312 | String path = ""; 313 | while (i < request.length) { 314 | byte x = request[i]; 315 | 316 | if (recording) { 317 | if (x != ' ') { 318 | path += (char) x; 319 | } else { 320 | break; 321 | } 322 | } else { 323 | if (x == ' ') { 324 | recording = true; 325 | } 326 | } 327 | i++; 328 | } 329 | return path; 330 | } 331 | 332 | public static String getExtension(byte[] request) { 333 | String url = getPathFromRequest(request); 334 | int query_start = url.indexOf('?'); 335 | if (query_start == -1) { 336 | query_start = url.length(); 337 | } 338 | url = url.substring(0, query_start); 339 | int last_dot = url.lastIndexOf('.'); 340 | if (last_dot == -1) { 341 | return ""; 342 | } 343 | else { 344 | return url.substring(last_dot); 345 | } 346 | } 347 | 348 | public static IHttpRequestResponse fetchFromSitemap(URL url) { 349 | IHttpRequestResponse[] pages = callbacks.getSiteMap(sensibleURL(url)); 350 | for (IHttpRequestResponse page : pages) { 351 | if (page.getResponse() != null) { 352 | if (url.equals(getURL(page))) { 353 | return page; 354 | } 355 | } 356 | } 357 | return null; 358 | } 359 | 360 | static int countMatches(byte[] response, byte[] match) { 361 | int matches = 0; 362 | if (match.length < 4) { 363 | return matches; 364 | } 365 | 366 | int start = 0; 367 | // Utilities.out("#"+response.length); 368 | while (start < response.length) { 369 | start = helpers.indexOf(response, match, true, start, response.length); 370 | if (start == -1) 371 | break; 372 | matches += 1; 373 | start += match.length; 374 | } 375 | 376 | return matches; 377 | } 378 | 379 | static byte[] replace(byte[] request, byte[] find, byte[] replace) { 380 | List matches = getMatches(request, find, -1); 381 | try { 382 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 383 | for (int i=0;i getMatches(byte[] response, byte[] match, int giveUpAfter) { 406 | if (giveUpAfter == -1) { 407 | giveUpAfter = response.length; 408 | } 409 | 410 | List matches = new ArrayList<>(); 411 | 412 | // if (match.length < 4) { 413 | // return matches; 414 | // } 415 | 416 | int start = 0; 417 | while (start < giveUpAfter) { 418 | start = helpers.indexOf(response, match, true, start, giveUpAfter); 419 | if (start == -1) 420 | break; 421 | matches.add(new int[]{start, start + match.length}); 422 | start += match.length; 423 | } 424 | 425 | return matches; 426 | } 427 | 428 | public static byte[] fixContentLength(byte[] request) { 429 | if (countMatches(request, helpers.stringToBytes("Content-Length: ")) > 0) { 430 | int start = Utilities.getBodyStart(request); 431 | int contentLength = request.length - start; 432 | return setHeader(request, "Content-Length", Integer.toString(contentLength)); 433 | } 434 | else { 435 | return request; 436 | } 437 | } 438 | 439 | public static byte[] setHeader(byte[] request, String header, String value) { 440 | int[] offsets = getHeaderOffsets(request, header); 441 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 442 | try { 443 | outputStream.write( Arrays.copyOfRange(request, 0, offsets[1])); 444 | outputStream.write(helpers.stringToBytes(value)); 445 | outputStream.write(Arrays.copyOfRange(request, offsets[2], request.length)); 446 | return outputStream.toByteArray(); 447 | } catch (IOException e) { 448 | throw new RuntimeException("Request creation unexpectedly failed"); 449 | } catch (NullPointerException e) { 450 | Utilities.out("header locating fail: "+header); 451 | Utilities.out("'"+helpers.bytesToString(request)+"'"); 452 | throw new RuntimeException("Can't find the header"); 453 | } 454 | } 455 | 456 | public static int[] getHeaderOffsets(byte[] request, String header) { 457 | int i = 0; 458 | int end = request.length; 459 | while (i < end) { 460 | int line_start = i; 461 | while (i < end && request[i++] != ' ') { 462 | } 463 | byte[] header_name = Arrays.copyOfRange(request, line_start, i - 2); 464 | int headerValueStart = i; 465 | while (i < end && request[i++] != '\n') { 466 | } 467 | if (i == end) { 468 | break; 469 | } 470 | 471 | String header_str = helpers.bytesToString(header_name); 472 | 473 | if (header.equals(header_str)) { 474 | int[] offsets = {line_start, headerValueStart, i - 2}; 475 | return offsets; 476 | } 477 | 478 | if (i + 2 < end && request[i] == '\r' && request[i + 1] == '\n') { 479 | break; 480 | } 481 | } 482 | return null; 483 | } 484 | 485 | public static int getBodyStart(byte[] response) { 486 | int i = 0; 487 | int newlines_seen = 0; 488 | while (i < response.length) { 489 | byte x = response[i]; 490 | if (x == '\n') { 491 | newlines_seen++; 492 | } else if (x != '\r') { 493 | newlines_seen = 0; 494 | } 495 | 496 | if (newlines_seen == 2) { 497 | break; 498 | } 499 | i += 1; 500 | } 501 | 502 | 503 | while (i < response.length && (response[i] == ' ' || response[i] == '\n' || response[i] == '\r')) { 504 | i++; 505 | } 506 | 507 | return i; 508 | } 509 | 510 | 511 | public static List getExtraInsertionPoints(byte[] request) { // 512 | List params = new ArrayList<>(); 513 | int end = getBodyStart(request); 514 | int i = 0; 515 | while(i < end && request[i++] != ' ') {} // walk to the url start 516 | int pathStart = i; 517 | ArrayList folderEnds = new ArrayList<>(); 518 | while(i < end) { 519 | byte c = request[i]; 520 | if (c == ' ' || 521 | c == '?' || 522 | c == '#') { 523 | break; 524 | } 525 | 526 | if (c == '/' && i != pathStart) { 527 | folderEnds.add(i); 528 | } 529 | i++; 530 | } 531 | 532 | 533 | if (globalSettings.getBoolean("scan path start")) { 534 | params.add(new PartialParam("pathstart", pathStart+1, pathStart+1)); 535 | } 536 | 537 | if (globalSettings.getBoolean("scan root folder") && folderEnds.size() != 0) { 538 | String base = helpers.bytesToString(Arrays.copyOfRange(request, pathStart+1, folderEnds.get(0))); 539 | params.add(new PartialParam("folder "+base, pathStart+1, folderEnds.get(0))); 540 | } 541 | 542 | if (globalSettings.getBoolean("scan other folders") && folderEnds.size() != 0) { 543 | Iterator iterator = folderEnds.iterator(); 544 | int lastStart = iterator.next(); 545 | while (iterator.hasNext()) { 546 | Integer folderEnd = iterator.next(); 547 | String base = helpers.bytesToString(Arrays.copyOfRange(request, lastStart+1, folderEnd)); 548 | params.add(new PartialParam("folder "+base, lastStart+1, folderEnd)); 549 | lastStart = folderEnd; 550 | } 551 | } 552 | 553 | if (globalSettings.getBoolean("scan path end")) { 554 | params.add(new PartialParam("pathend", i, i)); 555 | } 556 | 557 | 558 | while(request[i++] != '\n' && i < end) {} 559 | 560 | if(globalSettings.getBoolean("scan headers")) { 561 | String[] to_poison = globalSettings.getString("target headers").split(","); 562 | while (i < end) { 563 | int line_start = i; 564 | while (i < end && request[i++] != ' ') { 565 | } 566 | byte[] header_name = Arrays.copyOfRange(request, line_start, i - 2); 567 | int headerValueStart = i; 568 | while (i < end && request[i++] != '\n') { 569 | } 570 | if (i == end) { 571 | break; 572 | } 573 | 574 | String header_str = helpers.bytesToString(header_name); 575 | for (String header : to_poison) { 576 | if (header.equals(header_str)) { 577 | params.add(new PartialParam(header, headerValueStart, i - 2)); 578 | } 579 | } 580 | } 581 | } 582 | 583 | 584 | return params; 585 | } 586 | 587 | } 588 | 589 | class PartialParam implements IParameter { 590 | 591 | int valueStart, valueEnd; 592 | String name; 593 | 594 | static final byte PARAM_PATH = 9; 595 | public PartialParam(String name, int valueStart, int valueEnd) { 596 | this.name = name; 597 | this.valueStart = valueStart; 598 | this.valueEnd = valueEnd; 599 | } 600 | 601 | @Override 602 | public byte getType() { 603 | return PARAM_PATH; 604 | } 605 | 606 | @Override 607 | public String getName() { 608 | return name; 609 | } 610 | 611 | @Override 612 | public String getValue() { 613 | return null; 614 | } 615 | 616 | @Override 617 | public int getNameStart() { 618 | return 0; 619 | } 620 | 621 | @Override 622 | public int getNameEnd() { 623 | return 0; 624 | } 625 | 626 | @Override 627 | public int getValueStart() { 628 | return valueStart; 629 | } 630 | 631 | @Override 632 | public int getValueEnd() { 633 | return valueEnd; 634 | } 635 | } 636 | 637 | 638 | 639 | --------------------------------------------------------------------------------