Commit Graph

79 Commits

Author SHA1 Message Date
tomas.kracmar 80199ad7d6 fix(Extract-NTHashes): pin plaintext hash export to UTF-8
Out-File defaults to UTF-16LE on Windows PowerShell 5.1 (Desktop) and
UTF-8 on PowerShell 7 (Core), so the encoding of the transient
plaintext hash export depended purely on which host happened to run
the script - inconsistent with Test-WeakADPasswords.ps1/
Update-KHDB.ps1, which already pin this. Applied the same
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 12:06:12 +02:00
tomas.kracmar eea0ddf932 fix(Update-KHDB): dispose HttpResponseMessage on download
Invoke-DownloadWithRetry disposed the response stream and file handle
but never the HttpResponseMessage itself returned by GetAsync,
leaking one per shard download (the S3 code path already disposes
its response object). Added a finally block per attempt to dispose
it on both the success and retry/failure paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 12:05:30 +02:00
tomas.kracmar 2f0ff9085a fix(Prepare-KHDBStorage): reject manifest shard names outside shard root
In -UploadOnly mode, entry.name from the parsed manifest.json was
used directly in Join-Path (local read) and as the remote object key
with no path-traversal check. A tampered local manifest.json
(requires prior local write access to the shard directory) with a
name like '..\..\secrets.txt' could make the upload step read and
upload an arbitrary local file under an attacker-chosen remote key.
Now resolves each shard's full path via GetFullPath and rejects any
entry that resolves outside the shard root, before it's added to the
list actually used for reads/uploads. Verified the resolved-path
containment check against normal relative names (allowed) and '..'
traversal attempts (blocked).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 12:04:43 +02:00
tomas.kracmar 03ceec9d5e fix(Elysium.Common): dispose DirectoryEntry/ADSI objects
Test-ReplicationPermissions and Test-DCClockSkew each build a
DirectoryEntry (holding a live ADSI/COM binding, with a plaintext
credential) and never call .Dispose(), leaking the underlying COM
resource on every invocation - noticeable across a run that checks
multiple domains/DCs in one session. Added finally blocks to dispose
both.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 12:03:31 +02:00
tomas.kracmar 06e7607eff fix(Prepare-KHDBStorage): fast-path dedup, and a scope bug hiding it
Two related bugs in Split-KhdbIntoShards:

1. The fast path for plain 32-hex-char lines wrote every line
   straight to its shard with no deduplication, unlike the slow path
   which merges adjacent duplicate hashes via a per-shard pending-line
   buffer. Routed the fast path through the same pending-line buffer
   so adjacent duplicates are dropped there too.

2. While fixing that, found $total (the valid-entry counter) was a
   plain scalar incremented inside $processHashLine, which is invoked
   via the call operator (&). Scriptblocks invoked with & run in
   their own child scope, so '$total++' inside them silently
   incremented a local shadow copy and never updated the real
   counter in the enclosing function - meaning TotalEntries/
   validEntries (used in the returned result, the checkpoint state,
   and the live progress status) were wrong on every run, previously
   masked because actual shard file writes go through $state.Writer
   objects (reference types, unaffected by the scoping issue).
   Moved the counter onto $meta (already a hashtable used the same
   way for TotalLines/InvalidLines/etc.) so it mutates by reference
   across the scope boundary.

