diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..398c21b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## Extract-NTHashes.ps1 + +### version 1.1.0 +**Added:** +- UPN retrieval (this will prolong the time needed to run the script significantly) +- Better error handling diff --git a/ElysiumSettings.txt.sample b/ElysiumSettings.txt.sample index 68d6fce..43aacc5 100644 --- a/ElysiumSettings.txt.sample +++ b/ElysiumSettings.txt.sample @@ -14,9 +14,9 @@ # KHDB Settings ############### -storageAccountName = '' -containerName = '' -sasToken = '?' +storageAccountName = +containerName = +sasToken = # Application Settings ###################### diff --git a/Test-WeakADPasswords.ps1 b/Test-WeakADPasswords.ps1 index ebab0a8..f461f71 100644 --- a/Test-WeakADPasswords.ps1 +++ b/Test-WeakADPasswords.ps1 @@ -8,7 +8,7 @@ ################################################## ## Project: Elysium ## ## File: Test-WeakADPasswords.ps1 ## -## Version: 1.0.1 ## +## Version: 1.1.0 ## ## Support: support@cqre.net ## ################################################## @@ -17,9 +17,12 @@ Weak AD password finder component of Elysium tool. .DESCRIPTION -This script will test the passwords of selected domain (defined in ElysiumSettings.txt) using DSInternals' Test-PasswordQuality cmdlet. It writes its output to a report file which is meant to be shared with the internal security team. +This script will test the passwords of selected domain (defined in ElysiumSettings.txt) using DSInternals' Test-PasswordQuality cmdlet. It writes its output to a report file which is meant to be shared with the internal security team. The report now includes UPNs for each account mentioned. #> +# Enable verbose output +$VerbosePreference = "Continue" + # Current timestamp for both report generation and header $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" @@ -33,7 +36,7 @@ Report Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") $footer = "`r`n==== End of Report ====" # Import settings -Write-Host "Loading settings..." +Write-Verbose "Loading settings..." $ElysiumSettings = @{} $settingsPath = "ElysiumSettings.txt" @@ -44,13 +47,19 @@ if (-not (Test-Path $settingsPath)) { } # Load settings from file -Get-Content $settingsPath | ForEach-Object { - if (-not [string]::IsNullOrWhiteSpace($_) -and -not $_.StartsWith("#")) { - $keyValue = $_ -split '=', 2 - if ($keyValue.Count -eq 2) { - $ElysiumSettings[$keyValue[0].Trim()] = $keyValue[1].Trim() +try { + Get-Content $settingsPath | ForEach-Object { + if (-not [string]::IsNullOrWhiteSpace($_) -and -not $_.StartsWith("#")) { + $keyValue = $_ -split '=', 2 + if ($keyValue.Count -eq 2) { + $ElysiumSettings[$keyValue[0].Trim()] = $keyValue[1].Trim() + } } } + Write-Verbose "Settings loaded successfully." +} catch { + Write-Error ("An error occurred while loading settings: {0}" -f $_.Exception.Message) + exit } # Define the function to extract domain details from settings @@ -80,6 +89,7 @@ function Get-DomainDetailsFromSettings { # Continue with script logic... $domainDetails = Get-DomainDetailsFromSettings -Settings $ElysiumSettings +Write-Verbose ("Domain details extracted: {0}" -f ($domainDetails | ConvertTo-Json)) # Required modules $requiredModules = @("DSInternals", "ActiveDirectory") @@ -87,10 +97,23 @@ $requiredModules = @("DSInternals", "ActiveDirectory") # Check each required module and import foreach ($module in $requiredModules) { if (-not (Get-Module -ListAvailable -Name $module)) { - Write-Error "Required module '$module' is not installed. Please install it to proceed." - exit + Write-Verbose "Required module '$module' is not installed." + $response = Read-Host "Would you like to install it? (Y/N)" + if ($response -eq 'Y') { + try { + Install-Module -Name $module -Force -ErrorAction Stop + Write-Verbose "Module '$module' installed successfully." + } catch { + Write-Error ("Failed to install module '{0}': {1}" -f $module, $_.Exception.Message) + exit + } + } else { + Write-Error "Required module '$module' is not installed. Please install it to proceed." + exit + } } Import-Module $module + Write-Verbose "Module '$module' imported." } # Verify the existence of the Weak Password Hashes file @@ -99,11 +122,37 @@ if (-not (Test-Path $WeakHashesSortedFilePath)) { Write-Error "Weak password hashes file not found at '$WeakHashesSortedFilePath'." exit } +Write-Verbose "Weak password hashes file found at '$WeakHashesSortedFilePath'." # Ensure the report directory exists $reportPathBase = $ElysiumSettings["ReportPathBase"] if (-not (Test-Path -Path $reportPathBase)) { - New-Item -Path $reportPathBase -ItemType Directory + try { + New-Item -Path $reportPathBase -ItemType Directory -ErrorAction Stop + Write-Verbose "Report directory created at '$reportPathBase'." + } catch { + Write-Error ("Failed to create report directory: {0}" -f $_.Exception.Message) + exit + } +} + +# Function to get UPN for a given SAM account name +function Get-UserUPN { + param ( + [string]$SamAccountName, + [string]$Domain, + [System.Management.Automation.PSCredential]$Credential + ) + + Write-Verbose "Attempting to get UPN for $SamAccountName in domain $Domain" + try { + $user = Get-ADUser -Identity $SamAccountName -Properties UserPrincipalName -Server $Domain -Credential $Credential + Write-Verbose "UPN found: $($user.UserPrincipalName)" + return $user.UserPrincipalName + } catch { + Write-Verbose ("Failed to get UPN for {0}: {1}" -f $SamAccountName, $_.Exception.Message) + return "UPN not found" + } } # Function to test for weak AD passwords @@ -117,29 +166,64 @@ function Test-WeakADPasswords { Write-Host "Select a domain to test:" $DomainDetails.GetEnumerator() | ForEach-Object { Write-Host "$($_.Key): $($_.Value.Name)" } $selection = Read-Host "Enter the number of the domain" - $selectedDomain = $DomainDetails[$selection] - - if (-not $selectedDomain) { + + if (-not ($DomainDetails.ContainsKey($selection))) { Write-Error "Invalid selection." return } + + $selectedDomain = $DomainDetails[$selection] + Write-Verbose "Selected domain: $($selectedDomain.Name)" # Prompt for DA credentials $credential = Get-Credential -Message "Enter AD credentials with replication rights for $($selectedDomain.Name)" # Performing the test - Write-Host "Testing password quality for $($selectedDomain.Name)..." - $testResults = Get-ADReplAccount -All -Server $selectedDomain["DC"] -Credential $credential | - Test-PasswordQuality -WeakPasswordHashesFile $FilePath + Write-Verbose "Testing password quality for $($selectedDomain.Name)..." + try { + $testResults = Get-ADReplAccount -All -Server $selectedDomain["DC"] -Credential $credential | + Test-PasswordQuality -WeakPasswordHashesFile $FilePath + Write-Verbose "Password quality test completed." + } catch { + Write-Error ("An error occurred while testing passwords: {0}" -f $_.Exception.Message) + return + } - # Report generation with dynamic content + # Report generation with dynamic content and UPNs $reportPath = Join-Path -Path $reportPathBase -ChildPath "$($selectedDomain.Name)_WeakPasswordReport_$timestamp.txt" + Write-Verbose "Generating report at $reportPath" $reportContent = @($header, ($testResults | Out-String).Trim(), $footer) -join "`r`n" - $reportContent | Out-File -FilePath $reportPath - Write-Host "Report saved to $reportPath" + + $lines = $reportContent -split "`r`n" + $newReportContent = @() + + foreach ($line in $lines) { + $newReportContent += $line + + if ($line -match "$($selectedDomain.Name)\\(.+)") { + $samAccountName = $matches[1] + Write-Verbose "Looking up UPN for $samAccountName" + $upn = Get-UserUPN -SamAccountName $samAccountName -Domain $selectedDomain.DC -Credential $credential + $newReportContent += " UPN: $upn" + } + } + + $updatedReportContent = $newReportContent -join "`r`n" + + try { + $updatedReportContent | Out-File -FilePath $reportPath -ErrorAction Stop + Write-Host "Report saved to $reportPath" + } catch { + Write-Error ("Failed to save report: {0}" -f $_.Exception.Message) + } } # Main script logic -Test-WeakADPasswords -DomainDetails $domainDetails -FilePath $WeakHashesSortedFilePath +try { + Write-Verbose "Starting main script execution..." + Test-WeakADPasswords -DomainDetails $domainDetails -FilePath $WeakHashesSortedFilePath +} catch { + Write-Error ("An error occurred during script execution: {0}" -f $_.Exception.Message) +} Write-Host "Script execution completed."