Active Directory Discovery with a Mac
Published:
Due to the nature of the work, many Red Teamers have a much stronger focus on Windows Enterprise networks. Because of this, Red Teamers have a myriad of tools and experience querying Active Directory from a windows box. Many Red Teamers start off with the common net user
, net group
, net localgroup
commands, and now everybody is familiar with Will Shroeder’s PowerView project. Some red teamers still want to use something like dsquery
to do some custom LDAP queries like dsquery * -filter “(&(objectclass=group)(name=*admin*))” -limit 1
(this is also possible with PowerView). You can even run something like the BloodHound Project to quickly get an insane amount of Active Directory information if you have the ability to run PowerShell or C# code. What if you’re on a Mac though?
Overview
I’m going to discuss a few different methods for doing some AD recon on a Mac with strictly built-in tools by comparing them to the more common Windows versions. Let’s start with a sample useful command and break it down:
dscl “/Active Directory/TEST/All Domains” read “/Groups/Domain Admins” member memberof
Ok, so what’s actually happening here? dscl
(/usr/bin/dscl) is MacOS’ directory service command line utility. It allows users to not only query different directory services, but configure them as well (with appropriate permissions). The general format for it is dscl [options] [datasource [command]]
. For our purposes, we’re going to be using two different data sources - local and the domain’s active directory. To query the local system, we use “.” and to query AD we use “/Active Directory” in place of the datasource.
The structure for this is based off of Apple’s old NetInfo Directory structure, and now includes some mix of their Open Directory (which is a fork of OpenLDAP) and Microsoft’s Active Directory. dscl
can be used interactively by simple running dscl
without any arguments. From here, you can use ls
and cd
to browse around the directory structure. Once you get down to a specific element, you will either read
it or cat
it (they alias to the same thing). In our example, TEST is the NETBIOS name for the current domain we’re in. When you get data back from dscl, it’s in the format of attribute:value.
You can also browse around the structure atomically with commands like:
dscl “/Active Directory/TEST/All Domains” ls /
This will enumerate the highest-level directory structure for Active Directory in the domain. This will be the same for every Domain, but will be a little different when we enumerate locally. To illustrate the differences, the local query is below on the left and the domain query is on the right:
So, back to our original command, we’ve covered the first two parts. Next, we have read
. This is stating that we’re going to read (or cat) the contents of the next one thing in the command. If we wanted to read a bunch of different objects, we would use the readall
command. It’s important to note that the dscl command does not support wildcards in its commands. Similarly, if we just want to list out what the possible things to read are, we use list
or just ls
. We are going to read the Active Directory data for the “/Groups/Domain Admins” object. Specifically, we’re interested in the member and memberof fields, so we will only request that information from the server. If you’re used to LDAP, this last field is selecting the specific attributes we’re interested in and only returning those.
Let’s now dig into this a bit more and see how this corresponds to some common Windows commands:
net user [username]
dscl . ls /Users
This command will list out the local user accounts. Two things will probably immediately jump out at you when you run this:
- there are a bunch of accounts
- a lot of them have a leading underscore Accounts that start with an underscore are service accounts. This is pretty common in *nix environments. An abbridged output shows some default user accounts, default service accounts, and a test user account:
For any of these accounts, if you want to get more information, use the read
or cat
commands:
There are a few areas that are important to note that will be covered in later sections: dsAttrTypeNative, GeneratedUID, RecordType, SMBSID. If you cat an account on a domain, you’ll get a lot more information as shown in the next section.
net user [username] /domain
dscl "/Active Directory/TEST/All Domains" ls /Users
dscl "/Active Directory/TEST/All Domains" read /Users/test
In my test environment, the output of the first command only reveals a few users:
However, if we dive into a user more closely, we will get a large amount of data. I’ve truncated it here, but there’s also domain specific data like user images and even plist files embedded in this data.
A couple of interesting pieces of information to note:
- distinguishedName: CN=Apple Macintosh,CN=Users,DC=test,DC=local
- My Test account’s full name is “Apple Macintosh” which is in the CN=Users group within the test.local domain
- memberOf: CN=Domain Admins,CN=Users,DC=test,DC=local
- This provides a list of all groups this account is a memberOf. Very important to look here for interesting groups.
- sAMAccountName: mac
- If you want to actually refer to this account on the network, it’s going to be by the samaccountname
- GeneratedUID: 755193A8-B596-4230-9549-55887845E73B
- This will come into play for using the
dscacheutil
anddsmemberutil
utilities
- This will come into play for using the
- NFSHomeDirectory: /Users/mac
- If the user’s home directory was on a fileshare, it would be indicated here
- SMBGroupRID: 513
- SMBPasswordLastSet: 131596785263339509
- SMBPrimaryGroupSID: S-1-5-21-3278496235-3004902057-1244587532-513
- SMBSID: S-1-5-21-3278496235-3004902057-1244587532-1105
- UserShell: /bin/bash
net localgroup [administrators]
dscl . ls /Groups
dscl . read /Groups/admin
dscl . read /Groups/wheel
dscl . read /Groups/com.apple.access_ssh
dscl . read /Groups/com.apple.access_screensharing
dscl . read /Groups/com.apple.access_sessionkey
dscl . read /Groups/com.apple.access_ftp
dscl . read /Groups/com.apple.access_disabled
This topic is a little less straightforward on a Mac than it is on Windows. You can see all of the local groups with the first command, but you’ll notice that there are a lot of groups. Some of these I’ll go into detail here, but you should definitely check them out to see if the organization added their own or modified some. Initial ones to look into are the admin (BUILTIN\Administrators) and wheel groups - these both often provide access to higher level administrative privileges. Apple provides a few other interesting local groups that should be examined:
- com.apple.access_ssh - users that can ssh into this machine
- com.apple.access_screensharing - users that can access this machine via ARD or VNC
- com.apple.access_sessionkey
- com.apple.access_ftp
- com.apple.access_disabled - this lists accounts that are disabled (awesome for potential backdoors by re-enabling them)
Groups tend to have two main formats to them; they will either list out GroupMembers (by GeneratedUID) and GroupMembership by shortnames, or they will list NestedGroups (by GeneratedUID). The first instance is easy because it will give the shortnames for the members of that group as well, but what if you’re just given the GeneratedUIDs for other NestedGroups? Consider the following two groups: Local Admin group and the Local com.apple.access_ssh group:
The admin group provides the GeneratedUID associated with that group, the members of the group with their GeneratedUIDs, the corresponding shortnames, and lists out the other nested groups by their GeneratedUIDs. This is a little annoying because you can’t easily tell which groups are nested. A couple other important things to note:
- PrimaryGroupID: 80
- RealName: Administrators
- This provides the common name for the group
- Recordname: admin BUILTIN\Administrators
- This one is pretty interesting, it gives the names used to reference this group in both Mac and Windows. This admin group is commonly referred toa s the BUILTIN\Administrators group on windows.
- SMBSID: S-1-5-32-544
- This is the standard SID for the BUILTIN\Administrators group. This is a handy way to identify common Windows groups and get the standard Windows SIDs for users and groups. If you’re currious about other standard windows SIDs, check out Microsoft What’s a good way to start working with these GeneratedUIDs, SIDs, ID values, and names?
dsmemberutil & dscacheutil
Enter two more built-in tools: dsmemberutil
(/usr/bin/dsmemberutil) and dscacheutil
(/usr/bin/dscacheutil). dsmemberutil “is a program that implements the membership API calls” and dscacheutil “does various operations against the Directory Service cache … replac[ing] most of the functionality of the lookup tool previously available” - macOS man pages. dsmemberutil is a pretty interesting tool actually - it allows us to do a lot of conversions between uuid, id, sid, and names of users and groups. It also allows us to check if users are members of a group. For example, what if you wanted to see what groups are nested within the com.apple.access_ssh NestedGroups? The GeneratedUID is a UUID when it comes to dsmemberutil, so that’s what we’ll be using. Our first step is to turn the UUID into an id:
$ dsmemberutil getid -X “ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000050”
gid: 80
This gives us the gid of the object referenced by the GeneratedUID (UUID). We specify a capital X because we’re providing a group’s UUID (it would be a lowercase x if it was a user’s UUID). From here, we use dscacheutil to get information about that gid:
This dumps the information for a group (-q group) with an attribute (-a) where the gid value is 80. dscacheutil
can provide some sneaky access in thie regard. For example, assume you know that the RID of the local “Administrators” group should be 544. This group can technically be renamed, just like in Windows, but the SID needs to be the same. We can use dsmemberutil
and dscacheutil
to go back from this SID to the real name:
dscl searching
dscl
offers the ability to search for key values with the search
action. Unfortunately, dscl only provides the ability to search for exact matches and does not provide support for wildcard searches. For example, if you want to search for all local groups that root belongs to:
dscl . -search /Groups GroupMembership root
For this command, we specify which directory we want to search (/Groups), which attribute we’re interested in (GroupMembership), and which value we’re looking for (root). This method allows a single match within a single attribute. You cannot do wildcards, regular expressions, or check for multiple values.
LDAP Queries - ldapsearch
In Windows, LDAP queries can be easily done with dsquery and now in PowerShell. On a Mac, LDAP queries can easily be done with the ldapsearch
binary (/usr/bin/ldapsearch). The format for ldapsearch is a little unintuitive, but not crazy:
ldapsearch -H ldap://test.local -b dc=test,dc=local -z 1 “(&(objectclass=group)(name=*admin*))” samaccountname
Breaking this down a bit, -H specifies where to actually query and the -b specifies the searchbase. In this example, these two are the same, but they don’t have to be. If you discover that there is a trusting domain connected to your domain, you can specify that domain with the -b flag (be sure to specify the fully qualified domain name). This will result in your computer asking the DC specified by -H to ask the DC specified by -b for the LDAP query answer. The -z parameter specifies the number of results to return. It’s helpful to get one result back first to help manage what kind of data will be pulled back, discover attributes to select (like samaccountname), and make sure your query is selecting what you think it is. A -z value of 0 (or its omission) will return all results. While this query format is a bit harder than using dscl, it does provide the ability to use regex and make more powerful queries. I will create another blog post specifically on some useful LDAP queries for red teaming.
net group “Domain Computers” /domain
dscl "/Active Directory/TEST/All Domains" ls /Computers
dscl "/Active Directory/TEST/All Domains" cat /Computers/testmac$
It’s important to note that computer names in this format will have a trailing $ symbol at the end. In my domain, there are only two computers - DC.test.local and testmac.test.local. Thus, these show up by their NETBIOS names followed by $: DC$ and testmac$.
There is a bunch of interesting information to get from these commands that you normally need complicated LDAP queries for:
- name: testmac
- This is the NETBIOS name for the computer
- networkAddress: 172.16.187.137
- The current IP address for the computer
- operatingSystem: Mac OS X
- Get the OS to help differentiate between different kinds of systems on the network
- Beneficial in conjunction with operatingSystemVersion: 10.13.1
- servicePrincipalName: afpserver/testmac.test.local host/testmac.test.local cifs/testmac.test.local vnc/testmac.test.local
- This gives information about the different services that are running on the system
- GeneratedUID: 5D111BDC-EB0B-4DF0-80C8-C5C61E46B899
Domain Information
dsconfigad -show
You can get a decent amount of information about a domain from this command such as:
This points out the current domain and forest, the domain controller, the groups allowed to administer the computer, and the computer account name.
Compiled Commands
A compiled list of these commands and other potentially useful commands is in the following github gist