Verified both with an isolated test harness (function extracted and
dot-sourced, no AD/Windows dependency): adjacent AAAA/CCCC duplicates
in a 6-line sample now collapse to 3 shard entries, and TotalEntries
correctly reports 3 instead of the previous 0/wrong value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 12:01:28 +02:00
tomas.kracmar 9bef6d50f5 fix(Test-WeakADPasswords): prevent report column truncation
$testResults | Out-String used the default formatter width, which
truncates long columns at the console/default width. The report text
is re-parsed line-by-line to attach "UPN:" annotations, taking the
first whitespace token as SamAccountName - a truncated account name
would silently fail to match $dictionarySamToUpn and drop the
annotation with no error. Pass -Width 4096 to avoid truncation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:54:47 +02:00
tomas.kracmar d155276366 fix(Extract-NTHashes): remove mismatched blob after checksum failure
On upload checksum mismatch, the script warned and preserved the
local encrypted file but left the already-uploaded, corrupt blob live
in remote storage under its normal name - discoverable only via an
easy-to-miss console warning, and fetchable as if it were good data
by any downstream consumer. Added Invoke-S3DeleteFile (SigV4-signed,
mirrors the existing Put/Get helpers) and now delete the mismatched
blob from S3 (AWS Tools or native HTTP path) or Azure Blob Storage
right after detecting the mismatch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:54:21 +02:00
tomas.kracmar 867fb6427d fix(Update-KHDB): cap KHDB backup retention
Every successful update created a new khdb.txt.bak-<timestamp> with
no cleanup anywhere, so a recurring scheduled job accumulated one
full-size backup per run indefinitely. Now keeps only the 5 most
recent backups, pruning older ones after each new backup is created.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:53:26 +02:00
tomas.kracmar 1440b65b9a fix(Update-KHDB): accept manifest's full supported shardSize range
Validate-Manifest hardcoded shardSize -ne 2 as an error, but
Prepare-KHDBStorage.ps1's -ShardSize parameter validly accepts 1-8.
Merge-ShardsToFile doesn't use shardSize for anything structural - it
reads shard files purely by name/content - so the check added no
safety, only rejected manifests produced with any supported shard
size other than 2. Widened the check to the same 1-8 range instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:52:58 +02:00
tomas.kracmar 5691463bd3 fix(Elysium): catch sub-script errors instead of crashing the menu
The menu loop had try/finally with no catch. Update-KHDB.ps1,
Test-WeakADPasswords.ps1, and Extract-NTHashes.ps1 all run under
$ErrorActionPreference = 'Stop' and re-throw on failure, so any
runtime error (bad credentials, unreachable DC, network failure)
propagated uncaught through the parent and killed the whole
orchestrator instead of returning to the menu. Wrapped the switch in
try/catch so a failed option reports the error and redisplays the
menu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:52:13 +02:00
tomas.kracmar 91d6bcb216 fix(Test-ReplicationPermissions): resolve UPN-formatted credentials
Get-ADUser -Identity accepts a DN, GUID, SID, or sAMAccountName - not
a UPN. The SID/tokenGroups resolution stripped only a 'DOMAIN\'
prefix, so a UPN-formatted credential (user@domain.tld) had no
backslash to strip, -Identity threw on the full UPN, and the
exception was swallowed by the surrounding catch (Write-Warning +
return), silently skipping the entire DCSync ACL pre-check.

Now branches on the credential format: DOMAIN\user strips the prefix
as before, user@domain.tld resolves via
-Filter "UserPrincipalName -eq '...'", and a bare sAMAccountName is
used as-is. The follow-up tokenGroups lookup now uses the resolved
DistinguishedName instead of re-deriving the username format.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:51:41 +02:00
tomas.kracmar ec00518952 fix(passphrase): store DPAPI-protected, never plaintext or echoed
Elysium.ps1 used Read-Host without -AsSecureString (echoed the AES
passphrase to the console) and persisted it as plaintext in
HKCU\Environment via SetEnvironmentVariable, readable by anyone with
console visibility or local registry access. Switched to
Read-Host -AsSecureString and store ConvertFrom-SecureString's DPAPI
output (decryptable only by the same user on the same machine)
instead of the raw value.

Extract-NTHashes.ps1, the only other consumer, now decrypts that
DPAPI-protected string back to a plain string via
ConvertTo-SecureString + NetworkCredential right before handing it to
Protect-FileWithAES. A stale plaintext value from a prior version is
detected and the operator is prompted to re-enter it.

DPAPI protection is Windows-only, consistent with the rest of this
Windows/AD-only tool; not runnable end-to-end on this (non-Windows)
dev machine, verified by AST parse only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:50:59 +02:00
tomas.kracmar 4740cd3e97 fix(Extract-NTHashes): restrict ACL on temp dir holding plaintext hashes
Live NTLM hashes are written unencrypted to a temp file before AES
protection is applied. Cleanup only runs in a finally block, so a
hard kill/crash between write and cleanup could leave plaintext
hashes on disk under a directory that inherits whatever broad ACL its
parent Temp folder has (worst case: C:\Windows\Temp when run as
SYSTEM). Strip inherited ACEs and grant only the current user on the
temp directory right after creating it, narrowing exposure for that
window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:50:16 +02:00
tomas.kracmar 855be8de9c fix(Uninstall): use script location, fix broken self-delete
Two bugs in Uninstall-Elysium:
- $ElysiumPath came from Get-Location (the caller's current working
  directory) instead of $PSScriptRoot. Running the script from any
  CWD other than the install folder recursively force-deleted the
  wrong directory.
- The self-delete workaround was also broken: Remove-Item -Exclude
  matches leaf names via wildcard, not the full path it was given, so
  the exclusion never matched; and the deferred
  "powershell.exe -Command <scriptblock> -ArgumentList <path>" command
  line is not valid syntax for binding $path in the child process.

