Finding the paths to escalate privileges in Active Directory (AD) can be challenging when dealing with a complex web of permissions, as shown in the picture above. This blog explains a method for discovering less obvious escalation paths by focusing on accounts with broad access that are not well protected (AdminCount False).
Often, during offensive engagements, you’ll find yourself with some initial access to the network, and you want to expand your access. On most networks, this can be achieved most efficiently by escalating privileges within Active Directory (AD). There are numerous ways to identify escalation paths, which generally involve identifying AD objects that are not properly protected and can perform a variety of interesting actions.
One interesting way to identify this is by analyzing the AD object attribute “AdminCount”. Microsoft leverages this attribute to apply additional protections to the object. I won’t be going over this in detail here, but you can learn more here: https://learn.microsoft.com/en-us/windows/win32/adschema/a-admincount
This value is set to either “True” or “False” with an AD object. AD objects that can do a lot of actions within the Domain are assigned a value of “True” (Example: Domain Admins, Enterprise Admins, etc.). The way I see networks is that you can divide them into two sections based on inbound permissions: objects with AdminCount: False, which allow more things to reach them, and objects with AdminCount: True, which allow fewer things to reach them.
It’s actually somewhat predictable when looking at an object’s inbound permissions. If AdminCount False, usually the same set of objects can reach it, and if it’s True, the same objects can reach it. Typically, you will not have an object with AdminCount: False that can reach an object that has AdminCount: True. If you do this, you are likely to identify new escalation paths.
When performing engagements, I often find a handful of accounts that numerous AD objects can reach due to AdminCount: False, which have excessive permissions that expose the entire domain to takeover. Generally, it’s just these few accounts that open the door for any attacker who can gain access from a lower-level object (for example, a Tier 1 help desk) and then escalate to these accounts, ultimately taking over the entire domain.
CVE-2017-8613: A good example of this common issue is with the Azure Sync account (MSOL_XXX). These accounts are frequently vulnerable to this permission abuse path because, for a considerable period, Microsoft failed to apply the AdminCount: True setting to these high-access accounts (they can dcsync by design).
https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2017-8613
https://learn.microsoft.com/en-us/security-updates/securityadvisories/2017/4033453
This same vulnerability is introduced to AD environments due to poor permission configuration. I observe this pattern in every engagement, and I wanted to share my analysis techniques to help others identify these escalation paths.
So, what I started doing on engagements is auditing all user accounts with AdminCount: False and their outbound rights. The idea is to identify accounts with lower inbound protections that can achieve a lot.
In Bloodhound, you can see this attribute in the “Node Info” tab:

You can list out all users with their AdminCount value via a simple query:
MATCH (u:User) return u.samaccountname, u.admincount
Note: I find it helpful to return the admincount attribute for most of the queries. This is provided as a simple example. It’s just good context to have when analyzing various attack paths.
Now, to automate this further, we need to determine their outbound rights to find who is admincount: false and can do more in the domain, as this will find interesting escalation paths similar to CVE-2017-8613:
def get_admincountFalseUsers_trans():
with GraphDatabase.driver(URI, auth=AUTH) as driver:
result = do_query(driver, "MATCH (m:User {admincount: false}) return m.name, m.objectid, m.admincount")
admincount_outbound_file=open("admincount_false_outbound_trans_rights.txt", "w")
for record in result:
if record["m.name"]:
username = record["m.name"]
objectid = record["m.objectid"]
admincount= record["m.admincount"]
result1 = do_query(driver, "MATCH (n) WHERE NOT n.objectid='"+objectid+"' MATCH p=shortestPath((u:User {objectid: '"+objectid+"'})-[r1:MemberOf|AddSelf|WriteSPN|AddKeyCredentialLink|AddMember|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns*1..]->(n)) RETURN count(p)")
for record1 in result1:
if record1["count(p)"]:
trans_rights=str(record1["count(p)"])
admincount_outbound_file.write("[-] User: "+username+" Transitive Outbound Rights: "+trans_rights+" Admincount: "+str(admincount)+"\n")
admincount_outbound_file.close()
with open("admincount_false_outbound_trans_rights.txt", "r") as fp:
entries = str(len(fp.readlines()))
print("[+] Generating a List of Users with AdminCount=False Transitive Outbound Rights: admincount_false_user_outbound_trans_rights.txt ("+entries+") lines")
The code snippet is pulled from AD-RECON; it will retrieve a list of users with AdminCount: False and then gather the numerical value associated with their transitive outbound rights. Then I will sort the output with bash because I failed to produce the output sorted in Python; maybe I will change that, but for now, just do the following:
```
└─$ cat admincount_false_outbound_trans_rights.txt| sort -k 7,7 -nr | less -S
```
This will sort the output based on the 7th element in the line, which is the numerical count for the transitive outbound rights in reverse numerical order (so highest rights on top):
```
[-] User: MSOL_F367GGD8CDD8@ORGRIMMAR.ORG Transitive Outbound Rights: 43848 Admincount: False
[-] User: acct1@ORGRIMMAR.ORG Transitive Outbound Rights: 43847 Admincount: False
[-] User: passreset@ORGRIMMAR.ORG Transitive Outbound Rights: 43847 Admincount: False
[-] User: domainjoin@ORGRIMMAR.ORG Transitive Outbound Rights: 43847 Admincount: False
[-] User: carol@ORGRIMMAR.ORG Transitive Outbound Rights: 43847 Admincount: False
...
```
The result is output that quickly identifies accounts with lower inbound protections, which can lead to various consequences. Now, all these aren’t always exactly the problem themselves, so you must look at the actual transitive outbound rights for these objects to see how they’re getting their permissions. When you do this, you’ll see there are a handful of accounts ending up in this graph that are allowing these lower-level Admincount: False accounts to do a lot of things.
Example: Since the MSOL account can dcsync and essentially take over the domain, and its Admincount is false, the “passreset” account can change its password and thus has an escalation path to take over the domain. Had the MSOL account been adequately protected with AdminCount set to True, this escalation path might not have existed. On most networks, you will find that the MSOL account is a problem, but it is not the only account that extends permissions up to higher-level AD objects.
Another example we often see is that the account used to add computers to the domain may not be properly configured and may not have permissions over the Domain Controllers (such as WriteAccountRestrictions and Owns), which creates a potential escalation path. See, getting to the domain controllers might be hard, but because the AdminCount: False for the “domainjoin” account, you just need to get any account that can reset a password (example, a Tier 1 help desk), and then take over the domainjoin account, and then the Domain Controllers.
When diving deep into AD, as in this case, you can discover numerous additional escalation paths to enhance security, not just the one initially required to achieve the desired access. Please note that on a Red Team, this might not be the primary goal, but for a penetration test (PenTest), it’s beneficial to include as many escalation paths as you can identify.
Andrew McNicol is the Chief Technology Officer and a co-founder of BreakPoint Labs, where he oversees the company’s technical strategy and manages the Cybersecurity Assessments line of business. He is a recognized expert in adversarial penetration testing, with more than 15 years of experience leading numerous technical teams on red team operations, vulnerability assessments, and penetration testing engagements to achieve each client’s specific objectives. Andrew holds numerous professional certifications and is a frequent speaker at government and industry security forums.