How to Add a Subject Alternative Name Safely

I am writing about that PKI stuff again. I am running out of ideas for catchy introductions. So, here is a new post with old code!

In Active Directory a UPN is mapped to a user automatically if it matches a user’s LDAP attribute userPrincipalName (and a DNS SAN is mapped to dnsHostName).  A Windows PKI (ADCS) might be used as a tool to escalate privileges to an administrator account, by enrolling for a certificate featuring the admin’s UPN. This SpecterOps article plus white paper has all the details! Microsoft recently fixed a bug that allowed normal users to impersonate Domain Controllers via changing computers’ dnsHostName attributes! (See also an update to this post by myself at the bottom, based on tests of this patch).

One of the risky configurations is related to a flag described in this post by Keyfactor in detail:

For a variety of reasons, it’s not uncommon for  a customer’s certificate request workflows to evolve to a point where the application or server owner will create a bare bones certificate signing request (CSR) and submit that CSR to a signing authority for completion or for signature. The owner of the PKI, being responsible for ‘finishing’ this CSR, adding the correct Hostname, DNS addresses, email address or IP address to the request. On a Microsoft CA, this is made possible by enabling the EDITF_ATTRIBUTESUBJECTALTNAME2 policy flag on the CA.

That setting can be misused by a user (or machine) having (auto)enroll access to any certificate template – not just to a ‘web server’ template which requires the submission of a custom subject name.

If the flag is not enabled, custom SANs would have to be part of the original request.  As Keyfactor describes, this is sometimes a challenge as administrators of servers or devices do not know how to add the SAN, a device’s software does not allow it, or the exact SAN(s) to be added is/are not known at the time of request generation.

How could a SAN be added to an already signed request … safely?

Windows ADCS does allow for adding any certificate extension to a certificate request in the pending queue – albeit the syntax to do that might be forbidding. You need to submit the content of the extension in ASN.1 encoding.

There is a classic Microsoft white paper from about 2004 about advanced enrollment requests which provides in the appendix:

  • … a working example for how to prepare the encoded version of the SAN with a DNS host name (and a GUID as an ‘exotic’ so-called Other name). In this example, the SAN is then used in an INF file to create a request for a domain controller ‘offline’. But the ASN.1 input file can be used to feed it into certutil -setextension – the command to change extensions in the queue.
  • … the syntax for certutil -setextension, demonstrated for the extension Key Usage. You need to replace the Object ID and the input file by the ones for the SAN.

In the following I am providing a modified version of Microsoft’s script. I am not claiming this is the best tool to do that – but I find it interesting because it will work out-of-the box on a Windows Server without installing anything.

addSAN.vbs

' Based on:
' Advanced Certificate Enrollment and Management, Microsoft White Paper, W2K3 era
' https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc782583(v=ws.10)

Set oArgs = WScript.Arguments
Set oShell = WScript.CreateObject("WScript.Shell")
'
' Parse command line
'
if ((oArgs(0) = "-?") or (oARgs.Count < 1)) then
    Wscript.Echo "Usage: addSAN.vbs [DNS Name]"
else
    sDNShostname = oArgs(0)
end if

Set oFilesystem = CreateObject("Scripting.FileSystemObject")

'##############################################################################
'
' Compute the ASN1 string
'
'##############################################################################
Function ComputeASN1 (iStrLen)
    If Len(Hex(iStrLen)) Mod 2 = 0 then
        sLength = Hex(iStrLen)
    else
        sLength = "0" & Hex(iStrLen)
    end if
    if iStrLen > 127 then
        ComputeASN1 = Hex (128 + (Len(sLength) / 2)) & sLength
    else
        ComputeASN1 = sLength
    End If
End Function