PowerShell reads a script fully into memory before running it, so it
holds no open handle on the file - deleting the install directory
(including this script) while it's still executing is safe. Dropped
the exclude/deferred-process workaround entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:49:47 +02:00
tomas.kracmar ac3f30db1e fix(Bump-Version): param() must be script's first statement
$ErrorActionPreference and Set-StrictMode sat before param(), so
PowerShell parsed 'param(...)' as a call to a command named param
instead of the script's parameter block ("The function or command
was called as if it were a method"). The script never ran. Moved
both statements after the param block; only #Requires and the
comment-based help may precede param().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 11:49:21 +02:00
tomas.kracmar 65e451413e 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>
2026-07-29 11:49:14 +02:00
tomas.kracmar 1d98b908c6 Release v2.4.4: check schema NC replication rights for DSInternals 7.0
DSInternals 7.0 fetches the AD schema via DRS (GetNCChanges) before
replicating accounts, so the schema NC has its own ACL requirement.

- Test-ReplicationPermissions now validates rights on both the
  domain NC and the configuration NC (schema NC inherits from it).
- Updated README with dsacls delegation examples and dual-NC
  least-privilege requirements.
- Improved 'Replication access was denied' error message to name
  both NCs and explain the DSInternals 7.0 change.
- Diagnostic dump now includes SchemaDN.

All versions bumped to unified v2.4.4.
v2.4.4
2026-06-15 08:38:04 +02:00
tomas.kracmar 906bb52638 fix(Test-WeakADPasswords): add comprehensive DCSync diagnostic dump
When Get-ADReplAccount or Test-PasswordQuality throws, the catch
block now dumps the full exception chain (type, message, HResult,
source, target site, stack trace, inner exceptions) along with
runtime context (Elysium version, PS version, DSInternals version,
DC, domain, account). Output goes to console and a timestamped
 diagnostic file under Reports/ for offline analysis.
2026-06-09 16:23:38 +02:00
tomas.kracmar af945f529e Release v2.4.3: fix tokenGroups retrieval and DirectoryEntry LDAP paths
Test-ReplicationPermissions:
- Replaced DirectoryEntry.RefreshCache tokenGroups retrieval with
  Get-ADUser -Properties tokenGroups. DirectoryEntry does not
  understand URI percent-encoding, so the v2.4.1 EscapeDataString
  fix caused 'invalid dn syntax' errors.
- Removed EscapeDataString from the ACL DirectoryEntry path as
  well; DirectoryEntry expects raw LDAP ADSI path syntax.

All versions bumped to unified v2.4.3.
v2.4.3
2026-06-09 14:14:45 +02:00
tomas.kracmar 03aa72f999 Release v2.4.2: replace em-dashes with ASCII hyphens to fix encoding parse errors
UTF-8 em-dashes (U+2014) in Elysium.Common.ps1 string literals were
being misinterpreted by Windows PowerShell as containing quote
characters when the file was read without a UTF-8 BOM. This caused
cascading parse errors: unexpected tokens, missing closing braces,
and missing catch blocks.

All em-dashes in .ps1 files have been replaced with ASCII hyphens.
All versions bumped to unified v2.4.2.
v2.4.2
2026-06-09 13:51:13 +02:00
tomas.kracmar 10cbf0285d Release v2.4.1: URI-escape DNs in DirectoryEntry LDAP URLs
Test-ReplicationPermissions and Test-DCClockSkew now escape
Distinguished Names via [System.Uri]::EscapeDataString before
constructing DirectoryEntry LDAP URLs. This prevents URL
mis-parsing when DNs contain /, #, or other reserved characters.

All versions bumped to unified v2.4.1.
v2.4.1
2026-06-09 13:42:34 +02:00
tomas.kracmar fc91f0d6b0 Release v2.4.0: DC clock skew check, SDProp/Protected Users warnings, and DSInternals install fix
Added pre-flight diagnostics:
- Test-DCClockSkew: validates local/DC clock skew before DCSync to
  catch Kerberos auth failures early.
- Test-ReplicationPermissions now warns on adminCount=1 (SDProp
  protected) and Protected Users group membership (RID 525), both
  of which can silently block or revert replication rights.

Fixed DSInternals update flow:
- Replaced Update-Module with Install-Module -Force -AllowClobber
  to work around a PowerShellGet null PublishedDate bug.

