I’ve found that one of the most useful features of PowerView (outside of its user hunting capabilities) is its ability to enumerate local group membership on remote machines. I’ve spoken about this briefly before, and gave some details on its utilization of the ADSI WinNT Provider in the “Pass-the-Hash is Dead: Long Live Pass-the-Hash” post. My colleague @sixdub wrote an excellent post titled “Derivative Local Admin” that shows the power this functionality can give attackers, and fellow ATD member @_wald0 expanded on this with his “Automated Derivative Administrator Search” post.
This functionality has been indispensable on both our pentests and longer-term red-team engagements. I wanted to do a more detailed writeup on Get-NetLocalGroup, its recent changes, and how you can use it effectively on assessments. I’ll also cover some new functionality that uses Group Policy Object information to derive local group membership, all without communicating directly with a target server.
Note: this new functionality is in the dev branch of PowerSploit (PowerView’s new home). After additional testing the dev branch will be merged into master in the next week or so.
For some quick review, Get-NetLocalGroup utilizes the Active Directory Service Interfaces [ADSI] WinNT provider to query the members of a local group (default of ‘Administrators’) from a remote machine. We can pull not just group member names, but whether the member is a group or user, whether the account is disabled, the account’s last login time, the account SID, and more. With the SID we can also determine if the result is a local account/group or a domain account as well. And what’s really cool is that we can query all of this information as an unprivileged domain user context (though you do need a domain authenticated account).
This information is great for determining whether you can pass-the-hash to a remote machine, important in the post KB2871997 world. If the local account with a SID ending in -500 is enabled or a domain account is in the machine’s local administrators, you can still pass-the-hash with those credentials. This is what Get-NetLocalGroup was originally built for, but after operating with it for a while, we’ve started to realize other useful cases for this cmdlet. @sixdub‘s post is a great example of unanticipated advantages of having this functionality in your toolbox.
Also note that the old Get-NetLocalGroups function has been depreciated- if you’d like to list the local groups on a machine, use the -ListGroups option. You can also specify the local group you’re querying for with -GroupName, for example Get-NetLocalGroup -GroupName “Remote Desktop Users” WINDOWS1.testlab.local.
Get-NetLocalGroup accepts NetBIOS names, IPs, and fully qualified host names for targets. There’s also an -API flag, which uses the NetLocalGroupGetMembers API call instead of the WinNT service provider. This returns more limited information, but is significantly faster. The Invoke-EnumerateLocalAdmin function (described below) also accepts this -API flag as well.
Local Groups, Server Targeting, and Domain Trusts
We soon started heavily utilizing Get-NetLocalGroup to target specific machines beyond passing-the-hash with local machine credentials. By enumerating the local administrators on a remote machine, we can pull any domain accounts that can install agents or otherwise triage a target. An example we run often is Get-NetDomainController | Get-NetLocalGroup to see what users can compromise the domain controllers in an environment. One sometimes confusing point is that these results for a domain controller are NOT just the members of the ‘Domain Admins’ group, but rather the members of the Administrators BUILTIN container on the DC. While ‘Domain Admins’ will be a part of the result set, you’ll sometimes find additional groups and users that have local administrative rights on a DC. These make excellent targets for later user hunting.
If you’re on a shorter timeframe pentest, and want to run this functionality on ALL machines in the network, you can execute Invoke-EnumerateLocalAdmin [-OutFile admins.csv] (with optional -Threads X for threaded functionality). This will perform local admin access enumeration on all computers listed in Active Directory and output everything to a nicely sortable .csv. But be warned: this can be very slow for large environments, and it is definitely not stealthy, as you are touching every machine as quickly as your system will allow.
Another nice side effect of Get-NetLocalGroup output is that you can easily determine if a result is a member of the target machine’s domain, or if the access is operating across a domain trust. The “Domain Trusts: We’re Not Done Yet” post covers the Invoke-EnumerateLocalTrustGroups (now “Invoke-EnumerateLocalAdmin -TrustGroups” in PowerView 2.0) cmdlet, which automates all of this for you. It enumerates local administrative results for all machines in a domain, and returns results that are not members of the target machine or machine’s domain, OR domain groups that have a user outside of the machine’s domain. This is can be useful when hopping between trusts.
Get-NetLocalGroup – New Developments
We started using this function more and more to target specific servers beyond just domain controllers. One of the issues we ran into is that any domain results for a server are preserved in NT4 “shortname” domain syntax, which can sometimes complicate later enumeration. This was recently fixed by adapting some of Bill Stewart’s code that utilizes the NameTranslate COM object to transform domain names between various formats (in our case, NT4 to canonical). This gives us nice new results like the screenshot at the top of the post.
With the full canonical domain name, we can also now easily recursively resolve any domain group results down to their user members. The new -Recurse flag for Get-NetLocalGroup does just this, returning an effective set of domain user/groups that can access a particular server with administrative rights:
This has greatly sped up user hunting engagements with complex, nested group relationships. This functionality was also recently added to Invoke-UserHunter and Invoke-UserHunter -Stealth (formerly Invoke-StealthUserHunter), with the -TargetServer <SERVER> argument. This will perform a recursive Get-NetLocalGroup <SERVER> -Recurse enumeration of a target server, and hunt for where any of these users are logged in on the network:
Tracking Local Administrators by Group Policy Objects
We first started diving into GPO enumeration a while ago when Skip Duckwall asked me if it was possible, through PowerView, to enumerate what organizational units a particular Group Policy Globally Unique Identifier (GUID) applied to. The “GPP and PowerView” post demonstrated how this could be done, by tracing a GPP GUID name back to the computers it applies to.
We then tried to take it one step further, and realized it was possible to map what machines a specific user or group has local administrator (or RDP) rights on by correlating GPO, OU, and user membership data. This approach has the advantage of no communication with a target system, as it only queries your primary (or specified) domain controller for the relevant information. I’ll walk through this process step by step and show how some of PowerView’s weaponized functions take care of this for you.
Get-NetGPOGroup is where everything starts. It will enumerate all current GPOs with Get-NetGPO and will parse any GptTmpl.inf files (located at “$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf”) and any Groups.xml files (located at “$GPOPath\MACHINE\Preferences\Groups\Groups.xml”). GptTmpl.inf is used by Group Policy to set ‘Restricted Groups’, i.e. what users are members of specific local groups on machines where the policy is applied. Groups.xml is used by Group Policy Preferences to perform similar task. The results of Get-NetGPOGroup will look something like this:
You can see above that Members contains the security identifiers of users/groups we’re interested in, and the MemberOf field specifies their local group membership (in this case ‘Administrators’, with well known SIDs documented here). If we combine this information with the complete group membership of a target user to identify what GPOs set local group membership for our target user, and then find linked OUs and resulting computers similar to how we enumerated GPP, we can get machines our target can access with only sending traffic to our domain controller.
Find-GPOLocation will do just that, taking a specified -UserName <USER> or -GroupName <GROUP> and returning the set of computers they can access. If a user or group name is not specified, all relationships for the domain will be returned. Also, a different -LocalGroup can be specified (default of ‘Administrators’).
Here’s how it works:
- Retrieve the SID for the specified user or group name.
- Enumerate all groups the user/group is currently a part of by invoking Get-NetGroup -UserName X which will retrieve membership from the tokenGroups attribute. Use this to build a list of target SIDs of all groups the target is a member of.
- Enumerate all GPOs that set ‘Restricted Groups’ or Groups.xml by calling Get-NetGPOGroup.
- Matching the target SID list to the queried GPO SID list to enumerate all GPOs the target is effectively applied with.
- Enumerate all OUs and sites that any resulting GPOs by linking the GPO GUID with the gplink attribute.
- Query for any computers in resulting OUs and return any resulting sites.
There’s also a functional inverse, Find-GPOComputerAdmin, that will return what objects have membership for a specified -LocalGroup on a target system. The -Recurse flag will resolve the membership of any results that are a group themselves.
And just like Get-NetLocalGroup, these functions don’t need elevated access to query this information. However, Find-GPOComputerAdmin will only return domain results, i.e. local user accounts will not be returned.
Local group enumeration is one of the more useful tools we have in our red team toolkit. Get-NetLocalGroup has been indispensable and Find-GPOLocation has started to prove its worth as well. Hopefully you guys find this new functionality as useful as we have on complex engagements.