Update to prefer PS7 if available
This commit is contained in:
35
Elysium.Common.ps1
Normal file
35
Elysium.Common.ps1
Normal file
@@ -0,0 +1,35 @@
|
||||
function Restart-WithPwshIfAvailable {
|
||||
param(
|
||||
[hashtable]$BoundParameters,
|
||||
[object[]]$UnboundArguments
|
||||
)
|
||||
|
||||
if ($PSVersionTable.PSVersion.Major -ge 7) { return }
|
||||
$pwsh = Get-Command -Name 'pwsh' -ErrorAction SilentlyContinue
|
||||
if (-not $pwsh) { return }
|
||||
if (-not $PSCommandPath) { return }
|
||||
|
||||
Write-Host ("PowerShell 7 detected at '{0}'; relaunching script under pwsh..." -f $pwsh.Path)
|
||||
|
||||
$argList = @('-NoLogo', '-NoProfile', '-File', $PSCommandPath)
|
||||
|
||||
if ($BoundParameters) {
|
||||
foreach ($entry in $BoundParameters.GetEnumerator()) {
|
||||
$key = "-$($entry.Key)"
|
||||
$value = $entry.Value
|
||||
if ($value -is [System.Management.Automation.SwitchParameter]) {
|
||||
if ($value.IsPresent) { $argList += $key }
|
||||
} else {
|
||||
$argList += $key
|
||||
$argList += $value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($UnboundArguments) {
|
||||
$argList += $UnboundArguments
|
||||
}
|
||||
|
||||
& $pwsh.Path @argList
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
18
Elysium.ps1
18
Elysium.ps1
@@ -21,12 +21,17 @@ Elysium.ps1 offers a menu to perform various actions:
|
||||
2. Test Weak AD Passwords
|
||||
3. Extract and Send Current Hashes for KHDB Update
|
||||
4. Uninstall the tool
|
||||
5. Exit
|
||||
5. Update Lithnet Password Protection store
|
||||
6. Exit
|
||||
#>
|
||||
|
||||
# Safer defaults
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
[string]$commonHelper = Join-Path -Path $PSScriptRoot -ChildPath 'Elysium.Common.ps1'
|
||||
if (-not (Test-Path -LiteralPath $commonHelper)) { throw "Common helper not found at $commonHelper" }
|
||||
. $commonHelper
|
||||
Restart-WithPwshIfAvailable -BoundParameters $PSBoundParameters -UnboundArguments $MyInvocation.UnboundArguments
|
||||
|
||||
# Define the path to the settings file
|
||||
$settingsFilePath = Join-Path -Path $PSScriptRoot -ChildPath "ElysiumSettings.txt"
|
||||
@@ -79,7 +84,8 @@ function Show-Menu {
|
||||
Write-Host "2: Test Weak AD Passwords"
|
||||
Write-Host "3: Extract and Send Current Hashes for KHDB Update"
|
||||
Write-Host "4: Uninstall"
|
||||
Write-Host "5: Exit"
|
||||
Write-Host "5: Update Lithnet Password Protection Store"
|
||||
Write-Host "6: Exit"
|
||||
}
|
||||
|
||||
Start-OrchestratorTranscript -BasePath $PSScriptRoot
|
||||
@@ -105,16 +111,20 @@ do {
|
||||
& (Join-Path -Path $PSScriptRoot -ChildPath 'Uninstall.ps1')
|
||||
}
|
||||
'5' {
|
||||
Write-Host "Updating Lithnet Password Protection store..."
|
||||
& (Join-Path -Path $PSScriptRoot -ChildPath 'Update-LithnetStore.ps1')
|
||||
}
|
||||
'6' {
|
||||
Write-Host "Exiting..."
|
||||
# end loop; transcript will be stopped after the loop
|
||||
$userSelection = '5'
|
||||
$userSelection = '6'
|
||||
}
|
||||
default {
|
||||
Write-Host "Invalid selection, please try again."
|
||||
}
|
||||
}
|
||||
pause
|
||||
} while ($userSelection -ne '5')
|
||||
} while ($userSelection -ne '6')
|
||||
} finally {
|
||||
Stop-OrchestratorTranscript
|
||||
}
|
||||
|
||||
@@ -51,6 +51,14 @@ ReportPathBase=Reports
|
||||
WeakPasswordsDatabase=khdb.txt
|
||||
# CheckOnlyEnabledUsers=true
|
||||
|
||||
# Lithnet Password Protection Settings
|
||||
######################################
|
||||
LithnetStorePath=
|
||||
LithnetSyncHibp=false
|
||||
LithnetHashSources=khdb.txt
|
||||
LithnetPlaintextSources=
|
||||
LithnetBannedWordSources=
|
||||
|
||||
# Telemetry (optional)
|
||||
######################
|
||||
# These values are empty by default so no telemetry is sent.
|
||||
|
||||
@@ -22,6 +22,11 @@ This script will connect to selected domain (defined in ElysiumSettings.txt) usi
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
[string]$commonHelper = Join-Path -Path $PSScriptRoot -ChildPath 'Elysium.Common.ps1'
|
||||
if (-not (Test-Path -LiteralPath $commonHelper)) { throw "Common helper not found at $commonHelper" }
|
||||
. $commonHelper
|
||||
Restart-WithPwshIfAvailable -BoundParameters $PSBoundParameters -UnboundArguments $MyInvocation.UnboundArguments
|
||||
|
||||
$scriptRoot = $PSScriptRoot
|
||||
|
||||
function Start-ExtractTranscript {
|
||||
|
||||
@@ -75,6 +75,11 @@ param(
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
[string]$commonHelper = Join-Path -Path $PSScriptRoot -ChildPath 'Elysium.Common.ps1'
|
||||
if (-not (Test-Path -LiteralPath $commonHelper)) { throw "Common helper not found at $commonHelper" }
|
||||
. $commonHelper
|
||||
Restart-WithPwshIfAvailable -BoundParameters $PSBoundParameters -UnboundArguments $MyInvocation.UnboundArguments
|
||||
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
|
||||
Add-Type -AssemblyName System.Net.Http -ErrorAction SilentlyContinue
|
||||
|
||||
@@ -20,6 +20,7 @@ Sensitive operations are confined only to the dedicated host. In the third step,
|
||||
This tool is provided in private git repository. Installation and updating is done with cloning and pulling from this repository.
|
||||
During first run, the tool will ask for passphrase that will be used to encrypt/decrypt sensitive content.
|
||||
After installation, edit ElysiumSettings.txt, check all variables and add domains to test.
|
||||
All scripts automatically relaunch under PowerShell 7 (`pwsh`) when it is installed so that features like parallel transfers are available; if pwsh is missing they continue under Windows PowerShell 5.1 with the legacy single-threaded behavior.
|
||||
### Update Known-Hashed Database (KHDB)
|
||||
Run script Elysium.ps1 as an administrator and choose option 1 (Update Known-Hashes Database).
|
||||
The updater now pulls a manifest plus individual hash shards (two-hex prefix layout) from the configured storage (Azure Blob or S3-compatible), verifies checksums, replaces only changed shards, and rebuilds `khdb.txt` for local use. Deleted shards listed in the manifest are removed automatically. When PowerShell 7 is available the downloader automatically fetches up to `-MaxParallelTransfers` shards in parallel (default `5`); on Windows PowerShell 5.1 it reverts to the original sequential behavior. Override the concurrency as needed when running the script directly (for example `.\Update-KHDB.ps1 -MaxParallelTransfers 8`).
|
||||
@@ -56,6 +57,10 @@ If you want to know the script was executed without collecting telemetry, set a
|
||||
Run script Elysium.ps1 as an administrator and choose option 3 (Extract and Send Hashes).
|
||||
Domains are listed in configuration order, after which the script prompts for the replication-capable account password. With valid credentials, it extracts current NTLM hashes (no history) for active accounts, compresses the results, encrypts them with the configured passphrase, and uploads the payload to the configured storage (Azure Blob or S3-compatible). A checksum-verified round-trip download confirms the upload before local artifacts are removed.
|
||||
|
||||
### Update Lithnet Password Protection store
|
||||
Run script Elysium.ps1 as an administrator and choose option 5 (Update Lithnet Password Protection Store).
|
||||
Configure the target folder via `LithnetStorePath` in `ElysiumSettings.txt` (the location created with `Open-Store`). The script automatically imports the `khdb.txt` file unless you override/add additional NTLM hash lists in `LithnetHashSources` (comma or semicolon separated). You can also populate plaintext password lists (`LithnetPlaintextSources`) and banned-word files (`LithnetBannedWordSources`), or enable `LithnetSyncHibp=true` to seed the store directly from the Have I Been Pwned API (using `Sync-HashesFromHibp`). Behind the scenes the helper loads the `LithnetPasswordProtection` module, opens the store, runs [`Import-CompromisedPasswordHashes`](https://docs.lithnet.io/password-protection/advanced-help/powershell-reference/import-compromisedpasswordhashes)/`Import-CompromisedPasswords`/`Import-BannedWords` for each configured file, and then closes the store.
|
||||
|
||||
S3-compatible usage notes:
|
||||
- No AWS Tools required. The scripts sign requests using native SigV4 via .NET and HttpClient, including non-default endpoint ports.
|
||||
- To force using AWS Tools instead, set `s3UseAwsTools = true` in `ElysiumSettings.txt` and install `AWS.Tools.S3`.
|
||||
|
||||
@@ -24,6 +24,11 @@ This script will test the passwords of selected domain (defined in ElysiumSettin
|
||||
# Enable verbose output
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
[string]$commonHelper = Join-Path -Path $PSScriptRoot -ChildPath 'Elysium.Common.ps1'
|
||||
if (-not (Test-Path -LiteralPath $commonHelper)) { throw "Common helper not found at $commonHelper" }
|
||||
. $commonHelper
|
||||
Restart-WithPwshIfAvailable -BoundParameters $PSBoundParameters -UnboundArguments $MyInvocation.UnboundArguments
|
||||
|
||||
$VerbosePreference = "SilentlyContinue"
|
||||
|
||||
$scriptRoot = $PSScriptRoot
|
||||
|
||||
@@ -19,6 +19,13 @@ Uninstall script for the Elysium AD password testing tool.
|
||||
This script will remove the Elysium tool and its components (scripts, configurations, and any generated data) from the system, and then delete itself.
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
[string]$commonHelper = Join-Path -Path $PSScriptRoot -ChildPath 'Elysium.Common.ps1'
|
||||
if (-not (Test-Path -LiteralPath $commonHelper)) { throw "Common helper not found at $commonHelper" }
|
||||
. $commonHelper
|
||||
Restart-WithPwshIfAvailable -BoundParameters $PSBoundParameters -UnboundArguments $MyInvocation.UnboundArguments
|
||||
|
||||
function Start-UninstallTranscript {
|
||||
try {
|
||||
$base = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'Elysium', 'logs')
|
||||
|
||||
@@ -24,6 +24,11 @@ Supports Azure Blob Storage (via SAS) and S3-compatible endpoints (SigV4).
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
[string]$commonHelper = Join-Path -Path $PSScriptRoot -ChildPath 'Elysium.Common.ps1'
|
||||
if (-not (Test-Path -LiteralPath $commonHelper)) { throw "Common helper not found at $commonHelper" }
|
||||
. $commonHelper
|
||||
Restart-WithPwshIfAvailable -BoundParameters $PSBoundParameters -UnboundArguments $MyInvocation.UnboundArguments
|
||||
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
|
||||
|
||||
$scriptRoot = $PSScriptRoot
|
||||
|
||||
167
Update-LithnetStore.ps1
Normal file
167
Update-LithnetStore.ps1
Normal file
@@ -0,0 +1,167 @@
|
||||
##################################################
|
||||
## ____ ___ ____ _____ _ _ _____ _____ ##
|
||||
## / ___/ _ \| _ \| ____| | \ | | ____|_ _| ##
|
||||
## | | | | | | |_) | _| | \| | _| | | ##
|
||||
## | |__| |_| | _ <| |___ _| |\ | |___ | | ##
|
||||
## \____\__\_\_| \_\_____(_)_| \_|_____| |_| ##
|
||||
##################################################
|
||||
## Project: Elysium ##
|
||||
## File: Update-LithnetStore.ps1 ##
|
||||
## Version: 1.0.0 ##
|
||||
## Support: support@cqre.net ##
|
||||
##################################################
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Populates a Lithnet Password Protection store with compromised passwords and banned words.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads configuration from ElysiumSettings.txt (or a provided settings file), opens the Lithnet
|
||||
Password Protection store, optionally synchronizes with Have I Been Pwned, imports local NTLM
|
||||
hash lists, plaintext password lists, and banned-word files.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$SettingsPath,
|
||||
[string[]]$HashFiles,
|
||||
[string[]]$PlaintextFiles,
|
||||
[string[]]$BannedWordFiles,
|
||||
[switch]$SkipHibpSync
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
[string]$commonHelper = Join-Path -Path $PSScriptRoot -ChildPath 'Elysium.Common.ps1'
|
||||
if (-not (Test-Path -LiteralPath $commonHelper)) { throw "Common helper not found at $commonHelper" }
|
||||
. $commonHelper
|
||||
Restart-WithPwshIfAvailable -BoundParameters $PSBoundParameters -UnboundArguments $MyInvocation.UnboundArguments
|
||||
|
||||
function Read-KeyValueSettings {
|
||||
param([string]$Path)
|
||||
$result = @{}
|
||||
if (-not (Test-Path -LiteralPath $Path)) { throw "Settings file not found at $Path" }
|
||||
Get-Content -LiteralPath $Path | ForEach-Object {
|
||||
$line = $_
|
||||
if (-not $line) { return }
|
||||
$trimmed = $line.Trim()
|
||||
if (-not $trimmed) { return }
|
||||
if ($trimmed.StartsWith('#')) { return }
|
||||
$kv = $line -split '=', 2
|
||||
if ($kv.Count -ne 2) { return }
|
||||
$key = $kv[0].Trim()
|
||||
$value = $kv[1].Trim()
|
||||
if (-not $key) { return }
|
||||
if ($value.StartsWith("'") -and $value.EndsWith("'") -and $value.Length -ge 2) {
|
||||
$value = $value.Substring(1, $value.Length - 2)
|
||||
}
|
||||
$result[$key] = $value
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-BooleanSetting {
|
||||
param(
|
||||
[string]$Value,
|
||||
[bool]$Default = $false
|
||||
)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return $Default }
|
||||
try { return [System.Convert]::ToBoolean($Value) } catch { return $Default }
|
||||
}
|
||||
|
||||
function Get-ListFromSetting {
|
||||
param([string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return @() }
|
||||
return ($Value -split '[,;]' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
}
|
||||
|
||||
function Resolve-ExistingPath {
|
||||
param([string]$PathValue, [string]$Description)
|
||||
if ([string]::IsNullOrWhiteSpace($PathValue)) { throw "$Description path was not provided." }
|
||||
if (-not (Test-Path -LiteralPath $PathValue)) {
|
||||
throw "$Description not found at '$PathValue'."
|
||||
}
|
||||
return (Resolve-Path -LiteralPath $PathValue).Path
|
||||
}
|
||||
|
||||
if (-not $SettingsPath) {
|
||||
$SettingsPath = Join-Path -Path $PSScriptRoot -ChildPath 'ElysiumSettings.txt'
|
||||
}
|
||||
$settings = Read-KeyValueSettings -Path $SettingsPath
|
||||
|
||||
$storePathSetting = $settings['LithnetStorePath']
|
||||
$storePath = Resolve-ExistingPath -PathValue $storePathSetting -Description 'Lithnet store'
|
||||
|
||||
$settingsHashSources = Get-ListFromSetting -Value $settings['LithnetHashSources']
|
||||
$settingsPlainSources = Get-ListFromSetting -Value $settings['LithnetPlaintextSources']
|
||||
$settingsBannedSources = Get-ListFromSetting -Value $settings['LithnetBannedWordSources']
|
||||
|
||||
$hashSourcePaths = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
foreach ($path in @($HashFiles) + $settingsHashSources) {
|
||||
if ([string]::IsNullOrWhiteSpace($path)) { continue }
|
||||
$resolved = Resolve-ExistingPath -PathValue $path -Description 'Hash list'
|
||||
[void]$hashSourcePaths.Add($resolved)
|
||||
}
|
||||
if ($hashSourcePaths.Count -eq 0) {
|
||||
$defaultKhdb = Join-Path -Path $PSScriptRoot -ChildPath 'khdb.txt'
|
||||
if (Test-Path -LiteralPath $defaultKhdb) {
|
||||
[void]$hashSourcePaths.Add((Resolve-Path -LiteralPath $defaultKhdb).Path)
|
||||
} else {
|
||||
throw 'No hash files were provided via parameters or LithnetHashSources, and khdb.txt was not found.'
|
||||
}
|
||||
}
|
||||
|
||||
$plaintextSourcePaths = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
foreach ($path in @($PlaintextFiles) + $settingsPlainSources) {
|
||||
if ([string]::IsNullOrWhiteSpace($path)) { continue }
|
||||
$resolved = Resolve-ExistingPath -PathValue $path -Description 'Plaintext password list'
|
||||
[void]$plaintextSourcePaths.Add($resolved)
|
||||
}
|
||||
|
||||
$bannedWordSourcePaths = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
foreach ($path in @($BannedWordFiles) + $settingsBannedSources) {
|
||||
if ([string]::IsNullOrWhiteSpace($path)) { continue }
|
||||
$resolved = Resolve-ExistingPath -PathValue $path -Description 'Banned word list'
|
||||
[void]$bannedWordSourcePaths.Add($resolved)
|
||||
}
|
||||
|
||||
$syncHibp = Get-BooleanSetting -Value $settings['LithnetSyncHibp'] -Default:$false
|
||||
if ($SkipHibpSync) { $syncHibp = $false }
|
||||
|
||||
Write-Host "Importing LithnetPasswordProtection module..."
|
||||
try {
|
||||
Import-Module -Name LithnetPasswordProtection -ErrorAction Stop | Out-Null
|
||||
} catch {
|
||||
throw "LithnetPasswordProtection module not found: $($_.Exception.Message). Install it from https://github.com/lithnet/password-protection."
|
||||
}
|
||||
|
||||
Write-Host ("Opening Lithnet store at '{0}'..." -f $storePath)
|
||||
Open-Store -Path $storePath
|
||||
$storeOpened = $true
|
||||
try {
|
||||
if ($syncHibp) {
|
||||
Write-Host 'Synchronizing compromised hashes from Have I Been Pwned (this can take a while)...'
|
||||
Sync-HashesFromHibp
|
||||
}
|
||||
|
||||
foreach ($hashFile in ($hashSourcePaths.ToArray() | Sort-Object)) {
|
||||
Write-Host ("Importing NTLM hash list '{0}'..." -f $hashFile)
|
||||
Import-CompromisedPasswordHashes -Filename $hashFile
|
||||
}
|
||||
|
||||
foreach ($plainFile in ($plaintextSourcePaths.ToArray() | Sort-Object)) {
|
||||
Write-Host ("Importing plaintext password list '{0}'..." -f $plainFile)
|
||||
Import-CompromisedPasswords -Filename $plainFile
|
||||
}
|
||||
|
||||
foreach ($bannedFile in ($bannedWordSourcePaths.ToArray() | Sort-Object)) {
|
||||
Write-Host ("Importing banned word list '{0}'..." -f $bannedFile)
|
||||
Import-BannedWords -Filename $bannedFile
|
||||
}
|
||||
|
||||
Write-Host 'Lithnet store update completed successfully.'
|
||||
} finally {
|
||||
if ($storeOpened) {
|
||||
try { Close-Store } catch {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user