Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nahi/5880963 to your computer and use it in GitHub Desktop.
Save nahi/5880963 to your computer and use it in GitHub Desktop.
Hostname check bypassing vulnerability in SSL client (CVE-2013-4073) patches. 0001-Hostname-check-bypassing-vulnerability-in-SSL-client.patch is for 2.0. 0001-Hostname-check-bypassing-vulnerability-in-SSL-client.ruby_1_9_3.patch is for 1.9.3. *UPDATE* The patches could cause SSL crash bug. Please apply https://gist.github.com/nahi/5934959, too.
From ddaf5b57bdc051ccc1161ec5273a59d30fc2fb72 Mon Sep 17 00:00:00 2001
From: Hiroshi Nakamura <nahi@ruby-lang.org>
Date: Wed, 5 Jun 2013 23:14:16 +0900
Subject: [PATCH] Hostname check bypassing vulnerability in SSL client
(CVE-2013-4073)
Ruby's SSL client implements hostname identity check but the OpenSSL
function it depends cannot properly handle hostnames in subjectAltName
that contain null bytes. The fix parses DER encoded bytes of
subjectAltName to extract GeneralName of dNSName and check it against
hostname.
= Vulnerability Summary =
A vulnerability in Ruby's SSL client that could allow man-in-the-middle
attackers to spoof SSL servers via valid certificate issued by a trusted
certification authority.
Ruby's SSL client implements hostname identity check but it does not
properly handle hostnames in the certificate that contain null bytes.
= Details =
OpenSSL::SSL.verify_certificate_identity implements RFC2818 Server
Identity check for Ruby's SSL client but it does not properly handle
hostnames in the subjectAltName X509 extension that contain null bytes.
Existing code in lib/openssl/ssl.rb uses OpenSSL::X509::Extension#value
for extracting identity from subjectAltName. Extension#value depends
OpenSSL function X509V3_EXT_print() and for dNSName of subjectAltName it
utilizes sprintf() that is known as null byte unsafe. As the result
Extension#value returns 'www.ruby-lang.org' if the subjectAltName is
'www.ruby-lang.org\0.example.com' and
OpenSSL::SSL.verify_certificate_identity wrongly identifies the
certificate is for 'www.ruby-lang.org'.
When a CA a SSL client trusts allows to issue the server certificate
that has null byte in subjectAltName, remote attackers can obtain the
certificate for 'www.ruby-lang.org\0.example.com' from the CA to spoof
'www.ruby-lang.org' and do man-in-the-middle between Ruby's SSL client
and SSL servers.
= Credit =
This vulnerability is found by William (B.J.) Snow Orvis and coordinated
with security@ruby-lang.org by David Thiel from iSEC Partners.
---
ext/openssl/lib/openssl/ssl.rb | 18 +++++++++++++-----
test/openssl/test_ssl.rb | 22 ++++++++++++++++++++++
2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb
index bc3b781..04eb592 100644
--- a/ext/openssl/lib/openssl/ssl.rb
+++ b/ext/openssl/lib/openssl/ssl.rb
@@ -98,14 +98,22 @@ module OpenSSL
should_verify_common_name = true
cert.extensions.each{|ext|
next if ext.oid != "subjectAltName"
- ext.value.split(/,\s+/).each{|general_name|
- if /\ADNS:(.*)/ =~ general_name
+ id, ostr = OpenSSL::ASN1.decode(ext.to_der).value
+ sequence = OpenSSL::ASN1.decode(ostr.value)
+ sequence.value.each{|san|
+ case san.tag
+ when 2 # dNSName in GeneralName (RFC5280)
should_verify_common_name = false
- reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
+ reg = Regexp.escape(san.value).gsub(/\\\*/, "[^.]+")
return true if /\A#{reg}\z/i =~ hostname
- elsif /\AIP Address:(.*)/ =~ general_name
+ when 7 # iPAddress in GeneralName (RFC5280)
should_verify_common_name = false
- return true if $1 == hostname
+ # follows GENERAL_NAME_print() in x509v3/v3_alt.c
+ if san.value.size == 4
+ return true if san.value.unpack('C*').join('.') == hostname
+ elsif san.value.size == 16
+ return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname
+ end
end
}
}
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index f3c1aac..daaaa0a 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -337,6 +337,28 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
}
end
+ def test_verify_certificate_identity
+ # creating NULL byte SAN certificate
+ ef = OpenSSL::X509::ExtensionFactory.new
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = OpenSSL::X509::Name.parse "/DC=some/DC=site/CN=Some Site"
+ ext = ef.create_ext('subjectAltName', 'DNS:placeholder,IP:192.168.7.1,IP:13::17')
+ ext_asn1 = OpenSSL::ASN1.decode(ext.to_der)
+ san_list_der = ext_asn1.value.reduce(nil) { |memo,val| val.tag == 4 ? val.value : memo }
+ san_list_asn1 = OpenSSL::ASN1.decode(san_list_der)
+ san_list_asn1.value[0].value = 'www.example.com\0.evil.com'
+ ext_asn1.value[1].value = san_list_asn1.to_der
+ real_ext = OpenSSL::X509::Extension.new ext_asn1
+ cert.add_extension(real_ext)
+
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com\0.evil.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::17'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17'))
+ end
+
def test_tlsext_hostname
return unless OpenSSL::SSL::SSLSocket.instance_methods.include?(:hostname)
--
1.8.1.2
From 0eb55c28a7f696df3f57bb8532c6396588bd82cb Mon Sep 17 00:00:00 2001
From: Hiroshi Nakamura <nahi@ruby-lang.org>
Date: Sun, 23 Jun 2013 16:30:01 +0900
Subject: [PATCH] Hostname check bypassing vulnerability in SSL client
(CVE-2013-4073)
Ruby's SSL client implements hostname identity check but the OpenSSL
function it depends cannot properly handle hostnames in subjectAltName
that contain null bytes. The fix parses DER encoded bytes of
subjectAltName to extract GeneralName of dNSName and check it against
hostname.
= Vulnerability Summary =
A vulnerability in Ruby's SSL client that could allow man-in-the-middle
attackers to spoof SSL servers via valid certificate issued by a trusted
certification authority.
Ruby's SSL client implements hostname identity check but it does not
properly handle hostnames in the certificate that contain null bytes.
= Details =
OpenSSL::SSL.verify_certificate_identity implements RFC2818 Server
Identity check for Ruby's SSL client but it does not properly handle
hostnames in the subjectAltName X509 extension that contain null bytes.
Existing code in lib/openssl/ssl.rb uses OpenSSL::X509::Extension#value
for extracting identity from subjectAltName. Extension#value depends
OpenSSL function X509V3_EXT_print() and for dNSName of subjectAltName it
utilizes sprintf() that is known as null byte unsafe. As the result
Extension#value returns 'www.ruby-lang.org' if the subjectAltName is
'www.ruby-lang.org\0.example.com' and
OpenSSL::SSL.verify_certificate_identity wrongly identifies the
certificate is for 'www.ruby-lang.org'.
When a CA a SSL client trusts allows to issue the server certificate
that has null byte in subjectAltName, remote attackers can obtain the
certificate for 'www.ruby-lang.org\0.example.com' from the CA to spoof
'www.ruby-lang.org' and do man-in-the-middle between Ruby's SSL client
and SSL servers.
= Credit =
This vulnerability is found by William (B.J.) Snow Orvis and coordinated
with security@ruby-lang.org by David Thiel from iSEC Partners.
---
ext/openssl/lib/openssl/ssl-internal.rb | 18 +++++++++++++-----
test/openssl/test_ssl.rb | 22 ++++++++++++++++++++++
2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/ext/openssl/lib/openssl/ssl-internal.rb b/ext/openssl/lib/openssl/ssl-internal.rb
index c70b5b8..356d4e8 100644
--- a/ext/openssl/lib/openssl/ssl-internal.rb
+++ b/ext/openssl/lib/openssl/ssl-internal.rb
@@ -88,14 +88,22 @@ module OpenSSL
should_verify_common_name = true
cert.extensions.each{|ext|
next if ext.oid != "subjectAltName"
- ext.value.split(/,\s+/).each{|general_name|
- if /\ADNS:(.*)/ =~ general_name
+ id, ostr = OpenSSL::ASN1.decode(ext.to_der).value
+ sequence = OpenSSL::ASN1.decode(ostr.value)
+ sequence.value.each{|san|
+ case san.tag
+ when 2 # dNSName in GeneralName (RFC5280)
should_verify_common_name = false
- reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
+ reg = Regexp.escape(san.value).gsub(/\\\*/, "[^.]+")
return true if /\A#{reg}\z/i =~ hostname
- elsif /\AIP Address:(.*)/ =~ general_name
+ when 7 # iPAddress in GeneralName (RFC5280)
should_verify_common_name = false
- return true if $1 == hostname
+ # follows GENERAL_NAME_print() in x509v3/v3_alt.c
+ if san.value.size == 4
+ return true if san.value.unpack('C*').join('.') == hostname
+ elsif san.value.size == 16
+ return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname
+ end
end
}
}
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index cf0f1b7..58493bf 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -351,6 +351,28 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
}
end
+ def test_verify_certificate_identity
+ # creating NULL byte SAN certificate
+ ef = OpenSSL::X509::ExtensionFactory.new
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = OpenSSL::X509::Name.parse "/DC=some/DC=site/CN=Some Site"
+ ext = ef.create_ext('subjectAltName', 'DNS:placeholder,IP:192.168.7.1,IP:13::17')
+ ext_asn1 = OpenSSL::ASN1.decode(ext.to_der)
+ san_list_der = ext_asn1.value.reduce(nil) { |memo,val| val.tag == 4 ? val.value : memo }
+ san_list_asn1 = OpenSSL::ASN1.decode(san_list_der)
+ san_list_asn1.value[0].value = 'www.example.com\0.evil.com'
+ ext_asn1.value[1].value = san_list_asn1.to_der
+ real_ext = OpenSSL::X509::Extension.new ext_asn1
+ cert.add_extension(real_ext)
+
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com\0.evil.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::17'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17'))
+ end
+
def test_tlsext_hostname
return unless OpenSSL::SSL::SSLSocket.instance_methods.include?(:hostname)
--
1.8.1.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment