5 Commits

Author SHA1 Message Date
tomas.kracmar 37d1a8d971 Release v2.2.5: resolve DSInternals module path in block error
The Zone.Identifier block detection now dynamically resolves the
actual DSInternals module installation path via Get-Module instead
of hardcoding a ProgramFiles path, so the Unblock-File command in
the error message is always correct.

All versions bumped to unified v2.2.5.
2026-06-09 13:10:36 +02:00
tomas.kracmar 0175864e72 Release v2.2.4: permission check InheritOnly fix and DSInternals block detection
Test-ReplicationPermissions:
- Skip InheritOnly ACEs since they do not apply to the domain root
  object itself, only to child objects.

Test-WeakADPasswords:
- Detect Windows Zone.Identifier blocks on DSInternals DLLs and
  emit a clear error with the exact Unblock-File remediation
  command instead of a vague warning.

All versions bumped to unified v2.2.4.
2026-06-09 13:07:46 +02:00
tomas.kracmar 9496063b97 Release v2.2.3: improve replication permission detection
Test-ReplicationPermissions now recognizes:
- GenericAll as satisfying replication rights
- Blanket ExtendedRight (empty ObjectType) ACEs

Also adds diagnostic hints distinguishing between
'missing ACE entirely' and 'ACE exists but not for you'.

All versions bumped to unified v2.2.3.
2026-06-09 11:53:44 +02:00
tomas.kracmar 27a682a968 Release v2.2.2: fix replication permission check for nested groups
Test-ReplicationPermissions now uses the tokenGroups constructed
attribute to resolve all effective SIDs in the caller's Kerberos
token, including nested group memberships. This replaces the
previous MemberOf walk which missed indirect entitlement and
could produce false-positive missing-permission errors.

All versions bumped to unified v2.2.2.
2026-06-09 11:41:14 +02:00
tomas.kracmar 255cfe0a17 chore: centralize version and add Bump-Version.ps1
- Add  to Elysium.Common.ps1 as the single
  runtime source of truth for version strings.
- Update Update-KHDB.ps1 User-Agent to reference .
- Update Test-WeakADPasswords.ps1 usage beacon payload to reference
  .
- Add Bump-Version.ps1 release helper that updates the centralized
  variable, ASCII headers across .ps1/.py files, runtime references,
  and prints a CHANGELOG stub.