'##############################################################################
'
' Create the ASN.1 file
'
'##############################################################################
Dim aASNsubstring(2, 5)
Dim sFileName
Const HEX_DATA_LENGTH = 1
Const ASCIIDATA = 2
Const HEXDATA = 3
Const HEX_BLOB_LENGTH = 4
Const HEX_TYPE = 5
aASNsubstring(0, ASCIIDATA) = sDNShostname
sASNFileName = sDNSHostname & ".asn"
aASNsubstring(0, HEX_TYPE) = "82"
'
' Convert DNS name into Hexadecimal
'
For i = 1 to Len(aASNsubstring(0, ASCIIDATA))
    aASNsubstring(0, HEXDATA) = aASNsubstring(0, HEXDATA) & _
    Hex(Asc(Mid(aASNsubstring(0, ASCIIDATA), i, 1)))
Next
aASNsubstring(0, HEX_DATA_LENGTH) = ComputeASN1 (Len(aASNsubstring(0, HEXDATA)) / 2)
'
' Build the ASN.1 blob for DNS name
'
sASN = aASNsubstring(0, HEX_TYPE) & _
aASNsubstring(0, HEX_DATA_LENGTH) & _
aASNsubstring(0, HEXDATA)

'
' Write the ASN.1 blob into a file
'
Set oFile = oFilesystem.CreateTextFile(sASNFileName)
'
' Put sequence, total length and ASN1 blob into the file
'
oFile.WriteLine "30" & ComputeASN1 (Len(sASN) / 2) & sASN
oFile.Close

WScript.Echo "Use this command to add a SAN (2.5.29.17) to a pending request as uncritical extension (flag 0)"
WScript.Echo "certutil.exe -setextension <RequestID> 2.5.29.17 0 @" & sASNFileName

Demo – how to use it:

Submit a certificate request without a SAN (If you would add one in the attributes form field, the CA would discard it, because that dangerous flag is not set):

If the certificate template (TEST Web Server) has been configured for CA Manager approval, the Certificate Manager sees it in the queue:

Now run the script on/against the CA as the Cert Manager – it will output the certutil command to be used:

C:\TEST>cscript addSan.vbs very.cool.hostname
Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.

Use this command to add a SAN (2.5.29.17) to a pending request as uncritical extension (flag 0)
certutil.exe -setextension <RequestID> 2.5.29.17 0 @very.cool.hostname.asn

Run the certutil command with the proper certificate request ID:

C:\TEST>certutil.exe -setextension 17 2.5.29.17 0 @very.cool.hostname.asn
0000 30 14 82 12 76 65 72 79 2e 63 6f 6f 6c 2e 68 6f 0...very.cool.ho
0010 73 74 6e 61 6d 65 stname
CertUtil: -setextension command completed successfully.

Check that the extension has been added: Right-click the request in the queue, view attributes and extensions. The origin of the SAN is Admin:

Issue the certificate:

The custom DNS name is included in the SAN:

This task can be delegated to accounts with the permission Manage Certificate at the CA, and those Cert Managers can also be restricted to specific certificate templates.

_____

Edit May 23: I’ve tested the behavior of certificate mapping after the May 2022 security update (the ones triggered by the ‘dnsHostName write vulnerability’ have been applied to DC and CA. The flag EDITF_ATTRIBUTESUBJECTALTNAME2 has been defused, sort of:

If you maliciously add a UPN via /certsrv to a template that ‘should actually be auto-enrolled’, the ‘evil’ UPN is embedded (in addition to the legit CN of the requester from AD), but so is the new extension for the SID of the true requester. Logon via certificate mapping (I tested with IIS certificate mapping) fails – we do not need to configure the (K)DC for Full Enforcement via the new registry keys, or wait until Microsoft does so in one year! Event 41 is recorded at the DC:

The Key Distribution Center (KDC) encountered a user certificate that was valid but contained a different SID than the user to which it mapped. As a result, the request involving the certificate failed.

If you use a template that is configured to add a custom Subject Name in the request (instead of populating names with attributes of the user’s AD object), logon is still possible now in compatibility mode. But this will change in one year latest! Recorded event 39:

The Key Distribution Center (KDC) encountered a user certificate that was valid but could not be mapped to a user in a secure way (such as via explicit mapping, key trust mapping, or a SID). Such certificates should either be replaced or mapped directly to the user via explicit mapping.

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 )

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.