├── .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 |
--------------------------------------------------------------------------------