2026-06-09 11:14:20 +02:00
11 changed files with 247 additions and 24 deletions
+161
View File
@@ -0,0 +1,161 @@
##################################################
## ____ ___ ____ _____ _ _ _____ _____ ##
## / ___/ _ \| _ \| ____| | \ | | ____|_ _| ##
## | | | | | | |_) | _| | \| | _| | | ##
## | |__| |_| | _ <| |___ _| |\ | |___ | | ##
## \____\__\_\_| \_\_____(_)_| \_|_____| |_| ##
## Move fast and fix things. ##
##################################################
## Project: Elysium ##
## File: Bump-Version.ps1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
#Requires -Version 5.1
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
<#
.SYNOPSIS
Bumps the unified Elysium version across all project files.
.DESCRIPTION
Updates the centralized $ElysiumVersion variable, ASCII headers in all
operational scripts, the settings template, and runtime references
(User-Agent, usage beacon, etc.). Optionally stubs a new CHANGELOG entry.
.PARAMETER NewVersion
The new version string to apply (e.g. 2.2.2).
.PARAMETER SkipChangelog
Do not print a CHANGELOG entry stub.
.EXAMPLE
.\Bump-Version.ps1 -NewVersion 2.2.2
#>
param(
[Parameter(Mandatory = $true)]
[string]$NewVersion,
[switch]$SkipChangelog
)
$scriptRoot = $PSScriptRoot
if (-not $scriptRoot) { $scriptRoot = (Get-Location).Path }
# ---------------------------------------------------------------------------
# Validate input
# ---------------------------------------------------------------------------
if ($NewVersion -notmatch '^\d+\.\d+\.\d+$') {
throw "Version must be in semantic format X.Y.Z (e.g. 2.2.2). Got: '$NewVersion'"
}
# ---------------------------------------------------------------------------
# Determine current version from Elysium.Common.ps1
# ---------------------------------------------------------------------------
$commonPath = Join-Path -Path $scriptRoot -ChildPath 'Elysium.Common.ps1'
if (-not (Test-Path -LiteralPath $commonPath)) {
throw "Elysium.Common.ps1 not found at $commonPath"
}
$commonContent = Get-Content -LiteralPath $commonPath -Raw
$currentVersionMatch = [regex]::Match($commonContent, "\`$script:ElysiumVersion\s*=\s*'([^']+)'")
if (-not $currentVersionMatch.Success) {
throw "Could not determine current version from Elysium.Common.ps1"
}
$oldVersion = $currentVersionMatch.Groups[1].Value
if ($oldVersion -eq $NewVersion) {
Write-Warning "Current version is already $NewVersion. Nothing to do."
return
}
Write-Host "Bumping Elysium from $oldVersion -> $NewVersion"
# ---------------------------------------------------------------------------
# Helper: replace in file
# ---------------------------------------------------------------------------
function Edit-FileVersion {
param(
[string]$Path,
[string]$Old,
[string]$New
)
$content = Get-Content -LiteralPath $Path -Raw
$newContent = $content.Replace($Old, $New)
if ($newContent -eq $content) {
Write-Verbose " No changes in $(Split-Path -Leaf $Path)"
} else {
Set-Content -LiteralPath $Path -Value $newContent -NoNewline -Encoding UTF8
Write-Host " Updated $(Split-Path -Leaf $Path)"
}
}
# ---------------------------------------------------------------------------
# 1. Central version variable
# ---------------------------------------------------------------------------
Write-Host "`n[1/4] Updating centralized version variable..."
Edit-FileVersion -Path $commonPath -Old "`$script:ElysiumVersion = '$oldVersion'" -New "`$script:ElysiumVersion = '$NewVersion'"
# ---------------------------------------------------------------------------
# 2. ASCII headers in scripts and templates
# ---------------------------------------------------------------------------
Write-Host "`n[2/4] Updating script headers..."
$headerTargets = Get-ChildItem -Path $scriptRoot -File | Where-Object {
$_.Extension -in @('.ps1', '.py') -or $_.Name -eq 'ElysiumSettings.txt.sample'
}
foreach ($file in $headerTargets) {
$content = Get-Content -LiteralPath $file.FullName -Raw
# The header pattern: ## Version: X.Y.Z ##
$pattern = "## Version:\s+$([regex]::Escape($oldVersion))\s+##"
$replacement = "## Version: $NewVersion ##"
$newContent = [regex]::Replace($content, $pattern, $replacement)
if ($newContent -ne $content) {
Set-Content -LiteralPath $file.FullName -Value $newContent -NoNewline -Encoding UTF8
Write-Host " Updated $($file.Name)"
}
}
# ---------------------------------------------------------------------------
# 3. Runtime references (safety net)
# ---------------------------------------------------------------------------
Write-Host "`n[3/4] Updating runtime version references..."
$runtimeTargets = Get-ChildItem -Path $scriptRoot -Filter '*.ps1' -File
foreach ($file in $runtimeTargets) {
$content = Get-Content -LiteralPath $file.FullName -Raw
$newContent = $content
# User-Agent patterns: 'Elysium/2.2.1 (+...)' or "Elysium/2.2.1 (+...)"
$newContent = [regex]::Replace($newContent,
"Elysium/$([regex]::Escape($oldVersion))",
"Elysium/$NewVersion")
# Literal string assignments: version = '2.2.1'
$newContent = [regex]::Replace($newContent,
"version\s*=\s*'$([regex]::Escape($oldVersion))'",
"version = '$NewVersion'")
if ($newContent -ne $content) {
Set-Content -LiteralPath $file.FullName -Value $newContent -NoNewline -Encoding UTF8
Write-Host " Updated runtime refs in $($file.Name)"
}
}
# ---------------------------------------------------------------------------
# 4. CHANGELOG stub
# ---------------------------------------------------------------------------
if (-not $SkipChangelog) {
Write-Host "`n[4/4] CHANGELOG entry stub (copy-paste ready):"
$today = (Get-Date).ToString('yyyy-MM-dd')
$stub = @"
---
## [$NewVersion] $today
### Changed
- (describe your change here)
"@
Write-Host $stub
Write-Host "`nAppend the above to CHANGELOG.md, then commit and tag."
}
Write-Host "`nDone. Review the changes with: git diff --stat"
+30
View File
@@ -6,6 +6,36 @@ Starting with **v2.2.0**, Elysium uses a **unified project version**. All script
---
## [2.2.5] — 2026-06-09
### Fixed
- The DSInternals `Zone.Identifier` block error message (added in v2.2.4) now dynamically resolves the actual DSInternals module path via `Get-Module` instead of hardcoding `$env:ProgramFiles\WindowsPowerShell\DSInternals`. The `Unblock-File` command in the error now points to the correct installation directory.
---
## [2.2.4] — 2026-06-09
### Fixed
- `Test-ReplicationPermissions` (in `Elysium.Common.ps1`) now skips `InheritOnly` ACEs when evaluating replication rights. An ACE marked `InheritOnly` applies only to child objects, not the domain root itself, so it does not grant the required extended rights for DCSync on the domain object.
- `Import-CompatModule` (in `Test-WeakADPasswords.ps1`) now detects DSInternals being blocked by Windows `Zone.Identifier` (alternate data stream from internet download) and throws a clear, actionable error with the exact `Unblock-File` command to run. Previously this surfaced as an opaque non-FIPS warning.
---
## [2.2.3] — 2026-06-09
### Fixed
- `Test-ReplicationPermissions` (in `Elysium.Common.ps1`) now correctly recognizes `GenericAll` and blanket `ExtendedRight` (empty ObjectType) ACEs as satisfying replication permission requirements. Previously, only exact GUID-matched ExtendedRight ACEs were detected, causing false negatives when rights were granted via broader permissions.
- Improved error diagnostics: the missing-rights message now indicates whether an ACE for the specific right exists on the domain object but is not assigned to the caller, versus no ACE existing at all.
---
## [2.2.2] — 2026-06-09
### Fixed
- `Test-ReplicationPermissions` (in `Elysium.Common.ps1`) now resolves the caller's **effective token SIDs** via the `tokenGroups` constructed attribute instead of walking `MemberOf` directly. This correctly accounts for nested group memberships and avoids false-positive "missing permissions" errors when the account is entitled through nested groups.
---
## [2.2.1] — 2026-06-09
### Changed
+36 -11
View File
@@ -1,3 +1,5 @@
$script:ElysiumVersion = '2.2.5'
function Invoke-RestartWithExecutable {
param(
[string]$ExecutablePath,
@@ -336,14 +338,20 @@ function Test-ReplicationPermissions {
try {
$samName = $Credential.UserName -replace '^.*\\', ''
$adUser = Get-ADUser -Identity $samName -Server $Server -Credential $Credential `
-Properties SID, MemberOf -ErrorAction Stop
-Properties SID, DistinguishedName -ErrorAction Stop
[void]$callerSids.Add($adUser.SID.Value)
foreach ($groupDN in @($adUser.MemberOf)) {
try {
$g = Get-ADGroup -Identity $groupDN -Server $Server -Credential $Credential `
-Properties SID -ErrorAction Stop
[void]$callerSids.Add($g.SID.Value)
} catch { }
# tokenGroups is a constructed attribute containing all SIDs in the user's token,
# including nested group memberships — more reliable than walking MemberOf recursively
$userDe = New-Object System.DirectoryServices.DirectoryEntry(
"LDAP://$Server/$($adUser.DistinguishedName)",
$Credential.UserName,
$Credential.GetNetworkCredential().Password
)
$userDe.RefreshCache(@('tokenGroups'))
foreach ($sidBytes in $userDe.Properties['tokenGroups']) {
$sid = New-Object System.Security.Principal.SecurityIdentifier($sidBytes, 0)
[void]$callerSids.Add($sid.Value)
}
} catch {
Write-Warning ("Could not resolve account SIDs for replication permission pre-check: {0}. Skipping." -f $_.Exception.Message)
@@ -368,17 +376,34 @@ function Test-ReplicationPermissions {
foreach ($rightName in $requiredRights.Keys) {
$guid = $requiredRights[$rightName]
$granted = $false
$aceExistsForGuid = $false
foreach ($ace in $acl) {
if ($ace.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow) { continue }
if (-not ($ace.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight)) { continue }
if ($ace.ObjectType -ne $guid) { continue }
# InheritOnly ACEs apply to child objects only — the domain root itself is not covered
if ([bool]($ace.PropagationFlags -band [System.Security.AccessControl.PropagationFlags]::InheritOnly)) { continue }
$rights = $ace.ActiveDirectoryRights
$hasExtended = [bool]($rights -band [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight)
$hasGenericAll = [bool]($rights -band [System.DirectoryServices.ActiveDirectoryRights]::GenericAll)
# Match: exact GUID, OR ExtendedRight with empty ObjectType (all extended rights), OR GenericAll
$isMatch = $hasGenericAll `
-or ($hasExtended -and $ace.ObjectType -eq [guid]::Empty) `
-or ($hasExtended -and $ace.ObjectType -eq $guid)
if (-not $isMatch) { continue }
if ($ace.ObjectType -eq $guid) { $aceExistsForGuid = $true }
if ($callerSids.Contains($ace.IdentityReference.Value)) { $granted = $true; break }
}
if (-not $granted) { $missing += $rightName }
if (-not $granted) {
$hint = if ($aceExistsForGuid) {
' (ACE exists on the domain object but is not assigned to this account or any of its groups)'
} else {
' (no ACE found for this right on the domain object at all)'
}
$missing += $rightName + $hint
}
}
if ($missing.Count -gt 0) {
throw ("Account '{0}' is missing the following replication permissions on '{1}':`n - {2}`n`nGrant these extended rights on the domain object to allow DCSync-based hash retrieval." -f `
throw ("Account '{0}' failed replication permission check on '{1}':`n - {2}`n`nGrant these extended rights on the domain object to allow DCSync-based hash retrieval." -f `
$Credential.UserName, $DomainDN, ($missing -join "`n - "))
}
+1 -1
View File
@@ -7,7 +7,7 @@
##################################################
## Project: Elysium ##
## File: Elysium.ps1 ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
+1 -1
View File
@@ -8,7 +8,7 @@
##################################################
## Project: Elysium ##
## File: ElysiumSettings.txt ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
+1 -1
View File
@@ -7,7 +7,7 @@
##################################################
## Project: Elysium ##
## File: Extract-NTHashes.ps1 ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
+1 -1
View File
@@ -7,7 +7,7 @@
##################################################
## Project: Elysium ##
## File: Prepare-KHDBStorage.ps1 ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
+10 -3
View File
@@ -8,7 +8,7 @@
##################################################
## Project: Elysium ##
## File: Test-WeakADPasswords.ps1 ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
@@ -95,7 +95,7 @@ function Invoke-UsageBeacon {
if ($normalizedMethod -in @('POST', 'PUT')) {
$payload = [ordered]@{
script = 'Test-WeakADPasswords'
version = '2.2.1'
version = $ElysiumVersion
ranAtUtc = (Get-Date).ToUniversalTime().ToString('o')
}
if (-not [string]::IsNullOrWhiteSpace($InstanceId)) {
@@ -352,7 +352,14 @@ function Import-CompatModule {
$nonFipsErrors = @($importErrors | Where-Object { $_.Exception.Message -notmatch 'Only FIPS certified cryptographic algorithms are enabled in \.NET' })
if ($nonFipsErrors.Count -gt 0) {
Write-Warning ("DSInternals import reported non-fatal warning(s): {0}" -f $nonFipsErrors[0].Exception.Message)
$nonFipsMsg = $nonFipsErrors[0].Exception.Message
if ($nonFipsMsg -match 'Zone\.Identifier|alternate data stream') {
$dsModule = Get-Module -Name DSInternals -ErrorAction SilentlyContinue
if (-not $dsModule) { $dsModule = Get-Module -ListAvailable -Name DSInternals -ErrorAction SilentlyContinue | Select-Object -First 1 }
$dsPath = if ($dsModule) { $dsModule.ModuleBase } else { '<DSInternals module path>' }
throw ("DSInternals native DLL is blocked by Windows (Zone.Identifier). Run the following on the target machine and retry:`n Get-ChildItem -Path '$dsPath' -Recurse | Unblock-File")
}
Write-Warning ("DSInternals import reported non-fatal warning(s): {0}" -f $nonFipsMsg)
}
Write-Verbose ("Imported module '{0}' (Core={1}, Windows={2})" -f $Name, $runningInPSCore, $onWindows)
+1 -1
View File
@@ -7,7 +7,7 @@
##################################################
## Project: Elysium ##
## File: Uninstall.ps1 ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
+2 -2
View File
@@ -7,7 +7,7 @@
##################################################
## Project: Elysium ##
## File: Update-KHDB.ps1 ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################
@@ -61,7 +61,7 @@ function New-HttpClient {
Add-Type -AssemblyName System.Net.Http -ErrorAction SilentlyContinue
$client = [System.Net.Http.HttpClient]::new()
$client.Timeout = [TimeSpan]::FromSeconds(600)
$client.DefaultRequestHeaders.UserAgent.ParseAdd('Elysium/2.2.1 (+Update-KHDB)')
$client.DefaultRequestHeaders.UserAgent.ParseAdd("Elysium/$ElysiumVersion (+Update-KHDB)")
return $client
}
+1 -1
View File
@@ -7,7 +7,7 @@
##################################################
## Project: Elysium ##
## File: Update-LithnetStore.ps1 ##
## Version: 2.2.1 ##
## Version: 2.2.5 ##
## Support: support@cqre.net ##
##################################################