Release v2.4.5: detect explicit Deny ACEs in replication permission check
Test-ReplicationPermissions previously only scanned Allow ACEs, so an explicit Deny on the DCSync extended rights (common in hardening baselines that Deny a broad group and Allow only named service accounts) was invisible to the pre-flight check: it reported "verified" while Get-ADReplAccount still failed with "Replication access was denied". The check now flags exactly which right is blocked and by which identity's Deny ACE. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,16 @@ Starting with **v2.2.0**, Elysium uses a **unified project version**. All script
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [2.4.5] — 2026-07-29
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `Test-ReplicationPermissions` now detects explicit **Deny** ACEs on the replication extended rights, not just missing Allow grants. Previously the pre-flight check only scanned `Allow` ACEs, so an explicit Deny (common in hardening baselines that Deny a broad group like `Everyone`/`Domain Users` the replication rights and Allow only named DCSync accounts) was invisible to the check: it reported "verified" while `Get-ADReplAccount` still failed with "Replication access was denied". The check now flags exactly which right is blocked and by which identity's Deny ACE.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- README *Common errors* section expanded with a dedicated troubleshooting flow for "pre-flight passed but DCSync still denied" (RODC target, unconverged ACL replication, cross-domain group scope, and how to get a definitive answer via Event ID 4662 auditing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [2.4.4] — 2026-06-15
|
## [2.4.4] — 2026-06-15
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+25
-6
@@ -1,4 +1,4 @@
|
|||||||
$script:ElysiumVersion = '2.4.4'
|
$script:ElysiumVersion = '2.4.5'
|
||||||
|
|
||||||
function Invoke-RestartWithExecutable {
|
function Invoke-RestartWithExecutable {
|
||||||
param(
|
param(
|
||||||
@@ -403,8 +403,8 @@ function Test-ReplicationPermissions {
|
|||||||
$guid = $rightsToCheck[$rightName]
|
$guid = $rightsToCheck[$rightName]
|
||||||
$granted = $false
|
$granted = $false
|
||||||
$aceExistsForGuid = $false
|
$aceExistsForGuid = $false
|
||||||
|
$denyIdentity = $null
|
||||||
foreach ($ace in $acl) {
|
foreach ($ace in $acl) {
|
||||||
if ($ace.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow) { continue }
|
|
||||||
# InheritOnly ACEs apply to child objects only - the NC root itself is not covered
|
# InheritOnly ACEs apply to child objects only - the NC root itself is not covered
|
||||||
if ([bool]($ace.PropagationFlags -band [System.Security.AccessControl.PropagationFlags]::InheritOnly)) { continue }
|
if ([bool]($ace.PropagationFlags -band [System.Security.AccessControl.PropagationFlags]::InheritOnly)) { continue }
|
||||||
$rights = $ace.ActiveDirectoryRights
|
$rights = $ace.ActiveDirectoryRights
|
||||||
@@ -415,10 +415,22 @@ function Test-ReplicationPermissions {
|
|||||||
-or ($hasExtended -and $ace.ObjectType -eq [guid]::Empty) `
|
-or ($hasExtended -and $ace.ObjectType -eq [guid]::Empty) `
|
||||||
-or ($hasExtended -and $ace.ObjectType -eq $guid)
|
-or ($hasExtended -and $ace.ObjectType -eq $guid)
|
||||||
if (-not $isMatch) { continue }
|
if (-not $isMatch) { continue }
|
||||||
|
if (-not $callerSids.Contains($ace.IdentityReference.Value)) { continue }
|
||||||
|
|
||||||
|
if ($ace.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny) {
|
||||||
|
# Explicit Deny ACEs are evaluated before Allow ACEs in a canonical ACL and win
|
||||||
|
# regardless of any Allow found elsewhere. A check that only scans Allow ACEs would
|
||||||
|
# falsely report the right as granted while the actual DRS call is still denied.
|
||||||
|
$denyIdentity = $ace.IdentityReference.Value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ($ace.ObjectType -eq $guid) { $aceExistsForGuid = $true }
|
if ($ace.ObjectType -eq $guid) { $aceExistsForGuid = $true }
|
||||||
if ($callerSids.Contains($ace.IdentityReference.Value)) { $granted = $true; break }
|
$granted = $true
|
||||||
}
|
}
|
||||||
if (-not $granted) {
|
if ($denyIdentity) {
|
||||||
|
$allMissingLines += "[on $ncDN] $rightName (DENIED by explicit Deny ACE for '$denyIdentity' - this overrides any Allow grant)"
|
||||||
|
} elseif (-not $granted) {
|
||||||
$hint = if ($aceExistsForGuid) {
|
$hint = if ($aceExistsForGuid) {
|
||||||
' (ACE exists but not assigned to this account or any of its groups)'
|
' (ACE exists but not assigned to this account or any of its groups)'
|
||||||
} else {
|
} else {
|
||||||
@@ -436,8 +448,15 @@ function Test-ReplicationPermissions {
|
|||||||
" Grant 'Replicating Directory Changes' on CN=Configuration,$DomainDN" +
|
" Grant 'Replicating Directory Changes' on CN=Configuration,$DomainDN" +
|
||||||
" (covers Schema NC via inheritance) in addition to the domain NC rights.")
|
" (covers Schema NC via inheritance) in addition to the domain NC rights.")
|
||||||
}
|
}
|
||||||
throw ("Account '{0}' failed replication permission check:`n - {1}{2}" -f `
|
$denyNote = ''
|
||||||
$Credential.UserName, ($allMissingLines -join "`n - "), $schemaNote)
|
if ($allMissingLines | Where-Object { $_ -match 'DENIED by explicit Deny ACE' }) {
|
||||||
|
$denyNote = ("`n`nNOTE: at least one right is blocked by an explicit Deny ACE, not a missing grant." +
|
||||||
|
" Find and remove/scope it: Advanced Security on the NC object > look for a Deny entry" +
|
||||||
|
" covering 'Replicating Directory Changes*' that matches this account or one of its groups" +
|
||||||
|
" (common with hardening baselines that Deny a broad group like Everyone/Domain Users).")
|
||||||
|
}
|
||||||
|
throw ("Account '{0}' failed replication permission check:`n - {1}{2}{3}" -f `
|
||||||
|
$Credential.UserName, ($allMissingLines -join "`n - "), $schemaNote, $denyNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ("[+] Replication permissions verified for '{0}' on domain NC and schema NC." -f $Credential.UserName)
|
Write-Host ("[+] Replication permissions verified for '{0}' on domain NC and schema NC." -f $Credential.UserName)
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
##################################################
|
##################################################
|
||||||
## Project: Elysium ##
|
## Project: Elysium ##
|
||||||
## File: Elysium.ps1 ##
|
## File: Elysium.ps1 ##
|
||||||
## Version: 2.4.4 ##
|
## Version: 2.4.5 ##
|
||||||
## Support: support@cqre.net ##
|
## Support: support@cqre.net ##
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
##################################################
|
##################################################
|
||||||
## Project: Elysium ##
|
## Project: Elysium ##
|
||||||
## File: Extract-NTHashes.ps1 ##
|
## File: Extract-NTHashes.ps1 ##
|
||||||
## Version: 2.4.4 ##
|
## Version: 2.4.5 ##
|
||||||
## Support: support@cqre.net ##
|
## Support: support@cqre.net ##
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
##################################################
|
##################################################
|
||||||
## Project: Elysium ##
|
## Project: Elysium ##
|
||||||
## File: Prepare-KHDBStorage.ps1 ##
|
## File: Prepare-KHDBStorage.ps1 ##
|
||||||
## Version: 2.4.4 ##
|
## Version: 2.4.5 ##
|
||||||
## Support: support@cqre.net ##
|
## Support: support@cqre.net ##
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,16 @@ Keep the service account disabled and only activate it for scheduled tests.
|
|||||||
The supplied username/password is invalid for the selected domain controller, or the session is not running in the expected domain context. Re-run and provide valid domain credentials.
|
The supplied username/password is invalid for the selected domain controller, or the session is not running in the expected domain context. Re-run and provide valid domain credentials.
|
||||||
- `Account '<user>' is missing the following replication permissions ...`:
|
- `Account '<user>' is missing the following replication permissions ...`:
|
||||||
Starting with v2.2.0, the script pre-validates the three required replication extended rights against the domain object ACL before attempting DCSync. If this error appears, delegate the listed rights (see *Least privileges* above) and retry.
|
Starting with v2.2.0, the script pre-validates the three required replication extended rights against the domain object ACL before attempting DCSync. If this error appears, delegate the listed rights (see *Least privileges* above) and retry.
|
||||||
- `Replication access was denied` (from `Get-ADReplAccount`):
|
- `Account '<user>' failed replication permission check ... (DENIED by explicit Deny ACE for '<sid>')`:
|
||||||
DSInternals 7.0+ fetches the AD schema via DRS (`GetNCChanges`) as its first step, before replicating any accounts. This fails if the service account lacks `Replicating Directory Changes` on the **schema NC** (`CN=Schema,CN=Configuration,DC=…`). Grant the three rights on `CN=Configuration,DC=…` (covers schema NC via inheritance) in addition to the domain NC — see *Least privileges* above. The pre-flight permission check in v2.4.4+ catches this mismatch before attempting replication.
|
The pre-flight check (v2.4.5+) also scans for explicit **Deny** ACEs on the replication extended rights, not just missing Allow grants. A Deny ACE — commonly added by hardening baselines that Deny a broad group (`Everyone`, `Domain Users`, `Authenticated Users`) the replication rights and Allow only named DCSync accounts — wins over any Allow, even one granted directly to this account. Open *Advanced Security* on the flagged NC object, find the Deny entry that matches this account or one of its groups, and either remove it or exclude the service account/its group from it.
|
||||||
|
- `Replication access was denied` (from `Get-ADReplAccount`) **after the pre-flight check reported success**:
|
||||||
|
This means the ACL looks correct from LDAP but the live DRS (`GetNCChanges`) call still denies access. Known causes, roughly in likelihood order:
|
||||||
|
1. **Target DC is an RODC.** Read-only domain controllers enforce the Password Replication Policy and will refuse to originate a full DCSync of secrets for accounts outside their allowed replication list, regardless of ACL grants. Point `ElysiumSettings.txt` at a writable DC instead.
|
||||||
|
2. **ACL change hasn't converged yet.** If the rights were just delegated on a different DC than the one configured for the test, wait for AD replication to catch up (or force it with `repadmin /syncall`) before retrying.
|
||||||
|
3. **Explicit Deny ACE not caught by an older script version.** Update to v2.4.5+ so the pre-flight check surfaces it (see above) instead of only discovering it at DCSync time.
|
||||||
|
4. **Account is in a cross-domain group** whose scope isn't visible in `tokenGroups` from the DC being queried (for example, a domain-local group in a different domain). Re-delegate directly to the account or to a universal group instead.
|
||||||
|
|
||||||
|
For a definitive answer straight from the DC: enable "Audit Directory Service Access" and add a SACL for `Replicating Directory Changes*` on the domain/schema NC, then check the DC's Security event log for Event ID 4662 on the next failed run — it names the exact object and right that were denied.
|
||||||
- `Only FIPS certified cryptographic algorithms are enabled in .NET`:
|
- `Only FIPS certified cryptographic algorithms are enabled in .NET`:
|
||||||
This warning comes from DSInternals under FIPS-enforced environments. Hash-quality operations that rely on MD5 may be limited.
|
This warning comes from DSInternals under FIPS-enforced environments. Hash-quality operations that rely on MD5 may be limited.
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
##################################################
|
##################################################
|
||||||
## Project: Elysium ##
|
## Project: Elysium ##
|
||||||
## File: Test-WeakADPasswords.ps1 ##
|
## File: Test-WeakADPasswords.ps1 ##
|
||||||
## Version: 2.4.4 ##
|
## Version: 2.4.5 ##
|
||||||
## Support: support@cqre.net ##
|
## Support: support@cqre.net ##
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
##################################################
|
##################################################
|
||||||
## Project: Elysium ##
|
## Project: Elysium ##
|
||||||
## File: Uninstall.ps1 ##
|
## File: Uninstall.ps1 ##
|
||||||
## Version: 2.4.4 ##
|
## Version: 2.4.5 ##
|
||||||
## Support: support@cqre.net ##
|
## Support: support@cqre.net ##
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
##################################################
|
##################################################
|
||||||
## Project: Elysium ##
|
## Project: Elysium ##
|
||||||
## File: Update-KHDB.ps1 ##
|
## File: Update-KHDB.ps1 ##
|
||||||
## Version: 2.4.4 ##
|
## Version: 2.4.5 ##
|
||||||
## Support: support@cqre.net ##
|
## Support: support@cqre.net ##
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
##################################################
|
##################################################
|
||||||
## Project: Elysium ##
|
## Project: Elysium ##
|
||||||
## File: Update-LithnetStore.ps1 ##
|
## File: Update-LithnetStore.ps1 ##
|
||||||
## Version: 2.4.4 ##
|
## Version: 2.4.5 ##
|
||||||
## Support: support@cqre.net ##
|
## Support: support@cqre.net ##
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user