diff --git a/.gitignore b/.gitignore index 6b468b6..96b3efa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ *.class +**/.idea/ +**/.gradle +**/.classes/ +**/build/ +**/.DS_Store \ No newline at end of file diff --git a/Remix/BurpRemix/build.gradle.kts b/Remix/BurpRemix/build.gradle.kts new file mode 100644 index 0000000..c94b496 --- /dev/null +++ b/Remix/BurpRemix/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + kotlin("jvm") version "1.3.72" +} + +version = "0.0.5" + +repositories { + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib-jdk8")) + implementation("net.portswigger.burp.extender:burp-extender-api:2.1") +} + +tasks { + compileKotlin { + kotlinOptions.jvmTarget = "1.8" + } +} + +tasks.withType { + from(configurations.compileClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) +} \ No newline at end of file diff --git a/Remix/BurpRemix/gradle.properties b/Remix/BurpRemix/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/Remix/BurpRemix/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/Remix/BurpRemix/gradle/wrapper/gradle-wrapper.jar b/Remix/BurpRemix/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f3d88b1 Binary files /dev/null and b/Remix/BurpRemix/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Remix/BurpRemix/gradle/wrapper/gradle-wrapper.properties b/Remix/BurpRemix/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ba94df8 --- /dev/null +++ b/Remix/BurpRemix/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Remix/BurpRemix/gradlew b/Remix/BurpRemix/gradlew new file mode 100755 index 0000000..2fe81a7 --- /dev/null +++ b/Remix/BurpRemix/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/Remix/BurpRemix/gradlew.bat b/Remix/BurpRemix/gradlew.bat new file mode 100644 index 0000000..24467a1 --- /dev/null +++ b/Remix/BurpRemix/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Remix/BurpRemix/settings.gradle.kts b/Remix/BurpRemix/settings.gradle.kts new file mode 100644 index 0000000..af6c9bc --- /dev/null +++ b/Remix/BurpRemix/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "huntburpremix" + diff --git a/Remix/BurpRemix/src/main/kotlin/BurpExtender.kt b/Remix/BurpRemix/src/main/kotlin/BurpExtender.kt new file mode 100644 index 0000000..0cb783f --- /dev/null +++ b/Remix/BurpRemix/src/main/kotlin/BurpExtender.kt @@ -0,0 +1,14 @@ +package burp + +class BurpExtender : IBurpExtender { + override fun registerExtenderCallbacks(callbacks: IBurpExtenderCallbacks) { + val tab = HuntTab(callbacks) + callbacks.registerHttpListener(HuntListener(callbacks, tab)) + callbacks.stdout.write("HUNT Remix - v0.0.5".toByteArray()) + callbacks.stdout.write("\nOriginally by: JP Villanueva, Jason Haddix and team at Bugcrowd".toByteArray()) + callbacks.stdout.write("\nRepo: https://github.com/bugcrowd/HUNT".toByteArray()) + callbacks.stdout.write("\nRemixed by: Caleb Kinney (derail.io)".toByteArray()) + callbacks.setExtensionName("HUNT Remix") + callbacks.addSuiteTab(tab) + } +} \ No newline at end of file diff --git a/Remix/BurpRemix/src/main/kotlin/HuntActions.kt b/Remix/BurpRemix/src/main/kotlin/HuntActions.kt new file mode 100644 index 0000000..5e19a43 --- /dev/null +++ b/Remix/BurpRemix/src/main/kotlin/HuntActions.kt @@ -0,0 +1,122 @@ +package burp + +import java.awt.Toolkit +import java.awt.datatransfer.Clipboard +import java.awt.datatransfer.StringSelection +import java.awt.event.ActionEvent +import java.awt.event.ActionListener +import javax.swing.JMenuItem +import javax.swing.JOptionPane +import javax.swing.JPopupMenu + +class HuntActions( + private val panel: HuntPanel, + private val callbacks: IBurpExtenderCallbacks +) : ActionListener { + private val table = panel.table + private val actionsMenu = JPopupMenu() + private val sendToRepeater = JMenuItem("Send request(s) to Repeater") + private val sendToIntruder = JMenuItem("Send request(s) to Intruder") + private val copyURLs = JMenuItem("Copy URL(s)") + private val deleteMenu = JMenuItem("Delete Hunt Issue(s)") + private val clearMenu = JMenuItem("Clear Hunt Issues") + private val comments = JMenuItem("Add comment") + private val details = JMenuItem("Details") + + init { + sendToRepeater.addActionListener(this) + sendToIntruder.addActionListener(this) + copyURLs.addActionListener(this) + deleteMenu.addActionListener(this) + clearMenu.addActionListener(this) + actionsMenu.add(sendToRepeater) + actionsMenu.add(sendToIntruder) + actionsMenu.add(copyURLs) + actionsMenu.addSeparator() + actionsMenu.add(deleteMenu) + actionsMenu.add(clearMenu) + actionsMenu.addSeparator() + comments.addActionListener(this) + details.addActionListener(this) + actionsMenu.addSeparator() + actionsMenu.add(comments) + actionsMenu.add(details) + panel.table.componentPopupMenu = actionsMenu + } + + + override fun actionPerformed(e: ActionEvent?) { + if (table.selectedRow == -1) return + val selectedHuntIssues = getSelectedHuntIssues() + when (val source = e?.source) { + deleteMenu -> { + panel.model.removeHuntIssues(selectedHuntIssues) + } + clearMenu -> { + panel.model.clearHunt() + panel.requestViewer?.setMessage(ByteArray(0), true) + panel.responseViewer?.setMessage(ByteArray(0), false) + } + copyURLs -> { + val urls = selectedHuntIssues.map { it.url }.joinToString() + val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard + clipboard.setContents(StringSelection(urls), null) + } + else -> { + for (selectedHuntIssue in selectedHuntIssues) { + val https = useHTTPs(selectedHuntIssue) + val url = selectedHuntIssue.url + when (source) { + sendToRepeater -> { + var label = selectedHuntIssue.types.first() + if (label.length > 10) { + label = label.substring(0, 9) + "+" + } + callbacks.sendToRepeater( + url.host, + url.port, + https, + selectedHuntIssue.requestResponse.request, + label + ) + } + sendToIntruder -> { + callbacks.sendToIntruder( + url.host, url.port, https, + selectedHuntIssue.requestResponse.request, null + ) + } + comments -> { + val newComments = JOptionPane.showInputDialog("Comments:", selectedHuntIssue.comments) + selectedHuntIssue.comments = newComments + panel.model.refreshHunt() + } + details -> { + selectedHuntIssue.types.forEach { type -> + val details = HuntData().namesDetails[type] + ?.replace("%PARAM%", "'${selectedHuntIssue.parameter}'") + ?.replace("%URL%", "'${selectedHuntIssue.url}'") + JOptionPane.showMessageDialog(null, details) + } + } + } + } + } + } + } + + + private fun getSelectedHuntIssues(): MutableList { + val selectedHuntIssue: MutableList = ArrayList() + for (index in table.selectedRows) { + val row = table.convertRowIndexToModel(index) + selectedHuntIssue.add(panel.model.displayedHuntIssues[row]) + } + return selectedHuntIssue + } + + private fun useHTTPs(huntIssue: HuntIssue): Boolean { + return (huntIssue.url.protocol.toLowerCase() == "https") + + } +} diff --git a/Remix/BurpRemix/src/main/kotlin/HuntData.kt b/Remix/BurpRemix/src/main/kotlin/HuntData.kt new file mode 100644 index 0000000..cd4e386 --- /dev/null +++ b/Remix/BurpRemix/src/main/kotlin/HuntData.kt @@ -0,0 +1,304 @@ +package burp + +class HuntData { + private val insecureDirectObjectReference = + HuntDetail( + name = "Insecure Direct Object Reference", + shortName = "IDOR", + params = mutableSetOf( + "account", + "doc", + "edit", + "email", + "group", + "id", + "key", + "no", + "number", + "order", + "profile", + "report", + "user" + ), + checkLocation = HuntLocation.REQUEST, + enabled = true, + detail = """ + HUNT located the %PARAM% parameter on %URL% inside of your application traffic. + The %PARAM% parameter is most often susceptible to Insecure Direct Object Reference Vulnerabilities. + Direct object reference vulnerabilities occur when there are insufficient authorization checks performed against object identifiers used in requests. +| This could occur when database keys, filenames, or other identifiers are used to directly access resources within an application. +| These identifiers would likely be predictable (an incrementing counter, the name of a file, etc), making it easy for an attacker to detect this vulnerability class. +| If further authorization checks are not performed, this could lead to unauthorized access to the underlying data. +| HUNT recommends further manual analysis of the parameter in question. +| For Insecure Direct Object Reference Vulnerabilities HUNT recommends the following resources to aid in manual testing: +| - The Web Application Hacker's Handbook: Chapter 8 +| - Testing for Insecure Direct Object References (OTG-AUTHZ-004): https://www.owasp.org/index.php/Testing_for_Insecure_Direct_Object_References_(OTG-AUTHZ-004) +| - Using Burp to Test for Insecure Direct Object References: https://support.portswigger.net/customer/portal/articles/1965691-using-burp-to-test-for-insecure-direct-object-references +| - IDOR Examples from ngalongc/bug-bounty-reference: https://github.com/ngalongc/bug-bounty-reference#insecure-direct-object-reference-idor + """.trimIndent(), + level = "Information" + ) + + private val osCommandInjection = HuntDetail( + name = "OS Command Injection", + shortName = "OSCI", + params = mutableSetOf( + "cli", + "cmd", + "daemon", + "dir", + "download", + "execute", + "ip", + "log", + "upload" + ), + checkLocation = HuntLocation.REQUEST, + enabled = true, + detail = """ + HUNT located the %PARAM% parameter on %URL% inside of your application traffic. +| The %PARAM% parameter is most often susceptible to OS Command Injection. +| HUNT recommends further manual analysis of the parameter in question. +| For OS Command Injection HUNT recommends the following resources to aid in manual testing: +| - OWASP Testing for OS Command Injection: https://www.owasp.org/index.php/Testing_for_Command_Injection_(OTG-INPVAL-013) +| - Jobert's How To Command Injection: https://www.hackerone.com/blog/how-to-command-injections +| - Commix Command Injection Tool: https://github.com/commixproject/commix +| - The FuzzDB OS CMD Exec section: https://github.com/fuzzdb-project/fuzzdb/tree/master/attack/os-cmd-execution +| - Ferruh Mavituna's CMDi Cheat Sheet: https://ferruh.mavituna.com/unix-command-injection-cheat-sheet-oku/ +| - The Web Application Hacker's Handbook: Chapter 10 + """.trimIndent(), + level = "Information" + ) + + private val fileInclusionPathTraversal = HuntDetail( + name = "File Inclusion and Path Traversal", + shortName = "FI/PT", + params = mutableSetOf( + "doc", + "document", + "file", + "folder", + "path", + "pdf", + "pg", + "php_path", + "root", + "style", + "template" + ), checkLocation = HuntLocation.REQUEST, + enabled = true, + detail = """ + HUNT located the %PARAM% parameter on %URL% inside of your application traffic. + The %PARAM% parameter is most often susceptible to File Inclusion or Path Traversal. + HUNT recommends further manual analysis of the parameter in question. + Also note that several parameters from this section and SSRF might overlap or need testing for both vulnerability categories. + For File Inclusion or Path Traversal HUNT recommends the following resources to aid in manual testing: + - The Web Application Hacker's Handbook: Chapter 10 + - Arr0way LFI Cheat Sheet: https://highon.coffee/blog/lfi-cheat-sheet/ + - Graceful's Path Traversal Cheat Sheet: Windows: https://www.gracefulsecurity.com/path-traversal-cheat-sheet-windows/ + - Graceful's Path Traversal Cheat Sheet: Linux: https://www.gracefulsecurity.com/path-traversal-cheat-sheet-linux + """.trimIndent(), + level = "Information" + ) + + private val sqlInjection = HuntDetail( + name = "SQL Injection", + shortName = "SQLI", + params = mutableSetOf( + "column", + "delete", + "fetch", + "field", + "filter", + "from", + "id", + "keyword", + "name", + "number", + "order", + "params", + "process", + "query", + "report", + "results", + "role", + "row", + "search", + "sel", + "select", + "sleep", + "sort", + "string", + "table", + "update", + "user", + "view", + "where" + ), checkLocation = HuntLocation.REQUEST, + enabled = true, + detail = """ + HUNT located the %PARAM% parameter on %URL% inside of your application traffic. + The %PARAM% parameter is most often susceptible to SQL Injection. + HUNT recommends further manual analysis of the parameter in question. + For SQL Injection HUNT references The Bug Hunters Methodology SQL Injection references table: + - PentestMonkey's MySQL Injection Cheat Sheet: http://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet + - Reiner's MySQL Injection Filter Evasion: https://websec.wordpress.com/2010/12/04/sqli-filter-evasion-cheat-sheet-mysql + - EvilSQL's Error/Union/Blind MSSQL Cheat Sheet: http://evilsql.com/main/page2.php + - PentestMonkey's MSSQL SQL Injection Cheat Sheet: http://pentestmonkey.net/cheat-sheet/sql-injection/mssql-sql-injection-cheat-sheet + - PentestMonkey's Oracle SQL Cheat Sheet: http://pentestmonkey.net/cheat-sheet/sql-injection/oracle-sql-injection-cheat-sheet + - PentestMonkey's PostgreSQL Cheat Sheet: http://pentestmonkey.net/cheat-sheet/sql-injection/postgres-sql-injection-cheat-sheet + - Access SQL Injection Cheat Sheet: http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html + - PentestMonkey's Ingres SQL Injection Cheat Sheet: http://pentestmonkey.net/cheat-sheet/sql-injection/ingres-sql-injection-cheat-sheet + - PentestMonkey's DB2 SQL Injection Cheat Sheet: http://pentestmonkey.net/cheat-sheet/sql-injection/db2-sql-injection-cheat-sheet + - PentestMonkey's Informix SQL Injection Cheat Sheet: http://pentestmonkey.net/cheat-sheet/sql-injection/informix-sql-injection-cheat-sheet + - SQLite3 Injection Cheat Sheet: https://sites.google.com/site/0x7674/home/sqlite3injectioncheatsheet'>
+ - Ruby on Rails (ActiveRecord) SQL Injection Guide: https://sites.google.com/site/0x7674/home/sqlite3injectioncheatsheet + """.trimIndent(), + level = "Information" + ) + + private val serverSideRequestForgery = HuntDetail( + name = "Server Side Request Forgery", + shortName = "SSRF", + params = mutableSetOf( + "callback", + "continue", + "data", + "dest", + "dir", + "domain", + "feed", + "host", + "html", + "navigation", + "next", + "open", + "out", + "page", + "path", + "port", + "redirect", + "reference", + "return", + "show", + "site", + "to", + "uri", + "url", + "val", + "validate", + "view", + "window" + ), checkLocation = HuntLocation.REQUEST, + enabled = true, + detail = """ + HUNT located the %PARAM% parameter on %URL% inside of your application traffic. + The %PARAM% parameter is most often susceptible to Server Side Request Forgery (and sometimes URL redirects). + HUNT recommends further manual analysis of the parameter in question. + For Server Side Request Forgery HUNT recommends the following resources to aid in manual testing: + - Server-side browsing considered harmful - Nicolas Grégoire: http://www.agarri.fr/docs/AppSecEU15-Server_side_browsing_considered_harmful.pdf + - How To: Server-Side Request Forgery (SSRF) - Jobert Abma: https://www.hackerone.com/blog-How-To-Server-Side-Request-Forgery-SSRF + - SSRF Examples from ngalongc/bug-bounty-reference: https://github.com/ngalongc/bug-bounty-reference#server-side-request-forgery-ssrf + - safebuff SSRF Tips: http://blog.safebuff.com/2016/07/03/SSRF-Tips/ + - The SSRF Bible: https://docs.google.com/document/d/1v1TkWZtrhzRLy0bYXBcdLUedXGb9njTNIJXa3u9akHM/edit + """.trimIndent(), + level = "Information" + ) + + + private val serverSideTemplateInjection = HuntDetail( + name = "Server Side Template Injection", + shortName = "SSTI", + params = mutableSetOf( + "activity", + "content", + "id", + "name", + "preview", + "redirect", + "template", + "view" + ), checkLocation = HuntLocation.REQUEST, + enabled = true, + detail = """ + HUNT located the %PARAM% parameter on %URL% inside of your application traffic. + The %PARAM% parameter is most often susceptible to Server Side Template Injection. + HUNT recommends further manual analysis of the parameter in question. + """.trimIndent(), + level = "Information" + ) + + private val debugLogicParameters = HuntDetail( + name = "Debug and Logic Parameters", + shortName = "DLP", + params = mutableSetOf( + "access", + "adm", + "admin", + "alter", + "cfg", + "clone", + "config", + "create", + "dbg", + "debug", + "delete", + "disable", + "edit", + "enable", + "exec", + "execute", + "grant", + "load", + "make", + "modify", + "rename", + "reset", + "root", + "shell", + "test", + "toggle" + ), checkLocation = HuntLocation.REQUEST, + enabled = true, + detail = """ + HUNT located the %PARAM% parameter on %URL% inside of your application traffic. + The parameter is most often associated to debug, access, or critical functionality in applications. + HUNT recommends further manual analysis of the parameter in question. + """.trimIndent(), + level = "Information" + ) + + private val issues = + mutableListOf( + insecureDirectObjectReference, + osCommandInjection, + fileInclusionPathTraversal, + sqlInjection, + serverSideRequestForgery, + serverSideTemplateInjection, + debugLogicParameters + ) + + val huntParams = issues.map { HuntParams(it.name, it.params) } + val namesDetails = issues.map { it.shortName to it.detail }.toMap() + val nameToShortName = issues.map { it.name to it.shortName }.toMap() + val shortToName = issues.map { it.shortName to it.name }.toMap() + + +} + +data class HuntDetail( + val name: String, + val shortName: String?, + val params: Set, + val checkLocation: HuntLocation, + var enabled: Boolean = true, + val detail: String, + val level: String +) + +data class HuntParams(val name: String, val params: Set) + +enum class HuntLocation { + REQUEST, RESPONSE, BOTH +} \ No newline at end of file diff --git a/Remix/BurpRemix/src/main/kotlin/HuntListener.kt b/Remix/BurpRemix/src/main/kotlin/HuntListener.kt new file mode 100644 index 0000000..0a20e5a --- /dev/null +++ b/Remix/BurpRemix/src/main/kotlin/HuntListener.kt @@ -0,0 +1,96 @@ +package burp + +import java.net.URL +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + + +class HuntListener(private val callbacks: IBurpExtenderCallbacks, private val huntTab: HuntTab) : IHttpListener { + private val helpers: IExtensionHelpers = callbacks.helpers + + override fun processHttpMessage(toolFlag: Int, messageIsRequest: Boolean, messageInfo: IHttpRequestResponse?) { + messageInfo?.let { + if (!messageIsRequest && (callbacks.isInScope(helpers.analyzeRequest(messageInfo).url)) && (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY || toolFlag == IBurpExtenderCallbacks.TOOL_SPIDER)) { + val request = helpers.analyzeRequest(messageInfo) ?: return + val parameters = request.parameters + val huntIssues = + parameters.asSequence().map { param -> checkParameterName(param.name.toLowerCase()) } + .filterNotNull().filter { !it.second.isNullOrEmpty() }.map { + makeHuntRequest( + requestResponse = messageInfo, + parameter = it.first, + types = it.second + ) + }.toList() + + huntTab.huntTable.addHuntIssue(huntIssues) + } + + } + } + + private fun checkParameterName(param: String) = + Pair(param, HuntData().huntParams.asSequence().filter { it.params.contains(param) }.map { it.name }.toSet()) + + private fun makeHuntRequest( + requestResponse: IHttpRequestResponse, + parameter: String, + types: Set + ): HuntIssue { + val now = LocalDateTime.now() + val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") + val dateTime = now.format(dateFormatter) ?: "" + val typeNames = types.map { HuntData().nameToShortName[it] ?: it }.toSet() + val requestInfo = callbacks.helpers.analyzeRequest(requestResponse) + val response = if (requestResponse.response != null) { + callbacks.helpers.analyzeResponse(requestResponse.response) + } else { + null + } + + + return HuntIssue( + requestResponse = callbacks.saveBuffersToTempFiles(requestResponse), + dateTime = dateTime, + host = requestInfo.url.host, + url = requestInfo.url, + types = typeNames, + parameter = parameter, + method = requestInfo?.method ?: "", + statusCode = response?.statusCode?.toString() ?: "", + title = getTitle(requestResponse.response), + length = requestResponse.response?.size?.toString() ?: "", + mimeType = response?.inferredMimeType ?: "", + protocol = requestInfo?.url?.protocol ?: "", + file = requestInfo?.url?.file ?: "", + comments = requestResponse.comment ?: "" + ) + } + + private fun getTitle(response: ByteArray?): String { + if (response == null) return "" + val html = callbacks.helpers.bytesToString(response) + val titleRegex = "(.*?)".toRegex() + val title = titleRegex.find(html)?.value ?: "" + return title.removePrefix("").removeSuffix("") + } +} + +data class HuntIssue( + val requestResponse: IHttpRequestResponsePersisted, + val dateTime: String, + val host: String, + val url: URL, + val types: Set, + val parameter: String, + val method: String, + val statusCode: String, + val title: String, + val length: String, + val mimeType: String, + val protocol: String, + val file: String, + var comments: String +) + + diff --git a/Remix/BurpRemix/src/main/kotlin/HuntOptions.kt b/Remix/BurpRemix/src/main/kotlin/HuntOptions.kt new file mode 100644 index 0000000..93164bc --- /dev/null +++ b/Remix/BurpRemix/src/main/kotlin/HuntOptions.kt @@ -0,0 +1,111 @@ +package burp + +import java.awt.FlowLayout +import javax.swing.* + + +class HuntOptions( + private val huntPanel: HuntPanel, + private val callbacks: IBurpExtenderCallbacks +) { + val panel = JSplitPane(JSplitPane.HORIZONTAL_SPLIT) + private val loadPanel = JPanel(FlowLayout(FlowLayout.RIGHT)) + private val filterBar = JTextField("", 20) + private val filterPanel = JPanel(FlowLayout(FlowLayout.LEFT)) + private val typeComboBox = JComboBox(arrayOf()) + + init { + val clearButton = JButton("Clear Hunt Issues") + val filterLabel = JLabel("Filter Hunt Issues:") + val filterButton = JButton("Filter") + val resetButton = JButton("Reset") + val typeLabel = JLabel("Types:") + typeComboBox.prototypeDisplayValue = "File Inclusion and Path Traversal " + clearButton.addActionListener { clearHuntIssues() } + filterBar.addActionListener { filterHuntIssues() } + filterButton.addActionListener { filterHuntIssues() } + resetButton.addActionListener { resetFilter() } + filterPanel.add(filterLabel) + filterPanel.add(filterBar) + filterPanel.add(typeLabel) + filterPanel.add(typeComboBox) + filterPanel.add(filterButton) + filterPanel.add(resetButton) + loadPanel.add(clearButton) + panel.leftComponent = filterPanel + panel.rightComponent = loadPanel + panel.dividerSize = 0 + } + + fun filtered(): Boolean { + return if (typeComboBox.selectedItem != "All" || filterBar.text.isNotEmpty()) { + filterHuntIssues() + true + } else { + false + } + } + + private fun filterHuntIssues() { + val selectedType = typeComboBox.selectedItem ?: "All" + SwingUtilities.invokeLater { + val searchText = filterBar.text.toLowerCase() + var filteredHuntIssues = this.huntPanel.model.huntIssues + filteredHuntIssues = filterTypes(filteredHuntIssues) + if (searchText.isNotEmpty()) { + filteredHuntIssues = filteredHuntIssues + .filter { + it.comments.toLowerCase().contains(searchText) || + it.url.toString().toLowerCase().contains(searchText) || + callbacks.helpers.bytesToString(it.requestResponse.request).toLowerCase().contains( + searchText + ) || + callbacks.helpers.bytesToString( + it.requestResponse.response ?: ByteArray(0) + ).toLowerCase().contains( + searchText + ) + }.toMutableList() + } + huntPanel.model.refreshHunt(filteredHuntIssues) + if (selectedType != "All") { + typeComboBox.selectedItem = selectedType + } + } + } + + private fun filterTypes(huntIssues: MutableList): MutableList { + val selectedType = typeComboBox.selectedItem ?: "All" + return if (selectedType != "All") { + val type = typeComboBox.selectedItem + huntIssues + .filter { + it.types.contains(HuntData().nameToShortName[type]) + }.toMutableList() + } else { + huntIssues + } + } + + private fun resetFilter() { + filterBar.text = "" + huntPanel.model.refreshHunt() + updateTypes() + huntPanel.requestViewer?.setMessage(ByteArray(0), true) + huntPanel.responseViewer?.setMessage(ByteArray(0), false) + } + + private fun clearHuntIssues() { + huntPanel.model.clearHunt() + huntPanel.requestViewer?.setMessage(ByteArray(0), true) + huntPanel.responseViewer?.setMessage(ByteArray(0), false) + } + + fun updateTypes() { + typeComboBox.removeAllItems() + typeComboBox.addItem("All") + for (type in huntPanel.model.types.sorted()) { + typeComboBox.addItem(type) + } + } +} \ No newline at end of file diff --git a/Remix/BurpRemix/src/main/kotlin/HuntTab.kt b/Remix/BurpRemix/src/main/kotlin/HuntTab.kt new file mode 100644 index 0000000..d4f0160 --- /dev/null +++ b/Remix/BurpRemix/src/main/kotlin/HuntTab.kt @@ -0,0 +1,216 @@ +package burp + +import javax.swing.JScrollPane +import javax.swing.JSplitPane +import javax.swing.JTable +import javax.swing.ListSelectionModel +import javax.swing.table.AbstractTableModel +import javax.swing.table.TableRowSorter + + +class HuntTab(callbacks: IBurpExtenderCallbacks) : ITab { + val huntTable = HuntPanel(callbacks) + + override fun getTabCaption() = "HUNT RMX" + + override fun getUiComponent() = huntTable.panel +} + +class HuntPanel(callbacks: IBurpExtenderCallbacks) { + private val huntOptions = HuntOptions(this, callbacks) + val model = HuntModel(huntOptions) + val table = JTable(model) + + private val messageEditor = MessageEditor(callbacks) + val requestViewer: IMessageEditor? = messageEditor.requestViewer + val responseViewer: IMessageEditor? = messageEditor.responseViewer + + val panel = JSplitPane(JSplitPane.VERTICAL_SPLIT) + private val rowSorter = TableRowSorter(model) + + init { + HuntActions(this, callbacks) + table.autoResizeMode = JTable.AUTO_RESIZE_ALL_COLUMNS + table.columnModel.getColumn(0).preferredWidth = 50 // ID + table.columnModel.getColumn(1).preferredWidth = 160 // date + table.columnModel.getColumn(2).preferredWidth = 125 // host + table.columnModel.getColumn(3).preferredWidth = 250 // url + table.columnModel.getColumn(4).preferredWidth = 150 // type + table.columnModel.getColumn(5).preferredWidth = 75 // parameter + table.columnModel.getColumn(6).preferredWidth = 150 // title + table.columnModel.getColumn(7).preferredWidth = 50 // method + table.columnModel.getColumn(8).preferredWidth = 50 // status + table.columnModel.getColumn(9).preferredWidth = 50 // length + table.columnModel.getColumn(10).preferredWidth = 50 // mime + table.columnModel.getColumn(11).preferredWidth = 50 // protocol + table.columnModel.getColumn(12).preferredWidth = 120 // comments + table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + table.rowSorter = rowSorter + table.autoscrolls = true + table.autoCreateRowSorter = true + + table.selectionModel.addListSelectionListener { + if (table.selectedRow != -1) { + val displayedHuntIssues = model.displayedHuntIssues + val selectedRow = table.convertRowIndexToModel(table.selectedRow) + val requestResponse = displayedHuntIssues[selectedRow].requestResponse + messageEditor.requestResponse = requestResponse + requestViewer?.setMessage(requestResponse.request, true) + responseViewer?.setMessage(requestResponse.response ?: ByteArray(0), false) + } + } + + val huntTable = JScrollPane(table) + val reqResSplit = + JSplitPane(JSplitPane.HORIZONTAL_SPLIT, requestViewer?.component, responseViewer?.component) + reqResSplit.resizeWeight = 0.5 + + val huntOptSplit = + JSplitPane(JSplitPane.VERTICAL_SPLIT, huntOptions.panel, huntTable) + + panel.topComponent = huntOptSplit + panel.bottomComponent = reqResSplit + panel.resizeWeight = 0.5 + callbacks.customizeUiComponent(panel) + } + + fun addHuntIssue(huntRequests: List) { + for (huntRequest in huntRequests) { + model.huntIssues.add(huntRequest) + model.filterOrRefresh() + } + } +} + +class MessageEditor(callbacks: IBurpExtenderCallbacks) : IMessageEditorController { + var requestResponse: IHttpRequestResponse? = null + + val requestViewer: IMessageEditor? = callbacks.createMessageEditor(this, true) + val responseViewer: IMessageEditor? = callbacks.createMessageEditor(this, false) + + override fun getResponse(): ByteArray? = requestResponse?.response ?: ByteArray(0) + + override fun getRequest(): ByteArray? = requestResponse?.request + + override fun getHttpService(): IHttpService? = requestResponse?.httpService +} + +class HuntModel(private val huntOptions: HuntOptions) : AbstractTableModel() { + private val columns = + listOf( + "ID", + "Added", + "Host", + "URL", + "Types", + "Param", + "Title", + "Method", + "Status", + "Length", + "MIME", + "Protocol", + "Comments" + ) + var huntIssues: MutableList = ArrayList() + var types: List = listOf() + var displayedHuntIssues: MutableList = ArrayList() + private set + + override fun getRowCount(): Int = displayedHuntIssues.size + + override fun getColumnCount(): Int = columns.size + + override fun getColumnName(column: Int): String { + return columns[column] + } + + override fun getColumnClass(columnIndex: Int): Class<*> { + return when (columnIndex) { + 0 -> java.lang.Integer::class.java + 1 -> String::class.java + 2 -> String::class.java + 3 -> String::class.java + 4 -> String::class.java + 5 -> String::class.java + 6 -> String::class.java + 7 -> String::class.java + 8 -> String::class.java + 9 -> String::class.java + 10 -> String::class.java + 11 -> String::class.java + 12 -> String::class.java + else -> throw RuntimeException() + } + } + + override fun getValueAt(rowIndex: Int, columnIndex: Int): Any { + + val huntIssue = displayedHuntIssues[rowIndex] + + return when (columnIndex) { + 0 -> rowIndex + 1 -> huntIssue.dateTime + 2 -> huntIssue.host + 3 -> huntIssue.url.toString() + 4 -> huntIssue.types.joinToString() + 5 -> huntIssue.parameter + 6 -> huntIssue.title + 7 -> huntIssue.method + 8 -> huntIssue.statusCode + 9 -> huntIssue.length + 10 -> huntIssue.mimeType + 11 -> huntIssue.protocol + 12 -> huntIssue.comments + else -> "" + } + } + + + override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean { + return when (columnIndex) { + 12 -> true + else -> false + } + } + + override fun setValueAt(value: Any?, rowIndex: Int, colIndex: Int) { + val huntIssue: HuntIssue = huntIssues[rowIndex] + when (colIndex) { + 12 -> huntIssue.comments = value.toString() + else -> return + } + filterOrRefresh() + } + + fun removeHuntIssues(selectedHuntIssues: MutableList) { + huntIssues.removeAll(selectedHuntIssues) + filterOrRefresh() + } + + fun clearHunt() { + huntIssues.clear() + filterOrRefresh() + } + + fun filterOrRefresh() { + if (!huntOptions.filtered()) { + refreshHunt() + } + } + + fun refreshHunt(updatedHuntIssues: MutableList = huntIssues) { + displayedHuntIssues = updatedHuntIssues + fireTableDataChanged() + updateTypes() + } + + private fun updateTypes() { + val shortToName = HuntData().shortToName + val newTypes = displayedHuntIssues.flatMap { it.types }.mapNotNull { shortToName[it] }.toSet().toList() + types = newTypes + huntOptions.updateTypes() + } +} + + diff --git a/Remix/README.md b/Remix/README.md new file mode 100644 index 0000000..e6dbe9b --- /dev/null +++ b/Remix/README.md @@ -0,0 +1,49 @@ +# HUNT Remix + +A complete rewrite of the HUNT scanner. + +## Burp Extension +The [Burp Suite](https://portswigger.net/burp) extension works in both the Community (Free) and Professional versions. + +## Features: +* Passively scan for potentially vulnerable parameters + +#### Screenshot +![HUNT Remix](/Remix/images/huntrmxburp.png) + +## ToDo +- [ ] OWASP ZAP Plugin +- [ ] Ability to add and modify rules +- [ ] Identify reflected parameters + +## Install the HUNT Remix Burp Suite Extension + +### Download or build the extension +#### Option 1: Download release +You can find the latest release (JAR file) [here](https://github.com/cak/HUNT/releases). + +#### Option 2: Build the extension + +```sh +gradle build jar +``` + +Extension JAR will be located at: `build/libs/huntburpremix-x.x.x.jar` + +### Load the extension +1. Open Burp Suite +2. Go to Extender tab +3. Burp Extensions -> Add +4. Load huntburpremix-x.x.x.jar + + +### Usage +#### Passive scanning +1. Set scope +2. Manually navigate or spider the application +3. Requests will vulnerable parameters be added to the `HUNT RMX` tab. +4. Select and right click on request to view details about the vulnerable parameter. + +## Credits +HUNT Remix was created by [cak](https://github.com/cak) [[projects](https://derail.io)] utilizing the research from [JP Villanueva](https://github.com/swagnetow), [Jason Haddix](https://github.com/jhaddix) and team at [Bugcrowd](https://www.bugcrowd.com). + diff --git a/Remix/images/huntrmxburp.png b/Remix/images/huntrmxburp.png new file mode 100644 index 0000000..d0c20f3 Binary files /dev/null and b/Remix/images/huntrmxburp.png differ