From 81ad45b4fee4613d5b8b9d6c33bde68e255ff886 Mon Sep 17 00:00:00 2001 From: Tomas Kracmar Date: Fri, 15 Mar 2024 09:25:58 +0100 Subject: [PATCH] First commit --- Elysium.ps1 | 51 ++++++++++ ElysiumSettings.txt | 46 +++++++++ README.md | 64 +++++++++++- Send-CurrentHashes.ps1 | 43 ++++++++ Test-WeakADPasswords.ps1 | 207 +++++++++++++++++++++++++++++++++++++++ Uninstall.ps1 | 31 ++++++ Update-KHDB.ps1 | 62 ++++++++++++ khdb.txt | 22 +++++ test-passwords_v0-1.ps1 | 44 +++++++++ 9 files changed, 568 insertions(+), 2 deletions(-) create mode 100644 Elysium.ps1 create mode 100644 ElysiumSettings.txt create mode 100644 Send-CurrentHashes.ps1 create mode 100644 Test-WeakADPasswords.ps1 create mode 100644 Uninstall.ps1 create mode 100644 Update-KHDB.ps1 create mode 100644 khdb.txt create mode 100644 test-passwords_v0-1.ps1 diff --git a/Elysium.ps1 b/Elysium.ps1 new file mode 100644 index 0000000..3b79b24 --- /dev/null +++ b/Elysium.ps1 @@ -0,0 +1,51 @@ +<# +.SYNOPSIS +This is the main script for the Elysium tool for testing weak AD passwords. + +.DESCRIPTION +Elysium.ps1 offers a menu to perform various actions: +1. Update Known-Hashes Database (KHDB) +2. Test Weak AD Passwords +3. Extract and Send Current Hashes for KHDB Update +4. Exit +#> + +function Show-Menu { + param ( + [string]$Title = 'Elysium Tool Main Menu' + ) + Clear-Host + Write-Host "================ $Title ================" + + Write-Host "1: Update Known-Hashes Database (KHDB)" + Write-Host "2: Test Weak AD Passwords" + Write-Host "3: Extract and Send Current Hashes for KHDB Update" + Write-Host "4: Exit" +} + +do { + Show-Menu + $input = Read-Host "Please make a selection" + switch ($input) { + '1' { + Write-Host "Updating KHDB..." + .\Update-KHDB.ps1 + } + '2' { + Write-Host "Testing Weak AD Passwords..." + .\Test-WeakADPasswords.ps1 + } + '3' { + Write-Host "Extracting and Sending Current Hashes..." + .\Send-CurrentHashes.ps1 + } + '4' { + Write-Host "Exiting..." + exit + } + default { + Write-Host "Invalid selection, please try again." + } + } + pause +} while ($input -ne '4') diff --git a/ElysiumSettings.txt b/ElysiumSettings.txt new file mode 100644 index 0000000..4e2e348 --- /dev/null +++ b/ElysiumSettings.txt @@ -0,0 +1,46 @@ +################################################## +## ____ ___ ____ _____ _ _ _____ _____ ## +## / ___/ _ \| _ \| ____| | \ | | ____|_ _| ## +## | | | | | | |_) | _| | \| | _| | | ## +## | |__| |_| | _ <| |___ _| |\ | |___ | | ## +## \____\__\_\_| \_\_____(_)_| \_|_____| |_| ## +## Move fast and fix things. ## +################################################## +## Project: Elysium ## +## File: ElysiumSettings.txt ## +## Version: 1.0 ## +## Support: support@cqre.net ## +################################################## + +# KHDB Settings +############### +KHDBUrl=https://yourserver.com/khdb +SecureToken=YourSecureToken +DataEndpoint=https://yourserver.com/dataendpoint +WeakPasswordsDatabase=khdb.txt + +# Application Settings +###################### +InstallationPath=/Users/avedelphina/Documents/Work/CQRE.NET/Elysium-project/elysium +ReportPathBase=/Users/avedelphina/Documents/Work/CQRE.NET/Elysium-project/elysium/Reports +# TODO CheckOnlyEnabledUsers=true + +# Domain Settings +################# +# Domain 1 (rdm.cz) +Domain1Name=rdm.cz +Domain1DC=xxx.rdm.cz +Domain1DCIP=10.94.x.x +Domain1DA=elysium_adm@rdm.cz + +# Domain 2 (st.sk) +Domain2Name=st.sk +Domain2DC=yyy.st.sk +Domain2DCIP=10.217.x.x +Domain2DA=elysium_adm@st.sk + +# Domain 3 (blackmesaresearch.local) +Domain3Name=blackmesaresearch.local +Domain3DC=dc01-bmr +Domain3DCIP=100.96.247.32 +Domain3DA=BMR\elysium_adm \ No newline at end of file diff --git a/README.md b/README.md index 87cd752..2dbcdc9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,63 @@ -# elysium +# Elysium +## Summary +This tool is used for regular and ad-hoc checking of weak passwords in Active Directory domain. It is a collection of PowerShell scripts leveraging mainly tools from DSInternals suite. The output of this tool is report of weak passwords in the AD domain that warrant attention from internal security team. -Automated testing of weak AD accounts. \ No newline at end of file +Usage of this tool consists of three steps: +1. Update the known-hashes database (KHDB). +2. Test AD passwords against KHDB and generate weak passwords report. +3. Extract current hashes (without usernames) and securely send them for improving the KHDB. + +Sensitive operations are confined only to the dedicated host. In the third step, only extracted hashes without usernames are tranferred (in compressed and encrypted form). This step is completely optional, but recommended as it enables the tool provider to update the KHDB. + +## Prerequisities +* **Windows Host:** A Windows machine with PowerShell and DSInternals suite installed. +* **Administrative Access:** Local admin privileges on the host for installation and updating. +* **Domain Credentials:** A domain user account with Domain Admin privileges for each tested AD domain. This account should be active only during testing. +* **Network Requirements:** A stable connection to the domain controller in each tested AD domain and internet access (specific hostnames/IP addresses will be provided). +--- +## Operation +### Install and update +This tool is provided in private git repository. Installation and updating is done with cloning and pulling from this repository. +### Update Known-Hashed Database (KHDB) +Run script Elysium.ps1 as an administrator and choose option 1 (Update Known-Hashes Database). +The script will then check online for newer version and if found, downloads it. +As the KHDB content is encrypted, the script will then ask for decryption pasword. +With correct password provided, database is then updated. +### Test Weak AD passwords +Run script Elysium.ps1 as an administrator and choose option 2 (Test Weak AD Passwords). +The script will then ask for the domain to be tested and upon choice will ask for domain administrator password. The DA username is already provided in the script for each domain. +The tool then connects to Domain Controller and tests all enabled users in the domain against KHDB. PDF report with findings is then generated. +### Send current hashes for update KHDB +Run script Elysium.ps1 as an administrator and choose option 3 (Extract and Send Hashes). +The tool will then ask for domain and password of domain administrator. With correct credentials, the tool will then extract current hashes (no history) of non-disabled users, compresses and encrypts them and sends them to the tool provider. +### Uninstallation +Remove the cloned repository. +--- +## FAQ +### What happens to the hashes we uploaded? +These hashes are subjected to cracking. Any cracked hash is then added to KHDB. Hash cracking happens on dedicated air-gapped machine and all sensitive material is never decrypted outside this machine. Secure exchange of decryption keys is arranged beforehand with every client. +### Do we need to upload the hashes? +Not at all. This step is purely optional, but it enables us to constantly improve the KHDB. +### What does "weak password" mean? +Account is flagged when it returns one or more of these conditions: +* Password hash is found in KHDB (that means it is known). +* Password is stored using reversible encryption. +* LM hashes are present. +* Has no password set. +* Has the same password as multiple other accounts. +* Has the SamAccountName as password. +* Is computer account with default password. +* Has Kerberos AES keys missing. +* Has not required Kerberos pre-authentication +* Only DES encryption is allowed to be used. +* Is susceptible to the Kerberoasting attack. +* Administrative accounts is allowed to be delegated to a service. +* Passwords of the account will never expire. +* Is not required to have a password. +* Requires smart card authentication and has a password. +### How are usernames paired with KHDB? +They are paired online while running the script. KHDB does not contain usernames as the extract script provides only hashes, not usernames. +### Would our EDR solution interfere with this tool? +It should! If you have EDR installed on the host machine, this tool should be exceptioned. +### Would our monitoring tool detect this activity? +It should, as it is extremely sensitive operation that should never happen outside of this (or similar) procedure. Running this tool should be cleared with your SOC beforehand (or used as a test case). \ No newline at end of file diff --git a/Send-CurrentHashes.ps1 b/Send-CurrentHashes.ps1 new file mode 100644 index 0000000..85c9170 --- /dev/null +++ b/Send-CurrentHashes.ps1 @@ -0,0 +1,43 @@ +param ( + [string]$DataEndpoint = "https://yourserver.com/dataendpoint", # URL to send data + [string]$SecureToken = "YourSecureToken" # Secure token for sending data +) + +function Send-CurrentHashes { + $domain = Read-Host "Enter the domain" + $domainAdminPassword = Read-Host "Enter the domain administrator password" -AsSecureString + + # Extract hashes + $hashes = Get-CurrentHashes -Domain $domain -Password $domainAdminPassword + + # Compress and encrypt data + $compressedEncryptedData = CompressAndEncrypt-Data -Data $hashes + + # Send data + try { + $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $headers.Add("Authorization", "Bearer $SecureToken") + Invoke-WebRequest -Uri $DataEndpoint -Method 'POST' -Body $compressedEncryptedData -Headers $headers + Write-Host "Data sent successfully." + } catch { + Write-Error "Error sending data: $_" + } +} + +function Get-CurrentHashes { + param ( + [Parameter(Mandatory = $true)][string]$Domain, + [Parameter(Mandatory = $true)][System.Security.SecureString]$Password + ) + # Logic to extract current hashes from AD +} + +function CompressAndEncrypt-Data { + param ( + [Parameter(Mandatory = $true)][string]$Data + ) + # Implement compression and encryption + # Return compressed and encrypted data +} + +Send-CurrentHashes diff --git a/Test-WeakADPasswords.ps1 b/Test-WeakADPasswords.ps1 new file mode 100644 index 0000000..262cef0 --- /dev/null +++ b/Test-WeakADPasswords.ps1 @@ -0,0 +1,207 @@ +################################################## +## ____ ___ ____ _____ _ _ _____ _____ ## +## / ___/ _ \| _ \| ____| | \ | | ____|_ _| ## +## | | | | | | |_) | _| | \| | _| | | ## +## | |__| |_| | _ <| |___ _| |\ | |___ | | ## +## \____\__\_\_| \_\_____(_)_| \_|_____| |_| ## +## Move fast and fix things. ## +################################################## +## Project: Elysium ## +## File: Test-WeakADPasswords.ps1 ## +## Version: 0.1 ## +## Support: support@cqre.net ## +################################################## + +# Import settings +Write-Host "Loading settings..." +$ElysiumSettings = @{} +$settingsPath = "ElysiumSettings.txt" + +if (-not (Test-Path $settingsPath)) { + Write-Error "Settings file not found at $settingsPath" + return +} + +Get-Content $settingsPath | ForEach-Object { + if (-not [string]::IsNullOrWhiteSpace($_) -and -not $_.StartsWith("#")) { + $keyValue = $_ -split '=', 2 + if ($keyValue.Count -eq 2) { + $ElysiumSettings[$keyValue[0]] = $keyValue[1] + } + } +} + +$WeakHashesSortedFilePath = Join-Path -Path $ElysiumSettings["InstallationPath"] -ChildPath $ElysiumSettings["WeakPasswordsDatabase"] +if (-not (Test-Path $WeakHashesSortedFilePath)) { + Write-Error "Weak password hashes file not found at '$WeakHashesSortedFilePath'." + return +} + +# Check if required modules are available and install them if necessary +$requiredModules = @("DSInternals", "ActiveDirectory") +foreach ($module in $requiredModules) { + if (-not (Get-Module -ListAvailable -Name $module)) { + $userConsent = Read-Host "Module '$module' is not installed. Do you want to install it now? (Y/N)" + if ($userConsent -eq 'Y') { + try { + Write-Host "Installing module '$module'..." + Install-Module -Name $module -Force -Scope CurrentUser + Write-Host "Module '$module' installed successfully." + } catch { + Write-Error "Failed to install module '$module'. Error: $_" + return + } + } else { + Write-Host "Skipping module installation. Script may not function correctly without the required modules." + return + } + } +} + +# Import required modules +Import-Module DSInternals +Import-Module ActiveDirectory + +# Get the variables from settings +$WeakHashesSortedFilePath = $ElysiumSettings["WeakPasswordsDatabase"] +# Get the report path from settings +$reportPathBase = $ElysiumSettings["ReportPathBase"] +if ($null -eq $reportPathBase) { + Write-Error "Report path is not defined in the settings." + return +} + +# Check if the report directory exists, create it if it doesn't +if (-not (Test-Path -Path $reportPathBase)) { + Write-Host "Report directory does not exist. Creating directory at $reportPathBase..." + New-Item -Path $reportPathBase -ItemType Directory +} + + +# Function to extract domain details from settings +function Get-DomainDetailsFromSettings { + param ( + [hashtable]$ElysiumSettings + ) + + Write-Host "Extracting domain details from settings..." + $domainDetails = @{} + $domainCounter = 1 + + while ($true) { + $domainNameKey = "Domain${domainCounter}Name" + $domainDCKey = "Domain${domainCounter}DC" + $domainDAKey = "Domain${domainCounter}DA" + + if ($ElysiumSettings.ContainsKey($domainNameKey)) { + $domainDetails["$domainCounter"] = @{ + "Name" = $ElysiumSettings[$domainNameKey] + "DC" = $ElysiumSettings[$domainDCKey] + "DA" = $ElysiumSettings[$domainDAKey] + } + $domainCounter++ + } else { + break + } + } + + return $domainDetails +} + +# Function to test for weak AD passwords +function Test-WeakADPasswords { + param ( + [hashtable]$DomainDetails, + [string]$WeakHashesSortedFilePath + ) + + Write-Host "Starting the test for weak AD passwords..." + + # Display domain options to the user + Write-Host "Select a domain to test:" + $sortedDomains = $DomainDetails.Keys | Sort-Object + foreach ($domain in $sortedDomains) { + $domainName = $DomainDetails[$domain].Name + Write-Host ($domain + ": " + $domainName) + } + + # User selects a domain + $selectedDomainKey = Read-Host "Enter the number of the domain" + $selectedDomain = $DomainDetails[$selectedDomainKey] + + if ($null -eq $selectedDomain) { + Write-Error "Invalid selection. Exiting." + return $null + } + + # Prompt for DA password + $DAUsername = $selectedDomain["DA"] + $DApassword = Read-Host "Enter password for DA account ($DAUsername) of $($selectedDomain.Name)" -AsSecureString + + # Preparing credentials for the domain + $credentials = New-Object System.Management.Automation.PSCredential($selectedDomain.DA, $DApassword) + + Write-Host "Enumerating accounts from the domain controller $($selectedDomain.DC)..." + $accounts = Get-ADReplAccount -All -Server $selectedDomain.DC -Credential $credentials + + Write-Host "Testing password quality..." + $testResults = $accounts | Test-PasswordQuality -WeakPasswordHashesFile $WeakHashesSortedFilePath + + # Debug: Print selected domain details + Write-Host "Selected domain details: Name=$($selectedDomain.Name), DC=$($selectedDomain.DC), DA=$($selectedDomain.DA)" + + # Generate report name and path + $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" + $reportName = $selectedDomain.Name + "_WeakPasswordReport_" + $timestamp + ".txt" + $reportPath = Join-Path -Path $reportPathBase -ChildPath $reportName + + Write-Host "Report will be saved as: $reportName" + + # Return a hashtable with report path and weak accounts + return @{ + "ReportPath" = $reportPath; + "WeakAccounts" = $testResults.WeakPasswords; + } +} + +# Function to export weak password report +function Export-WeakPasswordReport { + param ( + [array]$WeakAccounts, + [string]$ReportPath + ) + + Write-Host "Exporting weak password report to $ReportPath" + + # Handle empty weak accounts array + if ($null -eq $WeakAccounts -or $WeakAccounts.Count -eq 0) { + "No weak passwords found." | Out-File $ReportPath + } else { + $WeakAccounts | Out-File $ReportPath + } + + Write-Host "Weak password report generated at $ReportPath" +} + +# Main script execution +try { + $domainDetails = Get-DomainDetailsFromSettings -ElysiumSettings $ElysiumSettings + $results = Test-WeakADPasswords -DomainDetails $domainDetails -WeakHashesSortedFilePath $WeakHashesSortedFilePath + + # Check if results were returned before proceeding + if ($results) { + # Generate the report + Export-WeakPasswordReport -WeakAccounts $results.WeakAccounts -ReportPath $results.ReportPath + + # Check if there are weak accounts + if ($results.WeakAccounts) { + Write-Host "Weak passwords found. Report generated at $($results.ReportPath)" + } else { + Write-Host "No weak passwords found. Empty report generated at $($results.ReportPath)" + } + } +} catch { + Write-Error "An error occurred: $_" +} + +Write-Host "Script execution completed." diff --git a/Uninstall.ps1 b/Uninstall.ps1 new file mode 100644 index 0000000..3d5c867 --- /dev/null +++ b/Uninstall.ps1 @@ -0,0 +1,31 @@ +<# +.SYNOPSIS +Uninstall script for the Elysium AD password testing tool. + +.DESCRIPTION +This script will remove the Elysium tool and its components (scripts, configurations, and any generated data) from the system. +#> + +# Define the path where the Elysium tool is installed +$ElysiumPath = "C:\Path\To\Elysium" # Update this with the actual installation path + +function Uninstall-Elysium { + Write-Host "Uninstalling Elysium tool..." + + # Check if the Elysium directory exists + if (Test-Path $ElysiumPath) { + # Remove the Elysium directory and all its contents + Remove-Item -Path $ElysiumPath -Recurse -Force + Write-Host "Elysium tool and all related files have been removed." + } else { + Write-Host "Elysium directory not found. It might have been removed already or the path is incorrect." + } + + # Additional cleanup actions can be added here if needed +} + +# Execute the uninstall function +Uninstall-Elysium + +# Confirm uninstallation +Write-Host "Elysium tool has been successfully uninstalled." -ForegroundColor Green diff --git a/Update-KHDB.ps1 b/Update-KHDB.ps1 new file mode 100644 index 0000000..69d10bf --- /dev/null +++ b/Update-KHDB.ps1 @@ -0,0 +1,62 @@ +# Initialize an empty hashtable to store settings +$ElysiumSettings = @{} + +# Read the settings file +$settingsPath = "ElysiumSettings.txt" +Get-Content $settingsPath | ForEach-Object { + $keyValue = $_ -split '=', 2 + $ElysiumSettings[$keyValue[0]] = $keyValue[1] +} + +# Get the variables +$KHDBUrl = $ElysiumSettings["KHDBUrl"] +$SecureToken = $ElysiumSettings["SecureToken"] + +function Update-KHDB { + Write-Host "Checking for KHDB updates..." + + # Setting request headers + $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $headers.Add("Authorization", "Bearer $SecureToken") + + # Downloading KHDB + try { + $latestKHDB = Invoke-WebRequest -Uri $KHDBUrl -Headers $headers -UseBasicParsing + $encryptedKHDB = $latestKHDB.Content + Write-Host "KHDB downloaded successfully." + } catch { + Write-Error "Error downloading KHDB: $_" + return + } + + # Decrypting KHDB + try { + $decryptionPassword = Read-Host "Enter decryption password" -AsSecureString + $decryptedKHDB = Unprotect-KHDB -EncryptedData $encryptedKHDB -Password $decryptionPassword + Write-Host "KHDB decrypted successfully." + } catch { + Write-Error "Error decrypting KHDB: $_" + return + } + + # Updating local KHDB (assuming a specific method to update your local database) + Update-LocalKHDB -Data $decryptedKHDB +} + +function Unprotect-KHDB { + param ( + [Parameter(Mandatory = $true)][string]$EncryptedData, + [Parameter(Mandatory = $true)][System.Security.SecureString]$Password + ) + # Implement your decryption logic here + # Return decrypted data +} + +function Update-LocalKHDB { + param ( + [Parameter(Mandatory = $true)][string]$Data + ) + # Implement your logic to update the local KHDB +} + +Update-KHDB diff --git a/khdb.txt b/khdb.txt new file mode 100644 index 0000000..cecb1a7 --- /dev/null +++ b/khdb.txt @@ -0,0 +1,22 @@ +31d6cfe0d16ae931b73c59d7e0c089c0 +32ed87bdb5fdc5e9cba88547376818d4 +c22b315c040ae6e0efee3518d830362b +2d7f1a5a61d3a96fb5159b5eef17adc6 +f4d1d2336222da447d932ab6eff18e85 +55595eab977acf964520076af21dbe95 +64f12cddaa88057e06a81b54e73b949b +0a4f5fab36608a9b52cd285fa5d0c7ae +259745cb123a52aa2e693aaacca2db52 +04c5e557c1e177c45d5fd6b3a45941ae +5a5f0696b68373e10ba596ecd067c26d +a322fe6d656e60ea713bcd08d46a7a9b +1ce693d25c026d4b731ac2419b4ed824 +44d6e3d08e62050db13e341398ad96d9 +b375a33f2fef10e89a266977baafbf47 +7aeb99f81f02ad9a2ffc2b2e1d81a693 +2d37c534d80999fcd8a79bcb5a1be9e2 +34b247c091d9b3f8b727defbb5d9fd6b +a87f3a337d73085c45f9416be5787d86 +2084dd34cb5fc9d7e54dc2567d979b43 +134080dfc92d58efd7ae95fc5803825b +182136af72b77a43b32848d8d4bff347 \ No newline at end of file diff --git a/test-passwords_v0-1.ps1 b/test-passwords_v0-1.ps1 new file mode 100644 index 0000000..3014e6d --- /dev/null +++ b/test-passwords_v0-1.ps1 @@ -0,0 +1,44 @@ +# Import settings +Write-Host "Loading settings..." +$ElysiumSettings = @{} +$settingsPath = "ElysiumSettings.txt" + +Get-Content $settingsPath | ForEach-Object { + if (-not [string]::IsNullOrWhiteSpace($_) -and -not $_.StartsWith("#")) { + $keyValue = $_ -split '=', 2 + if ($keyValue.Count -eq 2) { + $ElysiumSettings[$keyValue[0]] = $keyValue[1] + } + } +} + +$WeakHashesSortedFilePath = Join-Path -Path $ElysiumSettings["InstallationPath"] -ChildPath $ElysiumSettings["WeakPasswordsDatabase"] +if (-not (Test-Path $WeakHashesSortedFilePath)) { + Write-Error "Weak password hashes file not found at '$WeakHashesSortedFilePath'." + return +} + +# Import required modules +Import-Module DSInternals +Import-Module ActiveDirectory + +# Get the variables from settings +$WeakHashesSortedFilePath = $ElysiumSettings["WeakPasswordsDatabase"] + +# Function to test for weak AD passwords +function Test-WeakADPasswords { + param ( + [string]$WeakHashesSortedFilePath + ) + + Write-Host "Starting the test for weak AD passwords..." + + Write-Host "Enumerating accounts from the domain controller..." + $accounts = Get-ADReplAccount -All -Server dc01-bmr -Credential (Get-Credential) + + Write-Host "Testing password quality..." + $accounts | Test-PasswordQuality -WeakPasswordHashesFile $WeakHashesSortedFilePath + + # Debug: Print results + $accounts | Format-Table +}