Auditing Users in Active Directory

Active Directory (AD) is Microsoft’s proprietary take on the widely utilised Lightweight Directory Access Protocol (LDAP) hierarchical database engine and underpins access control and central management for any Microsoft Windows based enterprise network.

It is an incredibly powerful system, but can become very difficult to administer if not handled carefully. As a result, regularly reviewing the AD setup should form a critical part of any organisation’s Information Security Management System (ISMS).

I recently discussed a mechanism for auditing passwords in use in AD ( and so I will move onto looking at the user and group objects themselves.

One of the most common mistakes that technical people make when trying to audit AD is to treat it like a classic relational database, rather than a hierarchical database which is a very different beast. While there are similarities – both are designed to store and retrieve large amounts of data quickly and accurately – there are many differences, not least of which is the way that they handle relationships between data and queries. For a high level overview of the two models, please review the following Wikipedia articles:

There are a number of tools and utilities out there for querying AD, some are freely available, some are commercial and proprietary, however, it is possible to get all of the information that you need with a little bit of programming knowledge, and the following code examples will demonstrate some common queries that will help to identify the issues I most often find whilst auditing and penetration testing against AD.

Please note that these code samples are written in C#, and require the Microsoft.Net Framework version 4 or later. You will also need to make use of the following namespaces, which are included as part of Microsoft.Net “System.DirectoryServices.AccountManagement”, “System.Linq”, and “System.Collections.Generics”.

The first thing we need to do is make a connection to AD. This means creating a context. I will refer to this later as simply “context”:

PrincipalContext ADContext(string domain, string username, string password) {
PrincipalContext context = new PrincipalContext(ContextType.Domain, domain, username, password);
return context;

With our context, we can now query for whatever information we need. A particularly common problem I have encountered is unnecessary administrative users. AD has three main groups which are typically used for administrative access; “Administrators”, “Domain Admins”, and “Enterprise Admins”. These are used as follows:

Administrators: Members of this group have full control over Domain Controllers within a given AD domain. Members include the “Domain Admins” and “Enterprise Admins” groups by default.
Domain Admins: This group has full control over a given domain within an AD forest.
Enterprise Admins: This group has full control over all domains within an AD forest.

For a detailed breakdown of these groups and their intended use, please refer to the following Microsoft TechNet article:

In order to get a comprehensive list of the administrators within a domain, we need to query membership of all three of the above groups, as well as any groups which are nested within them as these will inherit the same permissions. The following code sample shows how this can be done:

private List<Principal> Administrators() {
PrincipalSearcher searcher = new PrincipalSearcher(new GroupPrincipal(context) { SamAccountName = "Administrators" });
GroupPrincipal admins = (GroupPrincipal)searcher.FindOne();
PrincipalSearchResult<Principal> members = admins.GetMembers(true);
return members.ToList<Principal>();

We can then get the list of nested member groups or users with the following code samples respectively:

List<GroupPrincipal> memberGroups = Administrators().Where(p => p is GroupPrincipal);
List<UserPrincipal> memberUsers = Administrators().Where(p => p is UserPrincipal);

Another common issue I have seen is users with passwords in description fields. There is no easy way to programmatically identify whether a description field has a password contained in it; this is a manual checking process. However, it is straightforward to obtain a list of users with a non-empty description which we can then filter and manually review. The following code sample shows how:

private List<UserPrincipal> GetDescriptions() {
PrincipalSearcher searcher = new PrincipalSearcher(new UserPrincipal(context)));
PrincipalSearchResult<Principal> principals = searcher.FindAll();
return principals.Where(p => ((UserPrincipal)p).Description != "" && ((UserPrincipal)p).Description != null).Select(p => p as UserPrincipal).ToList<UserPrincipal>();

Finally, another common issue is active user accounts with passwords that do not expire, or have not been changed in some time. These can be obtained relatively easily with the following code samples.

This example shows how to retrieve a list of active accounts with non-expiring passwords:

List<UserPrincipal> GetNonExpiringAccounts() {
PrincipalSearcher searcher = new PrincipalSearcher(new UserPrincipal(context));
PrincipalSearchResult<Principal> principals = searcher.FindAll();
return principals.Where(p => (((UserPrincipal)p).Enabled.HasValue && ((UserPrincipal)p).Enabled.Value) && ((UserPrincipal)p).PasswordNeverExpires).Select(p => p as UserPrincipal).ToList<UserPrincipal>();

This sample shows how to retrieve a list of user accounts whose passwords are older than a specified number of days. An expiry time of 90 days is used as a default, however this can be changed to suit. Please note that some accounts may be ignored by this query incorrectly as the date on which their passwords were changed may not have been set:

List<UserPrincipal> GetUnchangedPasswords(byte days=90) {
PrincipalSearcher searcher = new PrincipalSearcher(new UserPrincipal(context));
PrincipalSearchResult<Principal> principals = searcher.FindAll();
return principals.Where(p => (((UserPrincipal)p).Enabled.HasValue && ((UserPrincipal)p).Enabled.Value) && (((UserPrincipal)p).LastPasswordSet.HasValue && ((UserPrincipal)p).LastPasswordSet.Value.AddDays(days) < DateTime.UtcNow)).Select(p => p as UserPrincipal).ToList<UserPrincipal>();

This is by no means the limit of what can be extracted from AD; I would recommend that anyone auditing their own AD infrastructure familiarise themselves with the other classes, properties and functions exposed by the “System.DirectoryServices.AccountManagement” namespace. Further information is available at the following URL:…

Find out how we can help with your cyber challenge

Please enter your contact details using the form below for a free, no obligation, quote and we will get back to you as soon as possible. Alternatively, you can email us directly at [email protected]