特权更高的 ADCS ESC_CES 攻击

· 2026-10-08 19:50 · 25 阅读

ADHDMurky 2026-10-08 19:50 中国香港

针对 AD CS 的 NTLM 中继长期聚焦于经典 Web 注册,但证书注册服务(CES)同样能签发证书:Web 注册用表单 POST 传 CSR 与 CertAttrib 模板,CES 用 SOAP/WSTEP 的 RequestSecurityToken,CSR 封在 BinarySecurityToken、模板藏在 AdditionalContext。

长期以来,当人们谈到针对 AD CS 的 NTLM 中继时,话题通常集中在以下端点上的经典 Web 注册(Web Enrollment):

/certsrv/
/certsrv/certfnsh.asp

但 AD CS 还有一个组件,可以提供额外的权限提升向量。

AD CS 由以下组件构成(供想深入了解的人参考):

  • 证书颁发机构(Certification Authority)

    ——核心 CA 服务,负责签发、续期、吊销和管理证书。

  • 证书颁发机构 Web 注册(CA Web Enrollment)

    ——用于请求和获取证书的经典 /certsrv/Web 界面。

  • 在线响应器(Online Responder)

    ——提供用于证书吊销检查的 OCSP 响应。

  • 网络设备注册服务(NDES)

    ——网络设备用来注册证书的 NDES/SCEP 服务。

  • 证书注册 Web 服务 / 证书注册策略 Web 服务

    ——基于 Web 服务的注册/策略组件:CES 为证书注册 Web 服务,CEP 为证书注册策略 Web 服务。

再深入看 AD CS 的角色服务,还有另一个同样可以通过 HTTP(S) 暴露的组件:

证书注册服务(Certificate Enrollment Services,简称 CES)

CES 通常位于如下端点:

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

这很有意思,因为 CES 同样可以接受证书注册请求。换句话说,如果攻击者能够以一个被允许注册某模板的主体身份认证到 CES,CES 或许也能返回一张证书。

于是引出了这个显而易见的问题:

如果 Web 注册可以通过 NTLM 中继被滥用从而获取证书,那么 CES 是否也能以类似方式被滥用?

为了回答这个问题,我开始对比经典 Web 注册与 CES 实际使用的 HTTP 请求。我考察了协议、认证协商、请求体、响应格式,以及两个端点周围的防护措施。

由于 CES 通常通过 HTTPS 暴露,且往往拥有自己的受信任 TLS 证书,拦截并理解其流量需要搭起 mitmproxy 或 Burp 这类工具,并正确处理 HTTPS 层。

结果是:Web 注册与 CES 都执行证书注册,但它们是两套完全不同的 HTTP 协议。

Web 注册

Web 注册使用普通的 HTTP 表单 POST。请求的内容类型是:

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

客户端提交 CSR 的请求形如:

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=

重要字段是:

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

例如:

CertAttrib=CertificateTemplate:User

如果包含 SAN/UPN,该属性可能形如:

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

在 Web 注册中,CSR 通过表单字段发送:CertRequest=;证书模板通过 CertAttrib=CertificateTemplate:<template>发送。

经典 Web 注册通常返回 HTML。如果请求被接受,响应往往包含一个请求 ID 或如下链接:

certnew.cer?ReqID=42

客户端随后单独获取证书:

GET /certsrv/certnew.cer?ReqID=42

所以流程通常是:

1. 生成私钥与 CSR
2. 把 CSR POST 到 /certsrv/certfnsh.asp
3. 从 HTML 响应中解析出 ReqID
4. GET /certsrv/certnew.cer?ReqID=<id>
5. 合并私钥与证书
6. 保存为 PFX

CES(证书注册服务)

证书注册服务是另一个 AD CS 角色服务。它暴露一个 WCF 服务端点,通常位于如下路径下:

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

典型 URL 是:

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

与 Web 注册不同,CES 不接受简单的 application/x-www-form-urlencoded请求。

CES 使用 SOAP/WSTEP。WSTEP 是 Web Services Trust Enrollment Protocol(Web 服务信任注册协议)的缩写。实践中,客户端发送一条 WS-Trust 的 SOAP RequestSecurityToken消息。

CES 使用:

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

一条 CES 注册请求发送到:

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>

请求体是 XML,包含一个 SOAP 信封,简化版本如下:

<?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 不是以表单字段发送的,而是嵌入在一个 WS-Security 元素中:

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

证书模板不是以 CertAttrib发送的,而是在 AdditionalContext内发送:

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

这是最重要的差异之一。

Web 注册:

CertAttrib=CertificateTemplate:User

CES:

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

CES 通常直接在 SOAP 响应中返回签发的证书材料。响应包含一个或多个 <BinarySecurityToken>。常见的返回令牌类型包括:

X509v3 certificate
PKCS7 response

因此与 Web 注册不同,CES 通常不需要单独发一次请求去取证书(即那条 GET /certsrv/certnew.cer?ReqID=<id>)。

流程通常是:

1. 生成私钥与 CSR
2. 构建 SOAP RequestSecurityToken
3. 把 SOAP POST 到 /service.svc/CES
4. 解析 SOAP 响应
5. 提取 X509v3 和/或 PKCS7 的 BinarySecurityToken
6. 合并私钥与证书
7. 保存为 PFX

认证与注册

Web 注册与 CES 都可以由 IIS 的 Windows 认证保护。这意味着认证层可能涉及:

NTLM
Negotiate
Kerberos

然而,两个端点的默认行为不同。

经典 Web 注册通常接受:

WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM

而 CES 默认只使用:

WWW-Authenticate: Negotiate

我写了个随手脚本验证这一点,以确保无误:

验证脚本截图

在我看来,这意味着我可以协商出 NTLM 认证——而事实证明确实如此。我构建了一个工具,用 Negotiate 向 CES 端点发送同样的 SOAP 请求。

概念验证如下:

概念验证截图

它对 Negotiate 的 NTLM 与 Kerberos 两种模式都有效。

拦截与中继

那么,为什么 Web 注册的工具不能自动对 CES 生效?

ntlmrelay 工具期望发送类似这样的内容:

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

到:

/certsrv/certfnsh.asp

而 CES 端点不认识那个请求——它期望的是包含 WS-Trust 请求的 SOAP XML:

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

发送到:

/service.svc/CES

概念是相似的:提交 CSR、请求模板、接收证书——但协议完全不同。

基于以上所有认识和我目前学到的东西,我尝试构建一个工具,能够拦截一次认证请求并将其中继到 CES 端点以获取证书。

免责声明:该工具大量基于现有的 impacket ntlmrelay 工具,我只是修改了请求处理部分。

这是我们的拦截与中继工具:

拦截与中继工具截图

拿到那份诱人的用户认证:

获取用户认证截图

以及轻松的 Un-PAC-The-Hash与 Pass-The-Certificate:

Un-PAC-The-Hash 与 Pass-The-Certificate 截图

工具在此获取:https://github.com/ADHDMurky/ADCS_Tools/

抱歉这篇文章有点长,附上一张鱼人(Murloc)的照片:

鱼人照片

阅读原文

跳转微信打开

本文转载自微信公众号「securitainment」,查看原文。版权归原作者所有,如有侵权请联系删除。