Monitor GPO Links modifications
You can track GPO links changes by analyzing the security eventlog, GPO links will give you information on which objects your GPO is applied to. We will monitor GPLink attribute changes.
In order to analyze in real time the security log of all your DCs you need to pay for a Syslog solution, like Snare or Kiwi. Or you can try to setup an eventlog forwarding solution if you are under Windows 2008, you can also try to run a script that catches security log events, but you might encounter some performance issues.
We noticed in last post that the GPLink attribute was already set up in default audit settings. We will have a closer look at this attribute with adsiedit.msc:
GPLink has the following structure [LDAP://CN={31F5F311-013D-11D2-125F-00D04F0E84F9},CN=Policies,CN=System,DC=ldap389,DC=info;0][LDAP://CN={31F5F311-0187-EFD2-345F-BED04F1284F9},CN=Policies,CN=System,DC=ldap389,DC=info;2], each GPO linked to the OU/Site/Domain is separated with “[]”, between the brackets there are two data that are separated with a ; : The GPO Distinguished Name and how this GPO is applied, a number between 0 and 3:
- 0: GPO enabled, not enforced
- 1: GPO disabled, not enforced
- 2: GPO enabled and enforced
- 3: GPO disabled and enforced
You can retrieve GPLink information described above by using the following script. We now need to monitor the security log to catch GPLink modifications in real time:
Under Windows 2003 Event ID 566 is created in security log when you modify an audited attribute. The problem is that you do not have information about the value of the attribute before and after the modification.
You have the following information: Who modified the GPO link, on which DC and the Distinguished Name of the OU/Site/Domain modified. If you want to get the GPLink value after modification, you just need to do an LDAP query on the DC where the modification occurred. To retrieve the GPLink value before modification you need a lag site, Microsoft does not support delayed replication DC if you use it for a disaster recovery plan, but you can use a lag site in this context: We just use it to retrieve information before modification. Then we just need to compare data after and before modification to know which GPO link was modified.
Under Windows 2008 you do not need a lag site, you have the before and after value of the modified attribute in the security log, you just need to activate AD auditing on your DC. Event ID is 5136, value before modification is in the event where operation equals value deleted, value after modification is in the event where operation equals value added
If you create your first GPO Link on an object (e.g. OU/Site/Domain) value before modification will be void, value after modification equals the new GPlink. If you delete the last GPLink on an object value before modification equals the single GPLink and value after modification is void. If you are monitoring GPO Links on a Windows 2003 environment it will be a bit more complex: If you create you first GPO Link on a object, the LDAP request on the lag site will return an error code, because value doesn’t exist, so you have to handle error codes in your LDAP requests. It’s the same when you delete last GPLink, you have to handle the error code returned by your LDAP request made on the DC where the deletion occurred.
Depending on the OS version of your DC you need to set up your Snare Agents in order to catch event IDs 566 or 5136 in security log, you can of course use other Syslog Softwares. In our infrastructure the application collecting Snare agents data is Kiwi. We noticed that some values were not logged with friendly names, especially the Distinguished Name of the OU/Site/Domain on which the modification happened. This information was logged with the GUID string value of the object, if you want to get the friendly name (DN) you need to use this script provided by MS. There is a little error in this KB article, the function name is not correct, you should replace the penultimate line by “ConvertStringGUIDToHexStringGUID = octetStr”.
We will now show how to compare GPLink value before modification and after modification in order to be aware of GPO Links modifications. As already discussed a GPLink has two parts: Distinguished Name of GPO, and a numeric value that tells us how GPO is applied. Here are the differents cases:
- GPLink value before [DNGPO1;0][DNGPO2;3] and GPLink value after [DNGPO1;0][DNGPO2;3][DNGPO3;2] a new enforced link was created.
- GPLink value before [DNGPO1;0][DNGPO2;3] and GPLink value after [DNGPO1;0] Link of DNGPO2 has been deleted on our OU/Site/Domain.
- GPLink value before [DNGPO1;0][DNGPO2;3] and GPLink value after [DNGPO1;1][DNGPO2;3] Link of DNGPO1 was disabled (but not deleted).
- GPLink value before [DNGPO1;0][DNGPO2;3] and GPLink value after [DNGPO1;0][DNGPO2;2] Link of DNGPO2 was enabled and is still enforced.
- etc…
In order to compare data before and after we will use dictionary objects. We create two dictionaries, one with data before modification and a second with data after modification. The dictionaries keys will be the GPOs Distinguished Names and dictionaries values will be the numeric values between 0 and 3. Here is how two create both dictionaries:
'After modification GPLink dicitonnary Set GPLinkdict = CreateObject("Scripting.Dictionary") 'Before modification GPLink dicitonnary Set GPLinkdictLAG = CreateObject("Scripting.Dictionary") 'strGPLinkAfter is the GPLink value after modification, you retrieved it with the event log 'under Windows 2008 and with an LDAP request on the where DC the modification occurred under Windows 2003 if instr(strGPLinkAfter,"]") <> 0 then OUModifiedGPLINK = split(strGPLinkAfter,"]") For i = UBound(OUModifiedGPLINK) -1 to LBound(OUModifiedGPLINK) Step -1 GPOLinkstatus = split(OUModifiedGPLINK(i),";") GPLinkdict.add GPOLinkstatus(0),GPOLinkstatus(1) Next End if 'strGPLink before is the GPLink value before modification, you retrieved it with the event log 'under Windows 2008 and with an LDAP request on the LAG DC under Windows 2003 if instr(strGPLinkBefore,"]") <> 0 then OUModifiedGPLINKLAG = split(strGPLinkBefore,"]") For j = UBound(OUModifiedGPLINKLAG)-1 to LBound(OUModifiedGPLINKLAG) Step -1 GPOLinkstatusLAG = split(OUModifiedGPLINKLAG(j),";") GPLinkdictLAG.add GPOLinkstatusLAG(0),GPOLinkstatusLAG(1) Next End if |
Click here to download sample script:
Now we will read through GPLinkdict dictionary, containing keys and values after modification and compare them to keys and values of the other dictionary. If a key doesn’t exist in the GPLinkdictLag dictionary, containing keys and values before modification, this means that a GPO Link was just created. If for the same key in both dictionaries the value is different, then the GPO Link was enabled/disabled or the enforce parameter was changed. With this script you can find out when GPO Linkq are created or wether the state of an existing GPO Link has changed:
'The DC where the modification was recorded, retrieved with eventlog dcsource = "DCSOURCENAME" 'OU/Site/Domain name where GPO Link was modified, retrieved with eventlog OUName = "OUName" 'User who made the modication, retrieved with eventlog Username = "UsernName" For Each oGPLinkdict in GPLinkdict If Not GPLinkdictLAG.Exists(oGPLinkdict) Then Set objGPOd = GetObject(split(replace(oGPLinkdict,"LDAP://","LDAP://"&dcsource&"/"),"[")(1)) DNobjGPOd = objGPOd.Get("DisplayName") Msgbox Username&" created a link on this object: "& OUName &" / GPOName: "&DNobjGPOd & " / Link Value: "&GPLinkdict.Item(oGPLinkdict) Else If GPLinkdictLAG.Item(oGPLinkdict) <> GPLinkdict.Item(oGPLinkdict) then if (GPLinkdictLAG.Item(oGPLinkdict) = 0 OR GPLinkdictLAG.Item(oGPLinkdict) = 2) AND (GPLinkdict.Item(oGPLinkdict) =1 OR GPLinkdict.Item(oGPLinkdict) = 3) then Set objGPOd = GetObject(split(replace(oGPLinkdict,"LDAP://","LDAP://"&dcsource&"/"),"[")(1)) DNobjGPOd = objGPOd.Get("DisplayName") Msgbox Username&" disabled a link on this object: "& OUName &" / GPOName: "&DNobjGPOd & " / Link Value Before: "&GPLinkdictLAG.Item(oGPLinkdict)&" / Link Value After: "&GPLinkdict.Item(oGPLinkdict) Else Set objGPOd = GetObject(split(replace(oGPLinkdict,"LDAP://","LDAP://"&dcsource&"/"),"[")(1)) DNobjGPOd = objGPOd.Get("DisplayName") Msgbox Username&" enabled a link on this object: "& OUName &" / GPOName: "&DNobjGPOd & " / Link Value Before: "&GPLinkdictLAG.Item(oGPLinkdict)&" / Link Value After: "&GPLinkdict.Item(oGPLinkdict) End if End if End if Next |
Click here to download sample script:
Finally we need to get GPO links that were deleted, for this we will read through the GPLinkdictLag dictionary. If a key doesn’t exists in the GPLinkdict dictionary then the GPO Link was deleted:
'Enter LAGDC if you have one, it will be useful only in this case : if a Link is deleted and the GPO is deleted from the domain as well at the same time 'In that case we cannot retrieve GPO Display Name if we do not have lag site. 'If no lag site then LAGDC = "" LAGDC = "LAGDCNAME" For Each oGPLinkdictLAG in GPLinkdictLAG If Not GPLinkdict.Exists(oGPLinkdictLAG) Then err.clear Set objGPOd = GetObject(split(replace(oGPLinkdictLAG,"LDAP://","LDAP://"&dcsource&"/"),"[")(1)) if err.number <> 0 then if LAGDC <> "" then Set objGPOd = GetObject(split(replace(oGPLinkdict,"LDAP://","LDAP://"&dcsource&"/"),"[")(1)) DNobjGPOd = objGPOd.Get("DisplayName") Else DNobjGPOd = split(oGPLinkdictLAG,"[")(1) Else DNobjGPOd = objGPOd.Get("DisplayName") End if Msgbox Username&" deleted a link on this object: "& OUName &" / GPOName: "&DNobjGPOd & " / Link Value was: "&GPLinkdictLAG.Item(oGPLinkdict) End if Next |
Click here to download sample script:
By following the guidelines described in this post and the previous one you can build your own GPO monitoring tool. In addition to creating reports you can launch a backup when a GPO is modified as well, by using sample scripts shipped with the GPMC.
This post is also available in: French