├── .gitignore ├── .idea └── .gitignore ├── BappDescription.html ├── BappManifest.bmf ├── README.md ├── additional-cors-checks.jar ├── build.gradle ├── doc ├── add_to_corsair.png ├── arbitrary_origin.png ├── issue.png ├── null_origin.png └── send_requests.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── burp ├── BurpExtender.kt ├── CorsActions.kt ├── CorsHTTP.kt ├── CorsHelper.kt ├── CorsIssue.kt ├── CorsMenu.kt ├── CorsOptions.kt └── CorsTab.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | .idea/ 4 | gradle 5 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

Description

2 |

This extension can be used to test websites for CORS misconfigurations. It can spot trivial misconfigurations, like arbitrary origin reflection, but also more subtle ones where a regex is not properly configured. An issue is created if a dangerous origin is reflected. If "Access-Control-Allow-Credentials: true" is also set, the issue is rated high, otherwise low. Finally, the user has to decide whether the reflected Origin is intended (e.g. CDN) or whether it is a security issue.

3 | 4 |

Features

5 |

"CORS* - Additional CORS Checks" can be run in either automatic or manual mode.

6 | 7 |

Automatic

8 | 16 | 17 |

Manual

18 | 22 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: 420a28400bad4c9d85052f8d66d3bbd8 2 | ExtensionType: 1 3 | Name: CORS*, Additional CORS Checks 4 | RepoName: additional-cors-checks 5 | ScreenVersion: 0.9.1 6 | SerialVersion: 3 7 | MinPlatformVersion: 0 8 | ProOnly: True 9 | Author: Yves Bieri 10 | ShortDescription: Test websites for CORS misconfigurations. 11 | EntryPoint: build/libs/additional-cors-checks.jar 12 | BuildCommand: ./gradlew clean build fatJar 13 | SupportedProducts: Pro 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Burp Extension: CORS* - Additional CORS Checks 2 | ## Description 3 | This extension can be used to test websites for CORS misconfigurations. 4 | It can spot trivial misconfigurations like arbitrary origin reflection, but also more sublte ones where a regex is not properly configured (e.g. www.victim.com.attacker.com). 5 | An issue is created if a dangeours origin is reflected. If `Access-Control-Allow-Credentials: true` is also set, the issue is rated high, otherwise low. Finally, the user has to decide whether the reflected Origin is intended (e.g. CDN) or whether it is a security issue. 6 | 7 | ## Features 8 | `CORS* - Additional CORS Checks` can be run in either `automatic` or `manual` mode. 9 | 10 | ### Automatic 11 | * In the `CORS*` tab, the extension can be activated. 12 | * If activated, the extension will test CORS misconfigurations for each proxy request by sending multiple requests with different origins. 13 | * There are options to only endable it for in-scope items and to exclude requests with certain file extensions. 14 | * The `URL for CORS Request` is used to test for arbitrary reflection and as prefix/suffix in testing regex misconfigurations. 15 | 16 | ![Arbitrary origin reflected](https://github.com/ybieri/Additional_CORS_Checks/blob/master/doc/arbitrary_origin.png) 17 | 18 | * If a potential misconfiguration is discovered, the request is highlighted in red (see request #3 above). 19 | * The request here does reflect the `null` origin and has `Access-Control-Allow-Credentials: true` set. 20 | 21 | ![Null origin reflected](https://github.com/ybieri/Additional_CORS_Checks/blob/master/doc/null_origin.png) 22 | 23 | * If an issue is detected, it is also reported in the `Target` and `Dashboard` tabs. 24 | 25 | ![Issue](https://github.com/ybieri/Additional_CORS_Checks/blob/master/doc/issue.png) 26 | 27 | ### Manual 28 | * Requests can be added to `CORS*` using the extension menu. 29 | 30 | ![Add to cors*](https://github.com/ybieri/Additional_CORS_Checks/blob/master/doc/add_to_corsair.png) 31 | 32 | * The requests to test for CORS misconfiguration can then be sent using the `Send CORS requests for selected entry` button. 33 | 34 | ![Send requests](https://github.com/ybieri/Additional_CORS_Checks/blob/master/doc/send_requests.png) 35 | 36 | ## Installation 37 | To install `CORS* - Additional CORS Checks` use the BApp Store. Open Burp and navigate to the `Extender` tab, then to the `BApp Store` tab. Select `CORS*` and hit the `Install` button to install the extension. 38 | 39 | ## Author 40 | * Yves Bieri (Github: [ybieri](https://github.com/ybieri), Twitter: [yves_bieri](https://twitter.com/yves_bieri)) 41 | 42 | ## Credits 43 | Thanks to https://github.com/chenjj/CORScanner for the inspiration and https://github.com/portswigger/bookmarks for the Burp template. 44 | -------------------------------------------------------------------------------- /additional-cors-checks.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/additional-cors-checks/09162a71796e66524c98055039ea5c9cdaa48d80/additional-cors-checks.jar -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'kotlin' 3 | 4 | sourceCompatibility = 1.8 5 | targetCompatibility = 1.8 6 | 7 | buildscript { 8 | repositories { 9 | mavenCentral() 10 | } 11 | dependencies { 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | sourceSets { 21 | main { 22 | kotlin { 23 | srcDir 'src' 24 | } 25 | java { 26 | srcDir 'src' 27 | } 28 | resources { 29 | srcDir 'resources' 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 36 | implementation 'net.portswigger.burp.extender:burp-extender-api:1.7.22' 37 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.3.2' 38 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2' 39 | } 40 | 41 | task fatJar(type: Jar) { 42 | from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } 43 | with jar 44 | } -------------------------------------------------------------------------------- /doc/add_to_corsair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/additional-cors-checks/09162a71796e66524c98055039ea5c9cdaa48d80/doc/add_to_corsair.png -------------------------------------------------------------------------------- /doc/arbitrary_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/additional-cors-checks/09162a71796e66524c98055039ea5c9cdaa48d80/doc/arbitrary_origin.png -------------------------------------------------------------------------------- /doc/issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/additional-cors-checks/09162a71796e66524c98055039ea5c9cdaa48d80/doc/issue.png -------------------------------------------------------------------------------- /doc/null_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/additional-cors-checks/09162a71796e66524c98055039ea5c9cdaa48d80/doc/null_origin.png -------------------------------------------------------------------------------- /doc/send_requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/additional-cors-checks/09162a71796e66524c98055039ea5c9cdaa48d80/doc/send_requests.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin_version=1.5.20 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/additional-cors-checks/09162a71796e66524c98055039ea5c9cdaa48d80/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-6.7-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'additional-cors-checks' 2 | -------------------------------------------------------------------------------- /src/burp/BurpExtender.kt: -------------------------------------------------------------------------------- 1 | package burp 2 | 3 | import java.io.PrintWriter 4 | 5 | 6 | @Suppress("unused") // Remove warning, the class will be used by burp 7 | class BurpExtender : IBurpExtender { 8 | 9 | 10 | override fun registerExtenderCallbacks(callbacks: IBurpExtenderCallbacks) { 11 | 12 | 13 | //this.callbacks = callbacks 14 | val tab = CorsTab(callbacks) 15 | val table = tab.corsTable 16 | val menuItem = CorsMenu(table) 17 | HttpListener(callbacks, table) 18 | 19 | callbacks.setExtensionName("CORS*") 20 | val stdout = PrintWriter(callbacks.stdout, true) 21 | stdout.println("CORS* loaded!") 22 | 23 | // create new Burp tab 24 | callbacks.addSuiteTab(tab) 25 | // create menu item to send to CORS 26 | callbacks.registerContextMenuFactory(menuItem) 27 | // init a new HTTP listener 28 | callbacks.registerHttpListener(HttpListener(callbacks, table)) 29 | } 30 | } -------------------------------------------------------------------------------- /src/burp/CorsActions.kt: -------------------------------------------------------------------------------- 1 | package burp 2 | 3 | import java.awt.Toolkit 4 | import java.awt.datatransfer.Clipboard 5 | import java.awt.datatransfer.StringSelection 6 | import java.awt.event.ActionEvent 7 | import java.awt.event.ActionListener 8 | import java.util.* 9 | import javax.swing.JMenuItem 10 | import javax.swing.JPopupMenu 11 | 12 | class CorsActions( 13 | private val panel: CorsPanel, 14 | private val callbacks: IBurpExtenderCallbacks 15 | ) : ActionListener { 16 | private val table = panel.table 17 | private val actionsMenu = JPopupMenu() 18 | private val sendToRepeater = JMenuItem("Send request(s) to Repeater") 19 | private val sendToIntruder = JMenuItem("Send request(s) to Intruder") 20 | private val copyURLs = JMenuItem("Copy URL(s)") 21 | private val clearMenu = JMenuItem("Clear Cors Requests") 22 | 23 | init { 24 | sendToRepeater.addActionListener(this) 25 | sendToIntruder.addActionListener(this) 26 | copyURLs.addActionListener(this) 27 | clearMenu.addActionListener(this) 28 | actionsMenu.add(sendToRepeater) 29 | actionsMenu.add(sendToIntruder) 30 | actionsMenu.add(copyURLs) 31 | actionsMenu.addSeparator() 32 | actionsMenu.add(clearMenu) 33 | actionsMenu.addSeparator() 34 | actionsMenu.addSeparator() 35 | panel.table.componentPopupMenu = actionsMenu 36 | } 37 | 38 | 39 | override fun actionPerformed(e: ActionEvent?) { 40 | if (table.selectedRow == -1) return 41 | val selectedCorss = getSelectedCors() 42 | when (val source = e?.source) { 43 | 44 | clearMenu -> { 45 | panel.model.clearCors() 46 | panel.requestViewer?.setMessage(ByteArray(0), true) 47 | panel.responseViewer?.setMessage(ByteArray(0), false) 48 | } 49 | copyURLs -> { 50 | val urls = selectedCorss.map { it.url }.joinToString() 51 | val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard 52 | clipboard.setContents(StringSelection(urls), null) 53 | } 54 | 55 | else -> { 56 | for (selectedCors in selectedCorss) { 57 | val https = useHTTPs(selectedCors) 58 | val url = selectedCors.url 59 | when (source) { 60 | sendToRepeater -> { 61 | var title = "title" 62 | if (title.length > 10) { 63 | title = title.substring(0, 9) + "+" 64 | } 65 | 66 | callbacks.sendToRepeater( 67 | url.host, 68 | url.port, 69 | https, 70 | selectedCors.requestResponse.request, 71 | title 72 | ) 73 | } 74 | sendToIntruder -> { 75 | callbacks.sendToIntruder( 76 | url.host, url.port, https, 77 | selectedCors.requestResponse.request, null 78 | ) 79 | } 80 | 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | 88 | private fun getSelectedCors(): MutableList { 89 | val selectedBookmarks: MutableList = ArrayList() 90 | for (index in table.selectedRows) { 91 | val row = panel.rowSorter.convertRowIndexToModel(index) 92 | selectedBookmarks.add(panel.model.displayedCors[row]) 93 | } 94 | return selectedBookmarks 95 | } 96 | 97 | private fun useHTTPs(bookmark: CorsObj): Boolean { 98 | return (bookmark.url.protocol.lowercase(Locale.getDefault()) == "https") 99 | } 100 | } -------------------------------------------------------------------------------- /src/burp/CorsHTTP.kt: -------------------------------------------------------------------------------- 1 | package burp 2 | 3 | 4 | import java.awt.Color 5 | 6 | // implement interceptor and modify requests 7 | class HttpListener(private val callbacks: IBurpExtenderCallbacks, private val table: CorsPanel) : IHttpListener { 8 | private val issueHelper = IssueHelper(callbacks) 9 | override fun processHttpMessage(toolFlag: Int, messageIsRequest: Boolean, messageInfo: IHttpRequestResponse) { 10 | 11 | // only intercept proxy requests 12 | if (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY) { 13 | return 14 | } 15 | 16 | // if deactivated, don't perform any actions 17 | if (!table.corsOptions.isActive.isSelected) { 18 | return 19 | } 20 | 21 | // ignore extensions specified if box is checked 22 | val analyzedRequest = callbacks.helpers.analyzeRequest(messageInfo) 23 | val extensions = table.corsOptions.ignoreExtension.text.replace(" ", "").split(",").toTypedArray() 24 | if (analyzedRequest.url.path.substringAfterLast(".").lowercase() in extensions) { 25 | return 26 | } 27 | 28 | //ignore responses 29 | if (!messageIsRequest) { 30 | // ignore if out of scope request and only in scope button selected 31 | if (table.corsOptions.inScope.isSelected && callbacks.isInScope(analyzedRequest.url) || !table.corsOptions.inScope.isSelected) { 32 | val requests = ArrayList() 33 | val colors = ArrayList() 34 | 35 | // add original request 36 | requests.add(messageInfo) 37 | 38 | // add all cors requests 39 | val userUrl = table.corsOptions.urlTextField.text 40 | val helper = CorsHelper(callbacks, userUrl) 41 | requests.addAll(helper.generateCorsRequests(messageInfo)) 42 | 43 | for (req in requests) { 44 | val url = analyzedRequest.url 45 | val urlWithProto = url.protocol + "://" + url.host 46 | val color = helper.evaluateColor(req, urlWithProto) 47 | colors.add(color) 48 | if (color != null) { 49 | issueHelper.generateIssue(color, req, analyzedRequest.url) 50 | } 51 | } 52 | table.addCorsRequestToTable(requests.toTypedArray(), colors.toTypedArray()) 53 | } 54 | } 55 | } 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/burp/CorsHelper.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("ObjectPropertyName", "ObjectPropertyName", "ObjectPropertyName") 2 | 3 | package burp 4 | 5 | import java.awt.Color 6 | import java.util.* 7 | 8 | class CorsHelper(private val callbacks: IBurpExtenderCallbacks, private val url: String) { 9 | 10 | 11 | private fun cloneIHttpRequestResponse( 12 | originalRequestResponse: IHttpRequestResponse 13 | ): IHttpRequestResponse { 14 | return object : IHttpRequestResponse { 15 | var _request = originalRequestResponse.request 16 | var _response = originalRequestResponse.response 17 | var _comment = originalRequestResponse.comment 18 | var _highlight = originalRequestResponse.highlight 19 | var _httpService = originalRequestResponse.httpService 20 | override fun getRequest(): ByteArray { 21 | return _request 22 | } 23 | 24 | override fun setRequest(message: ByteArray) { 25 | _request = message 26 | } 27 | 28 | override fun getResponse(): ByteArray { 29 | return _response 30 | } 31 | 32 | override fun setResponse(message: ByteArray) { 33 | _response = message 34 | } 35 | 36 | override fun getComment(): String { 37 | return _comment 38 | } 39 | 40 | override fun setComment(comment: String) { 41 | this._comment = comment 42 | } 43 | 44 | override fun getHighlight(): String { 45 | return _highlight 46 | } 47 | 48 | override fun setHighlight(color: String) { 49 | _highlight = color 50 | } 51 | 52 | override fun getHttpService(): IHttpService { 53 | return _httpService 54 | } 55 | 56 | override fun setHttpService(httpService: IHttpService) { 57 | this._httpService = httpService 58 | } 59 | } 60 | } 61 | 62 | // create array of all Origin header modifications 63 | private fun corsHeaders(URL: String, BASE_URL: String): Collection { 64 | val corsHeaderArr = ArrayList() 65 | corsHeaderArr.add("Origin: https://$URL") // arbitrary reflection 66 | corsHeaderArr.add("Origin: http://$BASE_URL") // trust HTTP 67 | corsHeaderArr.add("Origin: null") // null origin 68 | corsHeaderArr.add("Origin: https://$BASE_URL.$URL") // prefix match https://evil.com.example.com 69 | corsHeaderArr.add("Origin: https://$BASE_URL$URL") // suffix match https://evil.comexample.com 70 | corsHeaderArr.add("Origin: https://subdomain.$BASE_URL") // trust arbitrary subdomain 71 | corsHeaderArr.add("Origin: https://${BASE_URL.dropLast(1)}") // substring match 72 | corsHeaderArr.add("Origin: https://$BASE_URL" + "_$URL") // underscope bypass https://www.corben.io/advanced-cors-techniques/ example.com_.evil.com 73 | 74 | // dot not escaped 75 | if (BASE_URL.count { "." in BASE_URL } > 1) { 76 | val lastindex = BASE_URL.lastIndexOf(".") 77 | val url = BASE_URL.substring(0, lastindex).replace(".", "x") + BASE_URL.substring(lastindex) 78 | corsHeaderArr.add("Origin: https://$url") // www.example.com -> wwwexample.com 79 | 80 | } 81 | return corsHeaderArr 82 | } 83 | 84 | // wrapper to generatecors headrs and execute requests 85 | fun generateCorsRequests(messageInfo: IHttpRequestResponse): Collection { 86 | val corsRequests = ArrayList() 87 | 88 | val analyzedRequest = callbacks.helpers.analyzeRequest(messageInfo) 89 | 90 | val baseUrl = messageInfo.httpService.host 91 | 92 | for (corsHeader in corsHeaders(url, baseUrl)) { 93 | val newReq = cloneIHttpRequestResponse(messageInfo) 94 | performCorsRequests(newReq, corsHeader, analyzedRequest)?.let { corsRequests.add(it) } 95 | } 96 | 97 | return corsRequests 98 | } 99 | 100 | // execute all cors requests for the original request 101 | private fun performCorsRequests( 102 | newReq: IHttpRequestResponse?, 103 | corsHeader: String, 104 | analyzedRequest: IRequestInfo 105 | ): IHttpRequestResponse? { 106 | val headers = analyzedRequest.headers 107 | val newHeaders = headers?.let { generateNewHeaders(it, corsHeader) } 108 | 109 | val message = callbacks.helpers.buildHttpMessage(newHeaders, 110 | newReq?.request?.let { Arrays.copyOfRange(newReq.request, analyzedRequest.bodyOffset, it.size) }) 111 | newReq?.request = message 112 | 113 | return callbacks.makeHttpRequest(newReq?.httpService, newReq?.request) 114 | } 115 | 116 | private fun generateNewHeaders(headers: List, corsHeader: String): ArrayList { 117 | 118 | //check if origin header already present 119 | val newHeaders = ArrayList() 120 | for (header in headers) { 121 | if (!header.contains("Origin:")) { 122 | newHeaders.add(header) 123 | } 124 | } 125 | newHeaders.add(corsHeader) 126 | return newHeaders 127 | } 128 | 129 | // returns color of a response 130 | fun evaluateColor(requestResponse: IHttpRequestResponse, urlWithProto: String): Color? { 131 | val request = callbacks.helpers.analyzeRequest(requestResponse.request) 132 | 133 | // the response can be null. If so, ignore. 134 | if(requestResponse.response == null) { 135 | return null 136 | } 137 | 138 | if(request.method.equals("OPTIONS", ignoreCase = true)){ 139 | return null 140 | } 141 | 142 | val response: IResponseInfo? = callbacks.helpers.analyzeResponse(requestResponse.response) 143 | 144 | var acac = false 145 | var acao = false 146 | var origin: String? = null 147 | 148 | // get origin 149 | for (reqHeader in request!!.headers) { 150 | if (reqHeader.startsWith("Origin:", ignoreCase = true)) { 151 | origin = reqHeader.substringAfter(":") 152 | } 153 | } 154 | 155 | // check if ACAC and/or ACAO are set 156 | for (respHeader in response!!.headers) { 157 | if (respHeader.contains("Access-Control-Allow-Credentials: true", ignoreCase = true)) { 158 | acac = true 159 | } else if (origin != null && respHeader.replace(" ", "") 160 | .contains("Access-Control-Allow-Origin: $origin".replace(" ", ""), ignoreCase = true) 161 | ) { 162 | if(origin != urlWithProto) { 163 | acao = true 164 | } 165 | } 166 | } 167 | 168 | return if (acac && acao) { 169 | Color.RED 170 | } else if (acao) { 171 | Color.YELLOW 172 | } else { 173 | null 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /src/burp/CorsIssue.kt: -------------------------------------------------------------------------------- 1 | package burp 2 | 3 | 4 | import java.awt.Color 5 | import java.net.URL 6 | 7 | internal class CorsIssue( 8 | private val httpService: IHttpService, 9 | private val url: URL, 10 | private val httpMessages: Array, 11 | private val name: String, 12 | private val detail: String, 13 | private val severity: String, 14 | private val confidence: String, 15 | private val remediation: String 16 | 17 | ) : IScanIssue { 18 | override fun getUrl(): URL { 19 | return url 20 | } 21 | 22 | override fun getIssueName(): String { 23 | return name 24 | } 25 | 26 | override fun getIssueType(): Int { 27 | return 0 28 | } 29 | 30 | override fun getSeverity(): String { 31 | return severity 32 | } 33 | 34 | override fun getConfidence(): String { 35 | return confidence 36 | } 37 | 38 | override fun getIssueBackground(): String? { 39 | return null 40 | } 41 | 42 | override fun getRemediationBackground(): String? { 43 | return null 44 | } 45 | 46 | override fun getIssueDetail(): String { 47 | return detail 48 | } 49 | 50 | override fun getRemediationDetail(): String { 51 | return remediation 52 | } 53 | 54 | override fun getHttpMessages(): Array { 55 | return httpMessages 56 | } 57 | 58 | override fun getHttpService(): IHttpService { 59 | return httpService 60 | } 61 | } 62 | 63 | class IssueHelper(private val callbacks: IBurpExtenderCallbacks){ 64 | 65 | fun getOrigin(response: IResponseInfo): String { 66 | for (respHeader in response.headers) { 67 | if (respHeader.startsWith("Access-Control-Allow-Origin:", ignoreCase = true)) { 68 | return respHeader.substringAfter(":").trim() 69 | } 70 | } 71 | return "" 72 | } 73 | 74 | fun generateIssue(color: Color, requestResponse: IHttpRequestResponse, url: URL) { 75 | val response = callbacks.helpers.analyzeResponse(requestResponse.response) 76 | 77 | val reflectedOrigin: String? 78 | val message = Array(1) { requestResponse } 79 | reflectedOrigin = getOrigin(response) 80 | 81 | 82 | // get current issues to avoid duplicates 83 | val issues = callbacks.getScanIssues(url.protocol + "://" + url.host) 84 | 85 | if (color == Color.RED) { 86 | val detail = "The following Origin was reflected: \"$reflectedOrigin\".
Additionally, \"Access-Control-Allow-Credentials: true\" was set." 87 | val corsIssue = CorsIssue( 88 | requestResponse.httpService, 89 | url, 90 | message, 91 | "CORS*: Cross-origin resource sharing issue", 92 | detail, 93 | "High", 94 | "Certain", 95 | "Rather than programmatically verifying supplied origins, use a whitelist of trusted domains." 96 | ) 97 | for(issue in issues){ 98 | if(issue.issueDetail == detail){ 99 | return 100 | } 101 | } 102 | callbacks.addScanIssue(corsIssue) 103 | } else if (color == Color.YELLOW) { 104 | val detail = "The following Origin was reflected: \"$reflectedOrigin\".
But, \"Access-Control-Allow-Credentials: true\" was NOT set." 105 | val corsIssue = CorsIssue( 106 | requestResponse.httpService, 107 | url, 108 | message, 109 | "CORS*: Cross-origin resource sharing issue", 110 | detail, 111 | "Low", 112 | "Certain", 113 | "Rather than programmatically verifying supplied origins, use a whitelist of trusted domains." 114 | ) 115 | for(issue in issues){ 116 | if(issue.issueDetail == detail){ 117 | return 118 | } 119 | } 120 | callbacks.addScanIssue(corsIssue) 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/burp/CorsMenu.kt: -------------------------------------------------------------------------------- 1 | package burp 2 | 3 | import java.awt.Color 4 | import javax.swing.JMenuItem 5 | 6 | class CorsMenu(private val table: CorsPanel) : IContextMenuFactory { 7 | override fun createMenuItems(invocation: IContextMenuInvocation?): MutableList { 8 | val menuItems: MutableList = arrayListOf() 9 | val requests = invocation?.selectedMessages 10 | val corsButton = JMenuItem("Add Requests to CORSA*") 11 | val colors = requests?.let { Array(it.size) { _ -> null } } 12 | 13 | corsButton.addActionListener { 14 | table.model.refreshCors() 15 | if (requests != null && colors != null) { 16 | table.addCorsRequestToTable(requests, colors) 17 | } 18 | } 19 | 20 | menuItems.add(corsButton) 21 | return menuItems 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/burp/CorsOptions.kt: -------------------------------------------------------------------------------- 1 | package burp 2 | 3 | import java.awt.FlowLayout 4 | import javax.swing.* 5 | 6 | class CorsOptions( 7 | private val corsPanel: CorsPanel, 8 | ) { 9 | val panel = JSplitPane(JSplitPane.HORIZONTAL_SPLIT) 10 | private val loadPanel = JPanel(FlowLayout(FlowLayout.RIGHT)) 11 | val urlTextField = JTextField("www.example.com", 20) 12 | private val configPanel = JPanel(FlowLayout(FlowLayout.LEFT)) 13 | val isActive = JCheckBox("Activate CORS*?") 14 | val inScope = JCheckBox("Only in scope?") 15 | private val ignoreJSAndImages = JCheckBox("Ignore extensions:") 16 | val ignoreExtension = JTextField("ico, svg, js, css, png", 30) 17 | 18 | 19 | init { 20 | val clearButton = JButton("Clear CORS requests") 21 | val urlTextLabel = JLabel("URL for CORS requests:") 22 | 23 | clearButton.addActionListener { clearCors() } 24 | configPanel.add(urlTextLabel) 25 | configPanel.add(urlTextField) 26 | configPanel.add(isActive) 27 | isActive.isSelected = false // do not enable by default as it generates a lot of requests 28 | configPanel.add(inScope) 29 | inScope.isSelected = true 30 | configPanel.add(ignoreJSAndImages) 31 | ignoreJSAndImages.isSelected = true 32 | configPanel.add(ignoreExtension) 33 | 34 | loadPanel.add(clearButton) 35 | panel.leftComponent = configPanel 36 | panel.rightComponent = loadPanel 37 | panel.dividerSize = 0 38 | } 39 | 40 | 41 | private fun clearCors() { 42 | corsPanel.model.clearCors() 43 | corsPanel.requestViewer?.setMessage(ByteArray(0), true) 44 | corsPanel.responseViewer?.setMessage(ByteArray(0), false) 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /src/burp/CorsTab.kt: -------------------------------------------------------------------------------- 1 | package burp 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.GlobalScope 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.swing.Swing 7 | import kotlinx.coroutines.withContext 8 | import java.awt.Color 9 | import java.awt.Component 10 | import java.awt.FlowLayout 11 | import java.net.URL 12 | import javax.swing.* 13 | import javax.swing.table.AbstractTableModel 14 | import javax.swing.table.DefaultTableCellRenderer 15 | import javax.swing.table.TableRowSorter 16 | 17 | 18 | class CorsTab(callbacks: IBurpExtenderCallbacks) : ITab { 19 | val corsTable = CorsPanel(callbacks) 20 | override fun getTabCaption() = "CORS*" 21 | override fun getUiComponent() = corsTable.panel 22 | 23 | } 24 | 25 | data class CorsObj( 26 | val requestResponse: IHttpRequestResponse, 27 | val host: String, 28 | val url: URL, 29 | val origin: String, 30 | val method: String, 31 | val statusCode: String, 32 | val length: String, 33 | val mimeType: String, 34 | var color: Color? 35 | ) 36 | 37 | class CorsPanel(private val callbacks: IBurpExtenderCallbacks) { 38 | val corsOptions = CorsOptions(this) 39 | val model = CorsModel() 40 | val table = JTable(model) 41 | 42 | private val messageEditor = MessageEditor(callbacks) 43 | val requestViewer: IMessageEditor? = messageEditor.requestViewer 44 | val responseViewer: IMessageEditor? = messageEditor.responseViewer 45 | val panel = JSplitPane(JSplitPane.VERTICAL_SPLIT) 46 | val rowSorter = TableRowSorter(model) 47 | 48 | 49 | private val repeatInTable = JCheckBox("Add repeated request to table") 50 | 51 | private val issueHelper = IssueHelper(callbacks) 52 | init { 53 | 54 | // black magic with Java Tables 55 | table.setDefaultRenderer(Any::class.java, object : DefaultTableCellRenderer() { 56 | override fun getTableCellRendererComponent( 57 | table: JTable, 58 | value: Any, 59 | isSelected: Boolean, 60 | hasFocus: Boolean, 61 | row: Int, 62 | column: Int 63 | ): Component { 64 | val c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column) 65 | if (!isSelected) { 66 | c.background = model.getColor(row) 67 | } 68 | return c 69 | } 70 | }) 71 | 72 | CorsActions(this, callbacks) 73 | table.autoResizeMode = JTable.AUTO_RESIZE_OFF 74 | table.columnModel.getColumn(0).preferredWidth = 30 // ID 75 | table.columnModel.getColumn(1).preferredWidth = 245 // Host 76 | table.columnModel.getColumn(2).preferredWidth = 475 // URL 77 | table.columnModel.getColumn(3).preferredWidth = 300 // Origin 78 | table.columnModel.getColumn(4).preferredWidth = 60 // Method 79 | table.columnModel.getColumn(5).preferredWidth = 60 // Status 80 | table.columnModel.getColumn(6).preferredWidth = 60 // Length 81 | table.columnModel.getColumn(7).preferredWidth = 60 // MIME 82 | table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) 83 | table.rowSorter = rowSorter 84 | table.autoscrolls = true 85 | 86 | 87 | table.selectionModel.addListSelectionListener { 88 | if (table.selectedRow != -1) { 89 | val displayedCors = model.displayedCors 90 | val selectedRow = table.convertRowIndexToModel(table.selectedRow) 91 | val requestResponse = displayedCors[selectedRow].requestResponse 92 | messageEditor.requestResponse = requestResponse 93 | requestViewer?.setMessage(requestResponse.request, true) 94 | responseViewer?.setMessage(requestResponse.response ?: ByteArray(0), false) 95 | } 96 | } 97 | 98 | val repeatPanel = JPanel(FlowLayout(FlowLayout.LEFT)) 99 | 100 | val repeatButton = JButton("Send CORS requests for selected entry") 101 | repeatButton.addActionListener { repeatRequest() } 102 | repeatInTable.isSelected = true 103 | 104 | repeatPanel.add(repeatButton) 105 | //repeatPanel.add(repeatInTable) 106 | 107 | val corsTable = JScrollPane(table) 108 | val reqResSplit = 109 | JSplitPane(JSplitPane.HORIZONTAL_SPLIT, requestViewer?.component, responseViewer?.component) 110 | reqResSplit.resizeWeight = 0.5 111 | 112 | val repeatReqSplit = 113 | JSplitPane(JSplitPane.VERTICAL_SPLIT, repeatPanel, reqResSplit) 114 | 115 | val corsOptSplit = 116 | JSplitPane(JSplitPane.VERTICAL_SPLIT, corsOptions.panel, corsTable) 117 | 118 | panel.topComponent = corsOptSplit 119 | panel.bottomComponent = repeatReqSplit 120 | panel.resizeWeight = 0.5 121 | callbacks.customizeUiComponent(panel) 122 | } 123 | 124 | 125 | fun addCorsRequestToTable(requestsResponses: Array, colors: Array) { 126 | for (i in requestsResponses.indices) { 127 | createCors(requestsResponses[i], colors[i]) 128 | } 129 | } 130 | 131 | private fun createCors( 132 | requestResponse: IHttpRequestResponse, color: Color? 133 | ) { 134 | 135 | val savedRequestResponse = callbacks.saveBuffersToTempFiles(requestResponse) 136 | val requestInfo = callbacks.helpers.analyzeRequest(requestResponse) 137 | val response = if (requestResponse.response != null) { 138 | callbacks.helpers.analyzeResponse(requestResponse.response) 139 | } else { 140 | null 141 | } 142 | 143 | val host = requestInfo.url.host 144 | val url = requestInfo.url 145 | val issueHelper = IssueHelper(callbacks) 146 | val origin = response?.let { issueHelper.getOrigin(it) } 147 | val method = requestInfo?.method ?: "" 148 | val statusCode = response?.statusCode?.toString() ?: "" 149 | val length = requestResponse.response?.size?.toString() ?: "" 150 | val mimeType = response?.inferredMimeType ?: "" 151 | 152 | val cors = origin?.let { 153 | CorsObj( 154 | savedRequestResponse, 155 | host, 156 | url, 157 | it, 158 | method, 159 | statusCode, 160 | length, 161 | mimeType, 162 | color 163 | ) 164 | } 165 | if (cors != null) { 166 | model.addCors(cors) 167 | } 168 | 169 | } 170 | 171 | 172 | private fun repeatRequest() { 173 | model.refreshCors() 174 | GlobalScope.launch(Dispatchers.IO) { 175 | 176 | val requestResponse = try { 177 | callbacks.makeHttpRequest(messageEditor.httpService, requestViewer?.message) 178 | } catch (e: java.lang.RuntimeException) { 179 | RequestResponse(requestViewer?.message, null, messageEditor.httpService) 180 | } 181 | 182 | val userUrl = corsOptions.urlTextField.text 183 | val helper = CorsHelper(callbacks, userUrl) 184 | val requests = helper.generateCorsRequests(requestResponse) 185 | val urlWithProto = messageEditor.httpService?.protocol + "://" + messageEditor.httpService?.host 186 | 187 | withContext(Dispatchers.Swing) { 188 | SwingUtilities.invokeLater { 189 | for (request in requests) { 190 | responseViewer?.setMessage(request.response ?: ByteArray(0), false) 191 | val color = helper.evaluateColor(request, urlWithProto) 192 | 193 | if (color != null) { 194 | val analyzedRequest = callbacks.helpers.analyzeRequest(request) 195 | issueHelper.generateIssue(color, request, analyzedRequest.url) 196 | } 197 | createCors(request, color) 198 | } 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | class MessageEditor(callbacks: IBurpExtenderCallbacks) : IMessageEditorController { 206 | var requestResponse: IHttpRequestResponse? = null 207 | 208 | val requestViewer: IMessageEditor? = callbacks.createMessageEditor(this, true) 209 | val responseViewer: IMessageEditor? = callbacks.createMessageEditor(this, false) 210 | 211 | override fun getResponse(): ByteArray = requestResponse?.response ?: ByteArray(0) 212 | 213 | override fun getRequest(): ByteArray? = requestResponse?.request 214 | 215 | override fun getHttpService(): IHttpService? = requestResponse?.httpService 216 | } 217 | 218 | class CorsModel : AbstractTableModel() { 219 | private val columns = 220 | listOf( 221 | "ID", 222 | "Host", 223 | "URL", 224 | "Origin", 225 | "Method", 226 | "Status", 227 | "Length", 228 | "MIME" 229 | ) 230 | private var corsObjArr: MutableList = ArrayList() 231 | var displayedCors: MutableList = ArrayList() 232 | private set 233 | 234 | override fun getRowCount(): Int = displayedCors.size 235 | 236 | override fun getColumnCount(): Int = columns.size 237 | 238 | override fun getColumnName(column: Int): String { 239 | return columns[column] 240 | } 241 | 242 | override fun getColumnClass(columnIndex: Int): Class<*> { 243 | return when (columnIndex) { 244 | 0 -> java.lang.Integer::class.java 245 | 1 -> String::class.java 246 | 2 -> String::class.java 247 | 3 -> String::class.java 248 | 4 -> String::class.java 249 | 5 -> String::class.java 250 | 6 -> String::class.java 251 | 7 -> String::class.java 252 | else -> throw RuntimeException() 253 | } 254 | } 255 | 256 | override fun getValueAt(rowIndex: Int, columnIndex: Int): Any { 257 | val cors = displayedCors[rowIndex] 258 | 259 | return when (columnIndex) { 260 | 0 -> rowIndex 261 | 1 -> cors.host 262 | 2 -> cors.url.toString() 263 | 3 -> cors.origin 264 | 4 -> cors.method 265 | 5 -> cors.statusCode 266 | 6 -> cors.length 267 | 7 -> cors.mimeType 268 | else -> "" 269 | } 270 | } 271 | 272 | override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean { 273 | return false 274 | } 275 | /* 276 | override fun setValueAt(value: Any?, rowIndex: Int, colIndex: Int) { 277 | val cors: Cors = corsarr[rowIndex] 278 | //refreshCors() 279 | } 280 | */ 281 | 282 | fun addCors(corsObj: CorsObj) { 283 | corsObjArr.add(corsObj) 284 | displayedCors = corsObjArr 285 | fireTableRowsInserted(displayedCors.lastIndex, displayedCors.lastIndex) 286 | corsObj.color?.let { setColor(displayedCors.lastIndex, it) } 287 | refreshCors() 288 | } 289 | 290 | fun clearCors() { 291 | corsObjArr.clear() 292 | refreshCors() 293 | } 294 | 295 | fun refreshCors() { 296 | fireTableDataChanged() 297 | } 298 | 299 | fun getColor(row: Int): Color? { 300 | return corsObjArr[row].color 301 | } 302 | 303 | private fun setColor(row: Int, color: Color) { 304 | corsObjArr[row].color = color 305 | } 306 | 307 | } 308 | 309 | class RequestResponse(private var req: ByteArray?, private var res: ByteArray?, private var service: IHttpService?) : 310 | IHttpRequestResponse { 311 | 312 | override fun getComment(): String? = null 313 | 314 | override fun setComment(comment: String?) {} 315 | 316 | override fun getRequest(): ByteArray? = req 317 | 318 | override fun getHighlight(): String? = null 319 | 320 | override fun getHttpService(): IHttpService? = service 321 | 322 | override fun getResponse(): ByteArray? = res 323 | 324 | override fun setResponse(message: ByteArray?) { 325 | res = message 326 | } 327 | 328 | override fun setRequest(message: ByteArray?) { 329 | req = message 330 | } 331 | 332 | override fun setHttpService(httpService: IHttpService?) { 333 | service = httpService 334 | } 335 | 336 | override fun setHighlight(color: String?) {} 337 | } 338 | 339 | --------------------------------------------------------------------------------