├── 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 |
  1. When you're ready to generate reports, navigate to the new "Batch Scan Report 6 | Generator" tab.
  2. 7 |
  3. Select the output format for the reports that will be generated (HTML or 8 | XML).
  4. 9 |
  5. Select which issue Severities to report.
  6. 10 |
  7. Select which issue Confidences to report.
  8. 11 |
  9. Select whether to generate reports for all hosts or only hosts set in 12 | the Target->Scope tab.
  10. 13 |
  11. Select whether to merge HTTP (port 80) and HTTPS (port 443) into one host report, or to have 14 | separate reports for both 15 | 23 |
  12. 24 |
  13. 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 | 30 |
  14. 31 |
  15. 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.
  16. 34 |
  17. 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 | 41 |
  18. 42 |
  19. Once all options have been set, click the "Generate Report(s)" button to 43 | start report generation. 44 | 51 |
  20. 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 | ![](extender-snapshot.png?raw=true) 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 | --------------------------------------------------------------------------------