Compass Security Blog

Offensive Defense

Exploit credentials stored in Windows Group Policy Preferences

Group Policy preferences are a new feature set available since Windows Server 2008, which shouldn’t be confused with the well known Group Policy objects (GPOs) dating back to Windows NT. The main idea behind the creation of Group Policy preferences is the ability to push so-called “unmanaged” settings. Compared to “managed” GPOs, group policy preferences can be altered by the end user and aren’t enforced on a regular basis.

Before the release of the Group Policy feature, administrators could already set user preferences via scripts or .reg files. This lead to complex login scripts potentially containing sensitive information. Using Group Policy features, you can now manage this kind of settings centrally and in a convenient way.

While this addition was released with Windows 2008 Server, you can install the client-side extensions on client computers running on Vista, Windows XP SP2 or Windows 2003 SP1 for backward compatibility with all your workstations.

Let’s see an example on how we can define a local user on some workstations. It starts with the creation and edition of a group policy object:

In the Group Policy Management Editor, we still see our well-known Policies folder, but also a new hierarchy for these unmanaged preferences. Creating a local administrator on the machines targeted by the GPO “GPO_local_accounts” is only a matter of clicks:

Where is this setting stored and how secure is it? Group Policy Preferences are stored, as all other GPOs, within the SYSVOL folder on the domain controllers (%LOGONSERVER%\SYSVOL) but within xml files.

The above created policy gets translated into the following groups.xml file:

<?xml version="1.0" encoding="utf-8"?>
  <Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}">
    <User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}"
     name="ladmin_gpo" image="0" changed="2012-02-03 07:10:48"
      <Properties action="C" fullName="Local admin created by GPO"
       changeLogon="0" noChange="0" neverExpires="0"
       acctDisabled="0" userName="ladmin_gpo"/>
    <Group clsid="{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}"
     name="Administrators (built-in)" image="2"
     changed="2012-02-06 10:45:50"
      <Properties action="U" newName="" description=""
       deleteAllUsers="0" userAction="ADD" deleteAllGroups="0"
       removeAccounts="0" groupSid="S-1-5-32-544"
       groupName="Administrators (built-in)">
          <Member name="ladmin_gpo" action="ADD" sid=""/>

So how secure is my password, stored within the cpassword attribute? According to the documentation, it’s using AES-256 – so pretty secure, isn’t it?

Well, not exactly, as the key is public and documented in the MSDN. All you need to do is open CrypTool, add some padding to the value to be base64 compliant, and perform a base64 decoding:

Choose then the AES decryption option (Analysis – Symmetric Encryption (modern) – Rijndael (AES) and paste the public key:

That’s it, the password is decrypted without any brute-force attack:

As conclusion, while Group Policy preferences are a great tool to distribute unmanaged settings, it should not be used to push down features containing credentials such as:

  • User preferences
  • Database connections strings
  • Scheduled tasks
  • Mapped drives settings
  • Service preferences




  1. Rich

    Can you explain a little more about padding? I was able to decrypt the cpassword in my groups.xml file, but it was missing the last two characters. At first I padded with “==” like your example, but that didn’t work. After some searching about Base64 I found that I could use “=” instead. That worked, and decrypted the entire password. I was just wondering if you could explain in laymens terms, how this padding works?

    • Alexandre Herzog

      Hi Rich,

      Sure. The idea of Base64 encoding is to transform 3 octets of data into 4 printable characters (usually a to z, A to Z, 0 to 9 and characters + and /). If the message to encode hasn’t a length matching exactly a multiple of 3, one or two padding characters of “=” get inserted – e.g.
      Base64 encoding of A returns QQ==
      Base64 encoding of AA returns QUE=
      Base64 encoding of AAA returns QUFB
      Base64 encoding of AAAA returns QUFBQQ==

      If you want to successfully decode a Base64 string, you therefore need to pad the string with = characters as long as its length isn’t a multiple of 4. In the example of this blog, our encoded string “9QHhFTUdm6rDgu30J7ShZfqt07T6vOUGkyAFG3G7M+5AotJjkOva7E9KSAcamdrruTgly0O/uVTB/UUdLNU4775b5381hyuUzkd4lJW+llcNNNrQlYu7zqH3/i+8jfjhUq9lqPn8VjCtb9iaEqWbKQ” is 150 characters long. We therefore have to add two padding character = to ensure an adequate Base64 decoding (152/4 = 38.0).

      • 0xoldman

        thank you
        really useful for me.

  2. G M

    I have a key and I have the MSDN key (which should be it) but I cant seem to get the password, where are the periods useful and do I ignore them? could you give us the original Password? I have they key ‘ofXegRVNSMi8K7CvlHYEZBEgxPQuN1PQbqbPaU9r9e0’, I 64-byte encode it ‘???MH?+???vd ??.7S?n??iOk??’ and when I run it through I get . What have I done?

    • Cyrill Bannwart

      Your base64 decoded value seems to be invalid. Create a new file in CrypTool with the base64 encoded value. Add a “=” character for the padding. afterwards use the base64 decoder under “Indiv. Procedures -> Tools -> Codes -> Base64 Decode”. On the decoded value you can then use the AES decryption with the published key.

Leave a Reply

Your email address will not be published. Required fields are marked *