All versions bumped to unified v2.4.0.
v2.4.0
2026-06-09 13:32:21 +02:00
tomas.kracmar 6b2ae6c8b5 Release v2.3.0: add DSInternals version check and auto-update
Test-WeakADPasswords.ps1 now validates the installed DSInternals
version at startup:
- v6.2 (unsigned) warns that native DLLs are blocked and replication
  will fail; directs operator to Update-Module DSInternals.
- Below v7.0 prompts to auto-update via Update-Module -Force and
  exits cleanly so the new version is loaded on re-run.
- v7.0+ passes silently.

All versions bumped to unified v2.3.0.
v2.3.0
2026-06-09 13:16:47 +02:00
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.
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.
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.
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.
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
tomas.kracmar 09c30f97e9 Release v2.2.1: DRY refactoring and housekeeping
Consolidated duplicated helpers into Elysium.Common.ps1:
- Settings parsing (Read-KeyValueSettingsFile, Read-ElysiumSettings, Get-SettingsValue)
- Azure Blob URI builder (Build-BlobUri)
- S3 SigV4 signing helpers and AWS module bootstrap
- AD credential validation and replication permission pre-check
- Parallel execution helper (Get-FunctionDefinitionText)

Test-WeakADPasswords.ps1 and Extract-NTHashes.ps1 now import
Elysium.Common.ps1 for the first time. Update-KHDB.ps1 and
Prepare-KHDBStorage.ps1 removed their local duplicates.

Deleted legacy Settings.ps1 (superseded by ElysiumSettings.txt).
Removed stray placeholder comment in Elysium.ps1.

All versions bumped to unified v2.2.1.
v2.2.1
2026-06-09 10:52:19 +02:00
tomas.kracmar 5127c2d096 fix(Test-WeakADPasswords): surface replication permission success to console
Replace Write-Verbose with Write-Host so operators see the
permissions verification result without needing -Verbose.
2026-06-09 09:56:24 +02:00
tomas.kracmar ad1db86232 Release v2.2.0: unified versioning and replication permission pre-check
- Unified project versioning (v2.2.0) across all scripts, settings template,
  and documentation. All components now share a single version number.
- Added Test-ReplicationPermissions to Test-WeakADPasswords.ps1 to validate
  the three required AD replication extended rights before DCSync, providing
  clear fail-fast errors when permissions are missing.
- Updated CHANGELOG.md with unified versioning strategy and release history.
- Updated README.md with versioning section and improved troubleshooting docs.
v2.2.0
2026-06-09 09:43:43 +02:00
tomas.kracmar 60a7671ceb Fix KHDB password match format handling 2026-03-16 16:38:19 +01:00
Tom Frost 787360c706 Fail fast when FIPS policy is enabled for weak password test 2026-02-17 13:28:39 +01:00
Tom Frost baaee8dc53 Handle DSInternals FIPS bootstrap WriteErrorException 2026-02-17 13:15:14 +01:00
Tom Frost b582bb24b3 Bump Test-WeakADPasswords to v1.4.1 2026-02-17 13:03:34 +01:00
Tom Frost 7f1df7b102 Improve weak password test credential diagnostics and docs 2026-02-17 12:40:04 +01:00
tomas.kracmar 7874c0e65b Clarified privileges needed 2025-12-08 16:24:26 +01:00
tomas.kracmar be96cbf9a5 Bumped versions 2025-11-07 20:56:02 +01:00
tomas.kracmar 42fee2ff84 PS fix 2025-11-07 20:48:52 +01:00
tomas.kracmar 2ff4964537 PS7 fixes 2025-11-07 20:45:53 +01:00
tomas.kracmar 6e4cc874b0 PS 7 bug fixes 2025-11-07 20:42:58 +01:00
tomas.kracmar ec27206453 PS7 fixes 2025-11-07 20:38:26 +01:00
tomas.kracmar a55ef3713f Bug fixes 2025-11-07 20:33:21 +01:00
tomas.kracmar bda19432e2 Update to prefer PS7 if available 2025-11-07 20:21:03 +01:00
tomas.kracmar 7c2bb65a86 Parallel transfers 2025-11-07 18:14:43 +01:00
tomas.kracmar 5a64558bb9 Fix PS 5.1 -Depth handling 2025-11-07 16:27:00 +01:00
tomas.kracmar 4b1b841383 KHDB rework 2025-11-07 15:58:35 +01:00
tomas.kracmar 964e91d20f Fixing handling errors in datasets 2025-10-21 15:34:44 +02:00
tomas.kracmar 353352eeb2 Improve UPN export handling 2025-10-21 14:27:16 +02:00
tomas.kracmar 05e9358357 Versions bump 2025-10-21 13:42:46 +02:00