├── extender-snapshot.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── BappManifest.bmf
├── LICENSE
├── .gitignore
├── BappDescription.html
├── gradlew.bat
├── README.md
├── gradlew
└── burp
└── BurpExtender.java
/extender-snapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/batch-scan-report-generator/master/extender-snapshot.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/batch-scan-report-generator/master/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-8.9-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/BappManifest.bmf:
--------------------------------------------------------------------------------
1 | Uuid: bc4ad87282e64fc4b35e9b9b05bac1dd
2 | ExtensionType: 1
3 | Name: Batch Scan Report Generator
4 | RepoName: batch-scan-report-generator
5 | ScreenVersion: 0.4
6 | SerialVersion: 6
7 | MinPlatformVersion: 0
8 | ProOnly: True
9 | Author: Jeffrey Cap (Bort_Millipede)
10 | ShortDescription: Generates multiple scan reports by host with just a few clicks.
11 | EntryPoint: build/libs/batch-scan-report-generator-0.4.jar
12 | BuildCommand: ./gradlew fatJar
13 | SupportedProducts: Pro
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017, 2024 Jeffrey Cap (Bort-Millipede)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
12 | hs_err_pid*
13 |
14 | # Burp Extender API source files #
15 | IBurpCollaboratorClientContext.java
16 | IBurpCollaboratorInteraction.java
17 | IBurpExtender.java
18 | IBurpExtenderCallbacks.java
19 | IContextMenuFactory.java
20 | IContextMenuInvocation.java
21 | ICookie.java
22 | IExtensionHelpers.java
23 | IExtensionStateListener.java
24 | IHttpListener.java
25 | IHttpRequestResponse.java
26 | IHttpRequestResponsePersisted.java
27 | IHttpRequestResponseWithMarkers.java
28 | IHttpService.java
29 | IInterceptedProxyMessage.java
30 | IIntruderAttack.java
31 | IIntruderPayloadGenerator.java
32 | IIntruderPayloadGeneratorFactory.java
33 | IIntruderPayloadProcessor.java
34 | IMenuItemHandler.java
35 | IMessageEditor.java
36 | IMessageEditorController.java
37 | IMessageEditorTab.java
38 | IMessageEditorTabFactory.java
39 | IParameter.java
40 | IProxyListener.java
41 | IRequestInfo.java
42 | IResponseInfo.java
43 | IResponseKeywords.java
44 | IResponseVariations.java
45 | IScanIssue.java
46 | IScannerCheck.java
47 | IScannerInsertionPoint.java
48 | IScannerInsertionPointProvider.java
49 | IScannerListener.java
50 | IScanQueueItem.java
51 | IScopeChangeListener.java
52 | ISessionHandlingAction.java
53 | ITab.java
54 | ITempFile.java
55 | ITextEditor.java
56 |
57 | .gradle/
58 | build/
59 |
--------------------------------------------------------------------------------
/BappDescription.html:
--------------------------------------------------------------------------------
1 |
This extension can be used to generate multiple scan reports by host with just a few clicks.
2 |
3 | Usage:
4 |
5 | - When you're ready to generate reports, navigate to the new "Batch Scan Report
6 | Generator" tab.
7 | - Select the output format for the reports that will be generated (HTML or
8 | XML).
9 | - Select which issue Severities to report.
10 | - Select which issue Confidences to report.
11 | - Select whether to generate reports for all hosts or only hosts set in
12 | the Target->Scope tab.
13 | - Select whether to merge HTTP (port 80) and HTTPS (port 443) into one host report, or to have
14 | separate reports for both
15 |
16 | - If the option is selected, one report will be generated for the host
17 | that includes findings for HTTP:80 and HTTPS:443. The report filename
18 | will be in the following format: "httphttps__[HOST]-burp.[FORMAT]".
19 | - If the option is selected, Any findings for other ports/protocols on
20 | the host will be reported in separate file(s) with the following
21 | filename format: "[PROTOCOL]__[HOST]_[PORT]-burp.[FORMAT]".
22 |
23 |
24 | - Select whether to merge all protocols and ports for a host into one
25 | report. The report filename will be in the following format:
26 | "[HOST]_all-burp.[FORMAT]".
27 |
28 | - This option automatically sets the "Merge HTTP/HTTPS" option.
29 |
30 |
31 | - Select the output directory for the reports by clicking the "Select
32 | folder ..." button and selecting a directory. If the selected directory does
33 | not yet exist, it will be created when the reports are generated.
34 | - Select whether to append the date to the report filenames, and choose
35 | the desired date from the dropdown box. The report filenames will be in
36 | the following format: "[FILENAME]-[DATE_FORMAT].[FORMAT]".
37 |
38 | - The following date formats are available: MMDDYYYY, DDMMYYYY,
39 | YYYYMMDD, MMDDYY, DDMMYY, YYMMDD
40 |
41 |
42 | - Once all options have been set, click the "Generate Report(s)" button to
43 | start report generation.
44 |
45 | - The status of the report generation will be displayed next to the
46 | button and will be updated in real time.
47 | - A more verbose status of the generation will be printed in the
48 | Extender->Output tab for the Extension. This will include a list of the
49 | absolute paths to every report file that is successfully generated.
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Burp Batch Scan Report Generator
2 |
3 | 
4 |
5 | Small Burp Suite Extension to generate multiple scan reports by host with just a few clicks. Works with Burp Suite Professional only (can be successfully loaded into Burp Suite Free but will not perform any function).
6 |
7 | ## Usage
8 |
9 | 1. Load the ```Burp-Batch-Report-Generator-[VERSION].jar``` file in the Burp Suite "Extender" tab, or load the extension from the BApp Store.
10 | 2. When ready to generate reports, navigate to the new "Batch Scan Report Generator" tab.
11 | 3. Select the output format for the reports that will be generated (HTML or XML).
12 | 4. Select which issue Severities to report.
13 | 5. Select which issue Confidences to report.
14 | 6. Select whether to generate reports for all hosts or only hosts set in the Target->Scope tab.
15 | 7. Select whether to merge HTTP (port 80) and HTTPS (port 443) into one host report, or to have separate reports for both
16 | * If the option is selected, one report will be generated for the host that includes findings for HTTP:80 and HTTPS:443. The report filename will be in the following format: ```httphttps__[HOST]-burp.[FORMAT]```.
17 | * Additionally, if the option is selected, any findings for other ports/protocols on the host will be reported in separate file(s) with the following filename format: ```[PROTOCOL]__[HOST]_[PORT]-burp.[FORMAT]```.
18 | 8. Select whether to merge all protocols and ports for a host into one report. The report filename will be in the following format: ```[HOST]_all-burp.[FORMAT]```.
19 | * This option automatically sets the "Merge HTTP/HTTPS" option.
20 | 9. Select the output directory for the reports by clicking the "Select folder ..." button and selecting a directory. If the selected directory does not yet exist, it will be created when the reports are generated.
21 | 10. Select whether to append the date to the report filenames, and choose the desired date from the dropdown box. The report filenames will be in the following format: ```[FILENAME]-[DATE_FORMAT].[FORMAT]```.
22 | * The following date formats are available: MMDDYYYY, DDMMYYYY, YYYYMMDD, MMDDYY, DDMMYY, YYMMDD
23 | 11. Select whether to save generated reports to sub-directories by host (named after host and created when the reports are generated).
24 | 12. Once all options have been set, click the "Generate Report(s)" button to start report generation.
25 | * The status of the report generation will be displayed next to the button and will be updated in real time.
26 | * A more verbose status of the generation will be printed in the Extender->Output tab for the Extension. This will include a list of the absolute paths to every report file that is successfully generated.
27 | * Any errors encountered during report generation will be printed to the Extender->Errors tab for the extension.
28 |
29 | ## Building
30 |
31 | Requires OpenJDK 17 or higher, and Gradle 8 or higher.
32 |
33 | 1. Clone the Burp-Batch-Report-Generator repository.
34 | 2. Open a terminal and navigate to the Burp-Batch-Report-Generator directory.
35 | 3. Issue the following command to compile the extension and create the extension jar file (named ```Burp-Batch-Report-Generator-[VERSION].jar```):
36 | ```bash
37 | gradle fatJar
38 | ```
39 |
40 | ## Copyright
41 |
42 | Copyright (C) 2017, 2024 Jeffrey Cap (Bort_Millipede)
43 |
44 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | /*
2 | BurpExtender.java
3 |
4 | v0.4 (11/19/2024)
5 |
6 | Small Burp Suite Extension to generate multiple scan reports by host with just a few clicks. Works with Burp Suite Professional only.
7 | */
8 |
9 | package burp;
10 |
11 | import javax.swing.JPanel;
12 | import javax.swing.JScrollPane;
13 | import javax.swing.JLabel;
14 | import javax.swing.ButtonGroup;
15 | import javax.swing.JRadioButton;
16 | import javax.swing.JTextField;
17 | import javax.swing.JCheckBox;
18 | import javax.swing.JComboBox;
19 | import javax.swing.JFileChooser;
20 | import javax.swing.JButton;
21 | import javax.swing.JOptionPane;
22 | import javax.swing.SwingConstants;
23 | import javax.swing.SwingUtilities;
24 | import java.awt.Component;
25 | import java.awt.GridLayout;
26 | import java.awt.FlowLayout;
27 | import java.awt.Color;
28 | import java.awt.event.ActionListener;
29 | import java.awt.event.ActionEvent;
30 | import java.io.File;
31 | import java.util.Hashtable;
32 | import java.util.List;
33 | import java.util.ArrayList;
34 | import java.util.Set;
35 | import java.util.Iterator;
36 | import java.util.Date;
37 | import java.net.URL;
38 | import java.text.SimpleDateFormat;
39 |
40 | public class BurpExtender implements IBurpExtender,ITab,IExtensionStateListener,ActionListener {
41 | private IBurpExtenderCallbacks callbacks;
42 | private IExtensionHelpers helpers;
43 | private String name;
44 |
45 | //configuration fields
46 | private String reportFormat;
47 | private boolean[] severities; //filters for issue severities
48 | private boolean[] confidences; //filters for issue confidences
49 | private boolean inscopeOnly;
50 | private boolean mergeHttps; //merge http:80 and https:443 into one report
51 | private boolean mergeAll; //merge all protocols and ports into 1 report
52 | private File destDir;
53 | private int fileDate; //append generation date to filename (if -1, then do not append; if >-1, append using format from DATE_FORMATS index)
54 | private boolean createSubDirectories; //create sub-directory for each report (named after host)
55 |
56 | //UI fields
57 | private JPanel component;
58 | private JButton defaultSettingsButton;
59 | private JRadioButton htmlButton;
60 | private JRadioButton xmlButton;
61 | private JCheckBox includeHighSeverityCheck;
62 | private JCheckBox includeMediumSeverityCheck;
63 | private JCheckBox includeLowSeverityCheck;
64 | private JCheckBox includeInformationSeverityCheck;
65 | private JCheckBox includeFalsePositiveSeverityCheck;
66 | private JCheckBox includeCertainConfidenceCheck;
67 | private JCheckBox includeFirmConfidenceCheck;
68 | private JCheckBox includeTentativeConfidenceCheck;
69 | private JCheckBox inscopeCheck;
70 | private JCheckBox httpsCheck;
71 | private JCheckBox mergeAllCheck;
72 | private JFileChooser destDirChooser;
73 | private JButton destDirButton;
74 | private JLabel destDirLabel;
75 | private JCheckBox filenameDateCheck;
76 | private JComboBox dateFormatChooser;
77 | private JCheckBox createSubDirectoriesCheck;
78 | private JButton generateButton;
79 | private JLabel statusLabel;
80 |
81 | //constants
82 | private static final String VERSION = "0.4";
83 | private static final String[] DATE_FORMATS = {"MMddyyyy","ddMMyyyy","yyyyMMdd","MMddyy","ddMMyy","yyMMdd"};
84 | private static final String OPTION_PREFIX = "bort.batchreport";
85 |
86 | //IBurpExtender methods
87 | @Override
88 | public void registerExtenderCallbacks(IBurpExtenderCallbacks cb) {
89 | callbacks = cb;
90 | helpers = callbacks.getHelpers();
91 | name = "Batch Scan Report Generator";
92 | callbacks.setExtensionName(name);
93 |
94 | //initialize default settings, then restore saved settings (if any)
95 | setDefaultOptions();
96 | String savedOption = callbacks.loadExtensionSetting(OPTION_PREFIX+".reportFormat");
97 | if(savedOption != null) reportFormat = savedOption;
98 | savedOption = callbacks.loadExtensionSetting(OPTION_PREFIX+".severities");
99 | if(savedOption != null) {
100 | String[] optionSplit = savedOption.split(",");
101 | for(int i=0;i();
234 | for(int i=0;i> sitesDict = new Hashtable>();
396 | Set siteKeys = sitesDict.keySet();
397 | for(int i=0;i ppList = null;
405 | if(!siteKeys.contains(reqHost)) {
406 | ppList = new ArrayList();
407 | if(reqPort!=-1) {
408 | ppList.add(reqProt+":"+Integer.toString(reqPort));
409 | } else { //some potential for an edge case here where getDefaultPort() returns -1: will update later if it becomes a problem
410 | ppList.add(reqProt+":"+Integer.toString(issueUrl.getDefaultPort()));
411 | }
412 |
413 | sitesDict.put(reqHost,ppList);
414 | siteKeys = sitesDict.keySet();
415 | } else {
416 | ppList = (ArrayList) sitesDict.get(reqHost);
417 | boolean found = false;
418 | Iterator ppListItr = ppList.iterator();
419 | while(ppListItr.hasNext()) {
420 | String next = ppListItr.next();
421 | if(next.equals(reqProt+":"+Integer.toString(reqPort))) {
422 | found = true;
423 | break;
424 | } else if(next.equals(reqProt+":"+Integer.toString(issueUrl.getDefaultPort()))) { //some potential for an edge case here where getDefaultPort() returns -1: will update later if it becomes a problem
425 | found = true;
426 | break;
427 | }
428 | }
429 | if(!found) {
430 | if(reqPort!=-1) {
431 | ppList.add(reqProt+":"+Integer.toString(reqPort));
432 | } else { //some potential for an edge case here where getDefaultPort() returns -1: will update later if it becomes a problem
433 | ppList.add(reqProt+":"+Integer.toString(issueUrl.getDefaultPort()));
434 | }
435 | sitesDict.put(reqHost,ppList);
436 | }
437 | }
438 | }
439 |
440 | //issues met criteria for reporting: generate reports
441 | if(!siteKeys.isEmpty()) {
442 | if(!destDir.exists()) { //if chosen output folder does not exist, create it
443 | if(!destDir.mkdirs()) {
444 | callbacks.printOutput("Target directory"+destDir.getAbsolutePath()+" could not be created! Report generation aborted!\n");
445 | callbacks.printError("Target directory"+destDir.getAbsolutePath()+" could not be created! Report generation aborted!");
446 | statusLabel.setText("Target directory"+destDir.getAbsolutePath()+" directory could not be created! Report generation aborted!");
447 | reEnableUiElements();
448 | return;
449 | }
450 | } else if(!destDir.isDirectory()) { //if chosen output folder is not a directory
451 | callbacks.printOutput("Target "+destDir.getAbsolutePath()+" is not a directory! Report generation aborted!\n");
452 | callbacks.printError("Target "+destDir.getAbsolutePath()+" is not a directory! Report generation aborted!");
453 | statusLabel.setText("Target "+destDir.getAbsolutePath()+" is not a directory! Report generation aborted!");
454 | reEnableUiElements();
455 | return;
456 | }
457 |
458 | Hashtable> reportList = new Hashtable>();
459 | Iterator siteKeysItr = siteKeys.iterator();
460 | while(siteKeysItr.hasNext()) {
461 | String site = siteKeysItr.next();
462 | ArrayList ppList = sitesDict.get(site);
463 |
464 | if(mergeAll) {
465 | ArrayList prefixList = new ArrayList();
466 | Iterator ppListItr = ppList.iterator();
467 | while(ppListItr.hasNext()) {
468 | String pp = ppListItr.next();
469 | String[] ppSplit = pp.split(":");
470 | if((ppSplit[0].equalsIgnoreCase("http")) && (ppSplit[1].equals("80"))) {
471 | prefixList.add("http://"+site+"/");
472 | } else if((ppSplit[0].equalsIgnoreCase("https")) && (ppSplit[1].equals("443"))) {
473 | prefixList.add("https://"+site+"/");
474 | } else {
475 | prefixList.add(ppSplit[0]+"://"+site+":"+ppSplit[1]+"/");
476 | }
477 | }
478 | if(prefixList.size()>0) {
479 | reportList.put(site+"_all",prefixList);
480 | }
481 | } else {
482 | if(mergeHttps) {
483 | ArrayList stdPrefixList = new ArrayList();
484 | boolean nonStdPort = false;
485 | Iterator ppListItr = ppList.iterator();
486 | while(ppListItr.hasNext()) {
487 | String pp = ppListItr.next();
488 | String[] ppSplit = pp.split(":");
489 | if((ppSplit[0].equalsIgnoreCase("http")) && (ppSplit[1].equals("80"))) {
490 | stdPrefixList.add("http://"+site+"/");
491 | } else if((ppSplit[0].equalsIgnoreCase("https")) && (ppSplit[1].equals("443"))) {
492 | stdPrefixList.add("https://"+site+"/");
493 | } else {
494 | ArrayList nonStdPrefixList = new ArrayList(1);
495 | nonStdPrefixList.add(ppSplit[0]+"://"+site+":"+ppSplit[1]+"/");
496 | reportList.put(ppSplit[0]+"__"+site+"_"+ppSplit[1],nonStdPrefixList);
497 | nonStdPort = true;
498 | }
499 | }
500 | if(stdPrefixList.size()>0) {
501 | if(!nonStdPort) {
502 | reportList.put(site,stdPrefixList);
503 | } else {
504 | reportList.put("httphttps__"+site,stdPrefixList);
505 | }
506 | }
507 | } else {
508 | Iterator ppListItr = ppList.iterator();
509 | while(ppListItr.hasNext()) {
510 | String pp = ppListItr.next();
511 | String[] ppSplit = pp.split(":");
512 | ArrayList prefixList = new ArrayList(1);
513 | if((ppSplit[0].equalsIgnoreCase("http")) && (ppSplit[1].equals("80"))) {
514 | prefixList.add("http://"+site+"/");
515 | reportList.put(ppSplit[0]+"__"+site,prefixList);
516 | } else if((ppSplit[0].equalsIgnoreCase("https")) && (ppSplit[1].equals("443"))) {
517 | prefixList.add("https://"+site+"/");
518 | reportList.put(ppSplit[0]+"__"+site,prefixList);
519 | } else {
520 | prefixList.add(ppSplit[0]+"://"+site+":"+ppSplit[1]+"/");
521 | reportList.put(ppSplit[0]+"__"+site+"_"+ppSplit[1],prefixList);
522 | }
523 | }
524 | }
525 | }
526 | }
527 |
528 | //Determine if issues should be filtered by severity and/or confidence
529 | boolean sevFilter = severities[0] || severities[1] || severities[2] || severities[3] || severities[4];
530 | boolean conFilter = confidences[0] || confidences[1] || confidences[2];
531 |
532 | Set reportSites = reportList.keySet();
533 | Hashtable reportIssues = new Hashtable();
534 | Iterator reportIssuesItr = reportSites.iterator();
535 | while(reportIssuesItr.hasNext()) {
536 | String site = reportIssuesItr.next();
537 | ArrayList issueList = new ArrayList();
538 | ArrayList prefixList = reportList.get(site);
539 | Iterator prefixListItr = prefixList.iterator();
540 | while(prefixListItr.hasNext()) {
541 | String prefix = prefixListItr.next();
542 | IScanIssue[] issueTempList = callbacks.getScanIssues(prefix);
543 | for(int k=0;k0) {
573 | IScanIssue[] tempArr = new IScanIssue[issueList.size()];
574 | IScanIssue[] issueArr = issueList.toArray(tempArr);
575 | reportIssues.put(site+"-burp."+reportFormat.toLowerCase(),issueArr);
576 | }
577 | }
578 |
579 | Set reportFilenames = reportIssues.keySet();
580 | if(reportFilenames.size()==0) {
581 | callbacks.printOutput("No reports generated: Sites matching requirements contained no issues to report!\n");
582 | callbacks.printError("No reports generated: Sites matching requirements contained no issues to report!\n");
583 | statusLabel.setText("No reports generated: Sites matching requirements contained no issues to report!");
584 | reEnableUiElements();
585 | return;
586 | }
587 | callbacks.printOutput("Starting report generation of "+Integer.toString(reportFilenames.size())+" reports");
588 | statusLabel.setText("Starting report generation of "+Integer.toString(reportFilenames.size())+" reports...");
589 | Iterator reportFilenamesItr = reportFilenames.iterator();
590 | int count = 1;
591 | while(reportFilenamesItr.hasNext()) {
592 | String filename = reportFilenamesItr.next();
593 | callbacks.printOutput("Generating report "+Integer.toString(count)+" of "+Integer.toString(reportFilenames.size()));
594 | statusLabel.setText("Generating report "+Integer.toString(count)+" of "+Integer.toString(reportFilenames.size())+"...");
595 | IScanIssue[] issueList = reportIssues.get(filename);
596 | if(fileDate>-1) {
597 | filename = filename.substring(0,filename.length()-(reportFormat.toLowerCase().length()+1))+"-";
598 | SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMATS[fileDate]);
599 | filename += sdf.format(new Date())+"."+reportFormat.toLowerCase();
600 | }
601 | File reportFile = null;
602 | if(createSubDirectories) {
603 | File subDirFile = new File(destDir,issueList[0].getHttpService().getHost());
604 | if(!subDirFile.exists()) { //if chosen output folder does not exist, create it
605 | if(!subDirFile.mkdirs()) {
606 | callbacks.printOutput("Host sub-directory "+subDirFile.getAbsolutePath()+" could not be created! Report generation aborted!\n");
607 | callbacks.printError("Host sub-directory "+subDirFile.getAbsolutePath()+" could not be created! Report generation aborted!");
608 | statusLabel.setText(subDirFile.getAbsolutePath()+" directory could not be created! Report generation aborted!");
609 | reEnableUiElements();
610 | return;
611 | }
612 | } else if(!destDir.isDirectory()) { //if chosen output folder is not a directory
613 | callbacks.printOutput(destDir.getAbsolutePath()+" is not a directory! Report generation aborted!\n");
614 | callbacks.printError(destDir.getAbsolutePath()+" is not a directory! Report generation aborted!");
615 | statusLabel.setText(destDir.getAbsolutePath()+" is not a directory! Report generation aborted!");
616 | reEnableUiElements();
617 | return;
618 | }
619 | reportFile = new File(subDirFile,filename);
620 | } else {
621 | reportFile = new File(destDir,filename);
622 | }
623 | callbacks.generateScanReport(reportFormat.toUpperCase(),issueList,reportFile);
624 | callbacks.printOutput("Report "+Integer.toString(count)+" ("+reportFile.getAbsolutePath()+") of "+Integer.toString(reportFilenames.size())+" generated successfully!");
625 | statusLabel.setText("Generating report "+Integer.toString(count)+" of "+Integer.toString(reportFilenames.size())+"...");
626 | count++;
627 | }
628 | callbacks.printOutput("Report Generation Complete!\n");
629 | statusLabel.setText("Report Generation Complete!");
630 |
631 | } else {
632 | callbacks.printOutput("No reports generated: No sites match requirements for report generation!\n");
633 | callbacks.printError("No reports generated: No sites match requirements for report generation!\n");
634 | statusLabel.setText("No reports generated: No sites match requirements for report generation!");
635 | }
636 |
637 | reEnableUiElements();
638 | return;
639 | }
640 |
641 | private void reEnableUiElements() {
642 | generateButton.setEnabled(true);
643 | generateButton.setText("Generate Report(s)");
644 | defaultSettingsButton.setEnabled(true);
645 | htmlButton.setEnabled(true);
646 | xmlButton.setEnabled(true);
647 | includeHighSeverityCheck.setEnabled(true);
648 | includeMediumSeverityCheck.setEnabled(true);
649 | includeLowSeverityCheck.setEnabled(true);
650 | includeInformationSeverityCheck.setEnabled(true);
651 | includeFalsePositiveSeverityCheck.setEnabled(true);
652 | includeCertainConfidenceCheck.setEnabled(true);
653 | includeFirmConfidenceCheck.setEnabled(true);
654 | includeTentativeConfidenceCheck.setEnabled(true);
655 | inscopeCheck.setEnabled(true);
656 | httpsCheck.setEnabled(!mergeAll);
657 | mergeAllCheck.setEnabled(true);
658 | destDirButton.setEnabled(true);
659 | filenameDateCheck.setEnabled(true);
660 | if(fileDate>-1) dateFormatChooser.setEnabled(true);
661 | createSubDirectoriesCheck.setEnabled(true);
662 | return;
663 | }
664 | }
665 |
666 |
667 | //private "Default Settings Resetter" class
668 | private class ResetSettingsThread implements Runnable {
669 | @Override
670 | public void run() {
671 | int result = JOptionPane.showConfirmDialog(null,"Restore Batch Report Generation Settings to Defaults?","Restore Default Settings",JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE);
672 | if(result == JOptionPane.YES_OPTION) {
673 | setDefaultOptions();
674 | htmlButton.setSelected(true);
675 | xmlButton.setSelected(false);
676 | includeHighSeverityCheck.setSelected(true);
677 | includeMediumSeverityCheck.setSelected(true);
678 | includeLowSeverityCheck.setSelected(true);
679 | includeInformationSeverityCheck.setSelected(true);
680 | includeFalsePositiveSeverityCheck.setSelected(false);
681 | includeCertainConfidenceCheck.setSelected(true);
682 | includeFirmConfidenceCheck.setSelected(true);
683 | includeTentativeConfidenceCheck.setSelected(true);
684 | inscopeCheck.setSelected(true);
685 | httpsCheck.setSelected(true);
686 | httpsCheck.setEnabled(true);
687 | mergeAllCheck.setSelected(false);
688 | destDirLabel.setText(destDir.getAbsolutePath());
689 | filenameDateCheck.setSelected(false);
690 | dateFormatChooser.setSelectedIndex(0);
691 | createSubDirectoriesCheck.setSelected(false);
692 | callbacks.printOutput("Default settings restored.");
693 | }
694 | }
695 | }
696 | }
697 |
--------------------------------------------------------------------------------