Even more privileged ADCS ESC_CES

For a long time, when people talked about NTLM relay to AD CS, the conversation usually focused on classic Web Enrollment on the following endpoints:

/certsrv/
/certsrv/certfnsh.asp

But AD CS has one more component that can provide additional vector for privilege escalation

AD CS consists of following components, for those who what to know more:

  • Certification Authority
         - The core CA service that issues, renews, revokes, and manages certificates.
  • Certification Authority Web Enrollment
         - Classic /certsrv/ web interface for requesting and retrieving certificates.
  • Online Responder
         - Provides OCSP responses for certificate revocation checking.
  • Network Device Enrollment Service
         - NDES/SCEP service used by network devices to enroll certificates.
  • Certificate Enrollment Web Service / Certificate Enrollment Policy Web Service
         - Web-service based enrollment/policy components:
             - CES: Certificate Enrollment Web Service
             - CEP: Certificate Enrollment Policy Web Service

When looking deeper at AD CS role services, there is another component that can also be exposed over HTTP(S):

Certificate Enrollment Services, or CES

CES commonly lives at endpoints like:

https://ca.example.local/CA_CES_Kerberos/service.svc/CES
https://ca.example.local/CA_CES_NTLM/service.svc/CES

This is interesting because CES can also accept certificate enrollment requests. In other words, if an attacker can authenticate to CES as a principal that is allowed to enroll a template, CES may also be able to return a certificate.

That led to the obvious question:

If Web Enrollment can be abused through NTLM relay to obtain certificates, can CES be abused in a similar way?

To answer that, I started comparing the actual HTTP requests used by classic Web Enrollment and CES. I looked at protocols, authentication negotiation, request bodies, response formats, and the protections around both endpoints.

Since CES is normally exposed over HTTPS and often has its own trusted TLS certificate, intercepting and understanding the traffic required setting up tools like mitmproxy or Burp and dealing with the HTTPS layer properly.

The result is that Web Enrollment and CES both perform certificate enrollment, but they are completely different HTTP protocols.

Web Enrollment uses a normal HTTP form POST.

The request content type is:

Content-Type: application/x-www-form-urlencoded

The client submits a CSR with a request like:

POST /certsrv/certfnsh.asp HTTP/1.1
Host: ca.example.local
Content-Type: application/x-www-form-urlencoded
Authorization: NTLM <token>

Mode=newreq&CertRequest=<CSR>&CertAttrib=CertificateTemplate:User&TargetStoreFlags=0&SaveCert=yes&ThumbPrint=

The important fields are:

CertRequest=<PKCS10 CSR>
CertAttrib=CertificateTemplate:<template>

For example:

CertAttrib=CertificateTemplate:User

If a SAN/UPN is included, the attribute can look like:

CertAttrib=CertificateTemplate:User%0d%0aSAN:upn=user@example.local

In Web Enrollment, the CSR is sent in the form field:

CertRequest=

The certificate template is sent in:

CertAttrib=CertificateTemplate:<template>

Classic Web Enrollment usually returns HTML.

If the request is accepted, the response often contains a request ID or a link like:

certnew.cer?ReqID=42

The client then fetches the certificate separately:

GET /certsrv/certnew.cer?ReqID=42

So the flow is usually:

1. Generate private key and CSR
2. POST CSR to /certsrv/certfnsh.asp
3. Parse HTML response for ReqID
4. GET /certsrv/certnew.cer?ReqID=<id>
5. Combine private key and certificate
6. Save PFX

Certificate Enrollment Services is a different AD CS role service. It exposes a WCF service endpoint, usually under a path like:

/CA_CES_Kerberos/service.svc/CES
/CA_CES_NTLM/service.svc/CES

A typical URL is:

https://ca.example.local/CA_CES_Kerberos/service.svc/CES

Unlike Web Enrollment, CES does not accept a simple application/x-www-form-urlencoded request.

CES uses SOAP/WSTEP. WSTEP is short for Web Services Trust Enrollment Protocol.

In practice, the client sends a WS-Trust SOAP RequestSecurityToken message.

CES uses:

Content-Type: application/soap+xml; charset=utf-8

A CES enrollment request is sent to:

POST /CA_CES_Kerberos/service.svc/CES HTTP/1.1
Host: ca.example.local
Content-Type: application/soap+xml; charset=utf-8
Authorization: Negotiate <token>

