Add SAN parsing with a proper ASN.1 definition
The ORAddress field is left out because it's significantly more complicated than the rest and doesn't appear to be necessary at this time.
This commit is contained in:
parent
39c9355715
commit
0555b4ada0
|
@ -113,7 +113,7 @@ module Exploit::Remote::MsIcpr
|
|||
begin
|
||||
connect
|
||||
rescue Rex::ConnectionError => e
|
||||
raise MsIcprConnectionError, e.message
|
||||
raise MsIcprConnectionError, e.message
|
||||
end
|
||||
|
||||
begin
|
||||
|
@ -211,6 +211,14 @@ module Exploit::Remote::MsIcpr
|
|||
|
||||
return unless response[:certificate]
|
||||
|
||||
if (dns = get_cert_san_dns(response[:certificate]))
|
||||
print_status("Certificate DNS: #{dns}")
|
||||
end
|
||||
|
||||
if (email = get_cert_san_email(response[:certificate]))
|
||||
print_status("Certificate Email: #{email}")
|
||||
end
|
||||
|
||||
if (sid = get_cert_msext_sid(response[:certificate]))
|
||||
print_status("Certificate SID: #{sid}")
|
||||
end
|
||||
|
@ -382,26 +390,34 @@ module Exploit::Remote::MsIcpr
|
|||
# @param [OpenSSL::X509::Certificate] cert
|
||||
# @return [String, nil] The UPN if it was found, otherwise nil.
|
||||
def get_cert_msext_upn(cert)
|
||||
get_cert_ext_property(cert, 'subjectAltName', 'msUPN')
|
||||
return unless (san = get_cert_san(cert))
|
||||
|
||||
return unless (gn = san[:GeneralNames].value.find { |gn| gn[:otherName][:type_id]&.value == OID_NT_PRINCIPAL_NAME })
|
||||
|
||||
RASN1::Types::Utf8String.parse(gn[:otherName][:value].value, explicit: 0, constructed: true).value
|
||||
end
|
||||
|
||||
# Get a value from a certificate extension. Returns nil if it's not found. Allows fetching values not natively
|
||||
# supported by Ruby's OpenSSL by parsing the ASN1 directly.
|
||||
def get_cert_ext_property(cert, ext_oid, key)
|
||||
ext = cert.extensions.find { |e| e.oid == ext_oid }
|
||||
def get_cert_san(cert)
|
||||
ext = cert.extensions.find { |e| e.oid == 'subjectAltName' }
|
||||
return unless ext
|
||||
|
||||
# need to decode the contents and handle them ourselves
|
||||
ext_asn = OpenSSL::ASN1.decode(OpenSSL::ASN1.decode(ext.to_der).value[1].value)
|
||||
ext_asn.value.each do |value|
|
||||
value = value.value
|
||||
next unless value.is_a?(Array)
|
||||
next unless value[0]&.value == key
|
||||
Rex::Proto::CryptoAsn1::X509::SubjectAltName.parse(ext.value_der)
|
||||
end
|
||||
|
||||
return value[1].value[0].value
|
||||
end
|
||||
def get_cert_san_dns(cert)
|
||||
return unless (san = get_cert_san(cert))
|
||||
|
||||
nil
|
||||
return unless (gn = san[:GeneralNames].value.find { |gn| gn[:dNSName].value? })
|
||||
|
||||
gn[:dNSName].value
|
||||
end
|
||||
|
||||
def get_cert_san_email(cert)
|
||||
return unless (san = get_cert_san(cert))
|
||||
|
||||
return unless (gn = san[:GeneralNames].value.find { |gn| gn[:rfc822Name].value? })
|
||||
|
||||
gn[:rfc822Name].value
|
||||
end
|
||||
|
||||
def icpr_service_data
|
||||
|
|
|
@ -2,108 +2,6 @@
|
|||
require 'rasn1'
|
||||
|
||||
module Rex::Proto::CryptoAsn1
|
||||
class RASN1::Model
|
||||
def self.bmp_string(name, options = {})
|
||||
custom_primitive_type_for(name, BmpString, options)
|
||||
end
|
||||
|
||||
def self.teletex_string(name, options = {})
|
||||
strict_encoding = options.fetch(:strict_encoding, true)
|
||||
options.delete(:strict_encoding)
|
||||
|
||||
if strict_encoding
|
||||
raise NotImplementedError.new('The ITU T.61 codec is not available.')
|
||||
custom_primitive_type_for(name, TeletexString, options)
|
||||
else
|
||||
custom_primitive_type_for(name, TeletexString::Permissive, options)
|
||||
end
|
||||
end
|
||||
|
||||
def self.universal_string(name, options = {})
|
||||
custom_primitive_type_for(name, UniversalString, options)
|
||||
end
|
||||
|
||||
def self.custom_primitive_type_for(name, clazz, options = {})
|
||||
options.merge!(name: name)
|
||||
proc = proc do |opts|
|
||||
clazz.new(options.merge(opts))
|
||||
end
|
||||
@root = Elem.new(name, proc, nil)
|
||||
end
|
||||
|
||||
private_class_method :custom_primitive_type_for
|
||||
end
|
||||
|
||||
class BmpString < RASN1::Types::OctetString
|
||||
ID = 30
|
||||
|
||||
# Get ASN.1 type
|
||||
# @return [String]
|
||||
def self.type
|
||||
'BmpString'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value_to_der
|
||||
@value.to_s.dup.encode('UTF-16BE').b
|
||||
end
|
||||
|
||||
def der_to_value(der, ber: false)
|
||||
super
|
||||
@value = der.dup.force_encoding('UTF-16BE')
|
||||
end
|
||||
end
|
||||
|
||||
class TeletexString < RASN1::Types::OctetString
|
||||
ID = 20
|
||||
|
||||
def self.type
|
||||
'TeletexString'
|
||||
end
|
||||
|
||||
ENCODING = 'ITU-T.61'.freeze
|
||||
|
||||
# Technically this type should be using T.61 encoding, however some libraries
|
||||
# such as OpenSSL use this type to label strings encoded with ISO-8859-1.
|
||||
# See:
|
||||
# * https://pike.lysator.liu.se/generated/manual/modref/ex/7.8_3A_3A/Standards/ASN1/Types/TeletexString.html
|
||||
# * https://github.com/wbond/asn1crypto/blob/fad689f2072e405317436c8bf7f6609ba183a060/asn1crypto/x509.py#L461-L465
|
||||
class Permissive < TeletexString
|
||||
ENCODING = 'ISO-8859-1'.freeze
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value_to_der
|
||||
@value.to_s.dup.encode(self.class::ENCODING).b
|
||||
end
|
||||
|
||||
def der_to_value(der, ber: false)
|
||||
super
|
||||
@value = der.dup.force_encoding(self.class::ENCODING)
|
||||
end
|
||||
end
|
||||
|
||||
class UniversalString < RASN1::Types::OctetString
|
||||
ID = 28
|
||||
|
||||
def self.type
|
||||
'UniversalString'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value_to_der
|
||||
@value.to_s.dup.encode('UTF-32BE').b
|
||||
end
|
||||
|
||||
def der_to_value(der, ber: false)
|
||||
super
|
||||
@value = der.dup.force_encoding('UTF-32BE')
|
||||
end
|
||||
end
|
||||
|
||||
# see: [[MS-WCCE]: 2.2.2.7.10 szENROLLMENT_NAME_VALUE_PAIR](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/92f07a54-2889-45e3-afd0-94b60daa80ec)
|
||||
class EnrollmentNameValuePair < RASN1::Model
|
||||
sequence :enrollment_name_value_pair, content: [
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rasn1'
|
||||
|
||||
module Rex::Proto::CryptoAsn1::Types
|
||||
class RASN1::Model
|
||||
def self.bmp_string(name, options = {})
|
||||
custom_primitive_type_for(name, BmpString, options)
|
||||
end
|
||||
|
||||
def self.teletex_string(name, options = {})
|
||||
strict_encoding = options.fetch(:strict_encoding, true)
|
||||
options.delete(:strict_encoding)
|
||||
|
||||
if strict_encoding
|
||||
raise NotImplementedError.new('The ITU T.61 codec is not available.')
|
||||
custom_primitive_type_for(name, TeletexString, options)
|
||||
else
|
||||
custom_primitive_type_for(name, TeletexString::Permissive, options)
|
||||
end
|
||||
end
|
||||
|
||||
def self.universal_string(name, options = {})
|
||||
custom_primitive_type_for(name, UniversalString, options)
|
||||
end
|
||||
|
||||
def self.custom_primitive_type_for(name, clazz, options = {})
|
||||
options.merge!(name: name)
|
||||
proc = proc do |opts|
|
||||
clazz.new(options.merge(opts))
|
||||
end
|
||||
@root = Elem.new(name, proc, nil)
|
||||
end
|
||||
|
||||
private_class_method :custom_primitive_type_for
|
||||
end
|
||||
|
||||
class BmpString < RASN1::Types::OctetString
|
||||
ID = 30
|
||||
|
||||
# Get ASN.1 type
|
||||
# @return [String]
|
||||
def self.type
|
||||
'BmpString'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value_to_der
|
||||
@value.to_s.dup.encode('UTF-16BE').b
|
||||
end
|
||||
|
||||
def der_to_value(der, ber: false)
|
||||
super
|
||||
@value = der.dup.force_encoding('UTF-16BE')
|
||||
end
|
||||
end
|
||||
|
||||
class TeletexString < RASN1::Types::OctetString
|
||||
ID = 20
|
||||
|
||||
def self.type
|
||||
'TeletexString'
|
||||
end
|
||||
|
||||
ENCODING = 'ITU-T.61'.freeze
|
||||
|
||||
# Technically this type should be using T.61 encoding, however some libraries
|
||||
# such as OpenSSL use this type to label strings encoded with ISO-8859-1.
|
||||
# See:
|
||||
# * https://pike.lysator.liu.se/generated/manual/modref/ex/7.8_3A_3A/Standards/ASN1/Types/TeletexString.html
|
||||
# * https://github.com/wbond/asn1crypto/blob/fad689f2072e405317436c8bf7f6609ba183a060/asn1crypto/x509.py#L461-L465
|
||||
class Permissive < TeletexString
|
||||
ENCODING = 'ISO-8859-1'.freeze
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value_to_der
|
||||
@value.to_s.dup.encode(self.class::ENCODING).b
|
||||
end
|
||||
|
||||
def der_to_value(der, ber: false)
|
||||
super
|
||||
@value = der.dup.force_encoding(self.class::ENCODING)
|
||||
end
|
||||
end
|
||||
|
||||
class UniversalString < RASN1::Types::OctetString
|
||||
ID = 28
|
||||
|
||||
def self.type
|
||||
'UniversalString'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value_to_der
|
||||
@value.to_s.dup.encode('UTF-32BE').b
|
||||
end
|
||||
|
||||
def der_to_value(der, ber: false)
|
||||
super
|
||||
@value = der.dup.force_encoding('UTF-32BE')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rasn1'
|
||||
require 'rex/proto/crypto_asn1/types'
|
||||
|
||||
module Rex::Proto::CryptoAsn1::X509
|
||||
class AttributeType < RASN1::Types::ObjectId
|
||||
end
|
||||
|
||||
class AttributeValue < RASN1::Types::Any
|
||||
end
|
||||
|
||||
class AttributeTypeAndValue < RASN1::Model
|
||||
sequence :AttributeTypeAndValue, content: [
|
||||
wrapper(model(:type, AttributeType)),
|
||||
wrapper(model(:value, AttributeValue))
|
||||
]
|
||||
end
|
||||
|
||||
class DirectoryString < RASN1::Model
|
||||
choice :DirectoryString, content: [
|
||||
teletex_string(:teletexString, strict_encoding: false),
|
||||
printable_string(:printableString),
|
||||
universal_string(:universalString),
|
||||
utf8_string(:utf8String),
|
||||
bmp_string(:bmpString)
|
||||
]
|
||||
end
|
||||
|
||||
class EDIPartyName < RASN1::Model
|
||||
sequence :EDIPartyName, content: [
|
||||
wrapper(model(:nameAssigner, DirectoryString), implicit: 0, optional: true),
|
||||
wrapper(model(:partyName, DirectoryString), implicit: 1)
|
||||
]
|
||||
end
|
||||
|
||||
class RelativeDistinguishedName < RASN1::Model
|
||||
set_of(:RelativeDistinguishedName, AttributeTypeAndValue)
|
||||
end
|
||||
|
||||
class RDNSequence < RASN1::Model
|
||||
sequence_of(:RDNSequence, RelativeDistinguishedName)
|
||||
end
|
||||
|
||||
class Name < RASN1::Model
|
||||
choice :Name, content: [
|
||||
wrapper(model(:RDNSequence, RDNSequence))
|
||||
]
|
||||
end
|
||||
|
||||
class OtherName < RASN1::Model
|
||||
sequence :OtherName, implicit: 0, content: [
|
||||
objectid(:type_id),
|
||||
any(:value, explicit: 0, constructed: true)
|
||||
]
|
||||
end
|
||||
|
||||
class GeneralName < RASN1::Model
|
||||
choice :GeneralName, content: [
|
||||
wrapper(model(:otherName, OtherName), implicit: 0),
|
||||
ia5_string(:rfc822Name, implicit: 1),
|
||||
ia5_string(:dNSName, implicit: 2),
|
||||
# wrapper(model(:x400Address, ORAddress), implicit: 3),
|
||||
wrapper(model(:directoryName, Name), implicit: 4),
|
||||
wrapper(model(:ediPartyName, EDIPartyName), implicit: 5),
|
||||
ia5_string(:uniformResourceIdentifier, implicit: 6),
|
||||
octet_string(:iPAddress, implicit: 7),
|
||||
objectid(:registeredID, implicit: 8)
|
||||
]
|
||||
end
|
||||
|
||||
# https://datatracker.ietf.org/doc/html/rfc3280#section-4.2.1.7
|
||||
class GeneralNames < RASN1::Model
|
||||
sequence_of(:GeneralNames, GeneralName)
|
||||
end
|
||||
|
||||
# https://datatracker.ietf.org/doc/html/rfc3280#section-4.2.1.7
|
||||
class SubjectAltName < GeneralNames
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue