If you haven’t already seen it, it’s now been widely documented that the feature within GP Preferences Group Policy that allows you to store passwords within a GPO for a variety of uses, is essentially not secure. If you haven’t read one of these posts, please do and familiarize yourself with the issue:
http://blogs.technet.com/b/grouppolicy/archive/2008/08/04/passwords-in-group-policy-preferences.aspx
http://www.grouppolicy.biz/2013/11/why-passwords-in-group-policy-preference-are-very-bad/
and most importantly, this document:
http://msdn.microsoft.com/en-us/library/cc232587.aspx
Which contains a PDF that actually documents the key (32-byte AES Encryption Key) used to encrypt the password within the SYSVOL portion of a GPO that specifies the password. Specifically, the following areas within GP Preferences allow you to store passwords for a variety of tasks:
- Data Sources
- Local Users
- Scheduled Tasks (both XP and New Style)
- Services
- Drive Maps
If you use any of these extensions, you might be exposing yourself to issues by allowing users with access to the GPOs that implement these settings, a possible avenue to decode passwords for what could be privileged accounts. If you do a web search on the phrase “Group Policy Preferences password exploit”, you will find all sorts of utilities that take advantage of the documented, static encryption key to decode password stored within GP Preferences settings. Great! Thanks! 🙂
So, now that we know that this previously awesome capability for managing passwords across your domain is now known to be pretty insecure, what do we do about it? Well, the first challenge is knowing how big the problem is. Essentially any GP Preferences settings in the 5 areas above could be subject to exploit if they are storing passwords using this weak encryption scheme.
So, what I’ve done is create a PowerShell function called Get-GPPCPassword, that will run through your entire domain’s GPOs, and locate where this password might be found across the 5 policy areas above. The output is a collection of PSObjects with 3 properties- the GPO Name, GPO Side (i.e. computer or user) and the policy area (of the 5 above) that has implemented a password. You’ll notice in the script below, that I’m leveraging the Microsoft Group Policy PowerShell module, and specifically the Get-GPOReport cmdlet, which we use with the -All parameter to get a collection of XML Settings reports for all GPOs in a given domain. Once we have the XML reports, we iterate through each report, using the PowerShell XML type accelerator, to find out whether the CPassword attribute exists within any of the 5 Policy Areas above. The script took about a minute and a half to run through about 300 GPOs in our test environment, but your mileage may vary since many of our GPOs are empty. The script is not super-optimized so you might find some logic improvements you can make in it when you play with it. The goal here is to provide a quick and dirty of finding out how bad things are, when it comes to using this particular feature within GP Preferences. Remediation of the problem is actually a pretty easy extension to this script, once you find the instances of it. I will leave that for another day!
Below you’ll find the function listing, as well as a link to the .ps1 script file itself, that you can download. The function takes a single optional parameter -DomainName, which can include a DNS Domain name of domain other than the default one you are running the script from. The best way to run this is to “dot-source” the script file by typing
. .\Get-GPPCPassword.ps1
From your PowerShell Console. Then you can simply call Get-GPPCPasssword or Get-GPPCPassword -DomainName myotherdomain.com from the prompt.
The output of the script, as I mentioned, is a collection of objects where the password preferences exist, as shown in this screenshot:
So, here’s the download link for the script file: Get-GPPCPassword.PS1
And below is the script. Enjoy!
Darren
[codebox lang=”ps”]
#This script provided as-is with no warranty, by SDM Software, Inc. Copyright 2014. www.sdmsoftware.com
#for questions, contact info@sdmsoftware.com
#January, 2014
function Get-GPPCPassword {
param(
$DomainName #provide optional DNS domain name where you want to run report
)
Import-Module “GroupPolicy”
#create hashtables of extension names that contain CPassword extension property names and friendly Name
$computerExtensions = @{“LocalUsersAndGroups.User” = “Local Users and Groups”;
“DataSourcesSettings.Datasource” = “Data Sources”;
“NTServices.NTService” = “Services”;
“ScheduledTasks.Task” = “Scheduled Tasks”;
}
$userExtensions = @{“LocalUsersAndGroups.User” = “Local Users and Groups”;
“DataSourcesSettings.Datasource” = “Data Sources”;
“NTServices.NTService” = “Services”;
“ScheduledTasks.Task” = “Scheduled Tasks”;
“DriveMapSettings.Drive” = “Drive Maps”
}
$scheduledTaskTypes = @{“ScheduledTasks.Task” = “Scheduled Tasks”;
“ScheduledTasks.TaskV2” = “Scheduled Tasks (Vista and above)”;
“ScheduledTasks.ImmediateTask” = “Immediate Task (Windows XP)”;
“ScheduledTasks.ImmediateTaskV2” = “Immediate Task (Vista and above)”
}
# first, get GPO settings reports of all the GPOs in the selected domain
if ($DomainName -ne $null)
{
$GPOReports = Get-GPOReport -All -ReportType Xml -Domain $DomainName
}
else # run against current domain
{
$GPOReports = Get-GPOReport -All -ReportType Xml
}
# now iterate through all reports (i.e. GPOs) to find CPassword instances
for ($index = 0; $index -lt $GPOReports.Count; $index++)
{
[xml]$report = $GPOReports[$index]
#check computer extensions first
foreach ($extension in $report.GPO.Computer.ExtensionData)
{
foreach ($cExt in $computerExtensions.Keys)
{
if ($extension.Name -eq $computerExtensions[$cExt])
{
#create the standard command we’ll invoke for all extensions
$command = “`$report.GPO.Computer.ExtensionData.Extension.$cExt.Properties.cPassword”
#need to handle the special case for Scheduled where there could be multiple types
if ($extension.Name -eq “Scheduled Tasks”)
{
foreach ($schedTaskItem in $scheduledTaskTypes.Keys)
{
$command = “`$report.GPO.Computer.ExtensionData.Extension.$schedTaskItem.Properties.cPassword”
if ((Invoke-Expression -Command $command) -ne $null)
{
$obj = New-Object –typename PSObject
$obj | Add-Member –membertype NoteProperty –name GPOName –value ($report.GPO.Name) –passthru |
Add-Member –membertype NoteProperty –name Side –value (“Computer”) –passthru |
Add-Member –membertype NoteProperty –name Extension –value ($scheduledTaskTypes[$schedTaskItem])
Write-Output $obj
}
}
}
else
{
if ((Invoke-Expression -Command $command) -ne $null)
{
#Now create a new custom object containing the GPO Name, GPO side (computer or user) and extension where we found the password
$obj = New-Object –typename PSObject
$obj | Add-Member –membertype NoteProperty –name GPOName –value ($report.GPO.Name) –passthru |
Add-Member –membertype NoteProperty –name Side –value (“Computer”) –passthru |
Add-Member –membertype NoteProperty –name Extension –value ($extension.Name)
Write-Output $obj
}
}
}
}
}
#now check user extensions
foreach ($extension in $report.GPO.User.ExtensionData)
{
foreach ($cExt in $userExtensions.Keys)
{
if ($extension.Name -eq $userExtensions[$cExt])
{
#create the standard command we’ll invoke for all extensions
$command = “`$report.GPO.User.ExtensionData.Extension.$cExt.Properties.cPassword”
#need to handle the special case for Scheduled where there could be multiple types
if ($extension.Name -eq “Scheduled Tasks”)
{
foreach ($schedTaskItem in $scheduledTaskTypes.Keys)
{
$command = “`$report.GPO.User.ExtensionData.Extension.$schedTaskItem.Properties.cPassword”
if ((Invoke-Expression -Command $command) -ne $null)
{
$obj = New-Object –typename PSObject
$obj | Add-Member –membertype NoteProperty –name GPOName –value ($report.GPO.Name) –passthru |
Add-Member –membertype NoteProperty –name Side –value (“User”) –passthru |
Add-Member –membertype NoteProperty –name Extension –value ($scheduledTaskTypes[$schedTaskItem])
Write-Output $obj
}
}
}
else
{
if ((Invoke-Expression -Command $command) -ne $null)
{
#Now create a new custom object containing the GPO Name, GPO side (computer or user) and extension where we found the password
$obj = New-Object –typename PSObject
$obj | Add-Member –membertype NoteProperty –name GPOName –value ($report.GPO.Name) –passthru |
Add-Member –membertype NoteProperty –name Side –value (“User”) –passthru |
Add-Member –membertype NoteProperty –name Extension –value ($extension.Name)
Write-Output $obj
}
}
}
}
}
}
}
[/codebox]
Excellent article shedding light on a security vulnerablity that has been out there for some time. While I agree step 1 is identifying whether and where your domain leverages GPP passwords, I’m now at the point where I’m interested in step 2 – ways to manage these passwords through GP that do not compromise security. I’m specifically interested in the management of Schedule Tasks and their passwords. Are you aware of any secure workarounds to this issue that have been used?
David-
Sorry for the delayed response. There is no workaround, using the mechanism Microsoft provides, to make these passwords more secure. In other words, if a user can access the hash of the password, then they can run one of the many tools in the wild to decrypt it. The one possible mitigation here, outside of a third party product, is to create the GPO, deploy the scheduled task, and then delete the GPO as soon as all clients have it. That way, the window for someone discovering the hash is limited. Not a perfect solution but something to consider.
Darren
Darren-
it would be great to see that Password property detection integrated into the “GPOExporter” results. One tool to rule them all 😉
We prevented casual snooping of the file by changing security filtering from Authenticated Users to Domain Computers. How much more secure were we?
Not very.. now that I see could have simply viewed the xml file on the local client. 🙂
Right. And frankly, if users have local admin rights on their systems, they can impersonate local system and view the SYSVOL content that way as well.
running Get-GPOReport -All -ReportType Xml for me put in the beginning on the first array item and created another array item at the last index that is just . this caused some error text. i was able to fix by changing line 39 to
[xml]$report = $GPOReports[$index].replace(“”,””).replace(“”,””)
not sure why it seems that it only effected me
it seems my xml tags were removed there was (less than symbol) GPOS (greater than symbol) in the beginning and (less than symbol and backslash) GPOS (greater than symbol) as the last array item. my correction replaces these tags with nothing.
Hey Darren,
I’ve ran the script but it doesn’t seem to do anything. Basically, when i enter that command by going to that script .\Get-GPPCPasssword, it just took me to another line. Please advise
Same here, the script doesnt seem to work on 2012 R2 🙁
Have a look on our freeware page (https://sdmsoftware.com/gpoguy/free-tools/library/). Since this article was written, I wrote a GUI tool called GP Preferences Password Remediation utility that both lists out and remediates GPP Passwords within GPOs. Maybe you’ll have better luck there.