The body is XML it contains a SOAP envelope, a simplified version looks like this:

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope
  xmlns:a="http://www.w3.org/2005/08/addressing"
  xmlns:s="http://www.w3.org/2003/05/soap-envelope">

  <s:Header>
    <a:Action s:mustUnderstand="1">
      http://schemas.microsoft.com/windows/pki/2009/01/enrollment/RST/wstep
    </a:Action>
    <a:MessageID>urn:uuid:...</a:MessageID>
    <a:To s:mustUnderstand="1">
      https://ca.example.local/CA_CES_Kerberos/service.svc/CES
    </a:To>
  </s:Header>

  <s:Body>
    <RequestSecurityToken
      PreferredLanguage="en-US"
      xmlns="http://docs.oasis-open.org/ws-sx/ws-trust/200512">

      <TokenType>
        http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3
      </TokenType>

      <RequestType>
        http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue
      </RequestType>

      <BinarySecurityToken
        ValueType="http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10"
        EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#base64binary"
        xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        BASE64_PKCS10_CSR
      </BinarySecurityToken>

      <AdditionalContext
        xmlns="http://schemas.xmlsoap.org/ws/2006/12/authorization">
        <ContextItem Name="CertificateTemplate">
          <Value>User</Value>
        </ContextItem>
      </AdditionalContext>

    </RequestSecurityToken>
  </s:Body>
</s:Envelope>

CSR is not sent as a form field. It is embedded inside a WS-Security element:

<BinarySecurityToken
  ValueType="http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10"
  EncodingType="...#base64binary">
  BASE64_PKCS10_CSR
</BinarySecurityToken>

The certificate template is not sent as CertAttrib. It is sent inside AdditionalContext:

<AdditionalContext xmlns="http://schemas.xmlsoap.org/ws/2006/12/authorization">
  <ContextItem Name="CertificateTemplate">
    <Value>User</Value>
  </ContextItem>
</AdditionalContext>

This is one of the most important differences.

Web Enrollment:

CertAttrib=CertificateTemplate:User

CES:

<ContextItem Name="CertificateTemplate">
  <Value>User</Value>
</ContextItem>

CES usually returns the issued certificate material directly inside the SOAP response. The response contains one or more:

<BinarySecurityToken>

Common returned token types include:

X509v3 certificate
PKCS7 response

So unlike Web Enrollment, CES normally does not require a separate request to get certificate:

GET /certsrv/certnew.cer?ReqID=<id>

The flow is usually:

1. Generate private key and CSR
2. Build SOAP RequestSecurityToken
3. POST SOAP to /service.svc/CES
4. Parse SOAP response
5. Extract X509v3 and/or PKCS7 BinarySecurityToken
6. Combine private key and certificate
7. Save PFX

Authentication and Enrollment

Both Web Enrollment and CES can be protected by IIS Windows Authentication.

That means the authentication layer may involve:

NTLM
Negotiate
Kerberos

However, default behavior is different between endpoints.

Classic Web Enrollment often accepts:

WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM

While CES, by default, uses the following:

WWW-Authenticate: Negotiate

I tested this via this random script just to make sure:

e639a8b3b6405a7cf012afd29dbdda58.png

In my mind, this meant that I could negotiate NTLM authentication, and that turned out to be true. I built a tool that sends the same SOAP requests to the CES endpoint using Negotiate.

Here a proof-of-concept

e38ddc82a559544cba526ef49288fca2.png

It work with both NTLM and Kerberos mode for Negotiate.


Interception and Relay

So Why Web Enrollment Tooling Does Not Automatically Work Against CES?

ntlmrelay tool expects to send something like:

Mode=newreq&CertRequest=...&CertAttrib=CertificateTemplate:User

to:

/certsrv/certfnsh.asp

A CES endpoint does not understand that request it expects are SOAP XML containing a WS-Trust request:

<RequestSecurityToken>
  <BinarySecurityToken>CSR</BinarySecurityToken>
  <AdditionalContext>
    <ContextItem Name="CertificateTemplate">
      <Value>User</Value>
    </ContextItem>
  </AdditionalContext>
</RequestSecurityToken>

sent to:

/service.svc/CES

The concept is similar: submit CSR, request template, receive certificate, but the protocol is completely different.

With all that in mind, and based on what I’ve learned so far, I tried to build a tool that could intercept an authentication request and relay it to the CES endpoint to obtain a certificate.

Disclamer: Tool is heavily based on existing impacket ntlmrealy tool I just modified request handling.

Here is our intercept and relay tool

cc58ef0389b989ccfe55e5896498bf41.png

Getting that juicy user authentication:
592c9b43b9a12cca5b82f242369ee132.png

And easy Un-PAC-The-Hash and Pass-The-Certificate

914755129a85e4246d4cc9fd097acfd4.png

Tools are available on: https://github.com/ADHDMurky/ADCS_Tools/

Sorry for the long post here is a picture of Murloc

603705165cf3ade366cebe2b8a82b786.png