AD Objects & Schema

Active Directory is a database of objects. Every user, computer, group, OU, and GPO is an object with a class and attributes. Understanding group scopes, the adminCount attribute, service account types, and key attributes like userAccountControl is essential for both attacking and defending AD.

Active Directory looks like a tree of users and groups, but underneath it is a replicated LDAP database where every account, machine and policy is just an object with attributes. Once you can read those attributes, the whole domain stops being a mystery — for admins and attackers alike.

15 min read Visual + commands The AD data model

Most people meet Active Directory through a console — Active Directory Users and Computers, a tidy tree of folders with people and PCs inside. That tree is a comforting lie. What actually sits on a domain controller is a database: a single, hierarchical, replicated object store that you talk to with LDAP. A user isn't a person, it's a row with a hundred-odd columns. A group isn't a folder, it's an object whose member attribute happens to list other objects. The whole point of learning AD as a data model is that the moment you stop seeing "users and groups" and start seeing "objects and attributes," every audit query, every misconfiguration, and every attack path becomes something you can actually read off the wire.

AD is a hierarchical object database you query with LDAP

Strip away the marketing and Active Directory Domain Services is an LDAP directory: a tree (technically a forest of trees) where the leaves and branches are objects, each object holds attributes, and the legal shapes of those objects are defined by a schema. The protocol you use to read and write it is LDAP — port 389 for the domain, port 3268 for the forest-wide Global Catalog. Every PowerShell cmdlet, every net user /domain, every BloodHound collection run is, at bottom, an LDAP search translated into something friendlier.

Objects live in a namespace shaped like the DNS name of the domain. A domain called corp.local has a root of DC=corp,DC=local, and everything hangs beneath it. Containers and organizational units branch the tree; users, computers and groups are the leaves. There is no "file" and no "folder" in the filesystem sense — only objects whose place in the hierarchy is encoded in their name.

read one object as a bag of attributes
# everything in AD is reachable as an object — pull one and look at it raw
PS> Get-ADObject -Identity "CN=alice,OU=Staff,DC=corp,DC=local" -Properties *

# or query by LDAP filter instead of by name
PS> Get-ADObject -LDAPFilter "(objectClass=user)" -SearchBase "DC=corp,DC=local"

# the classic command-line LDAP query tool, still everywhere
C:\> dsquery user -name alice
C:\> dsquery * "CN=alice,OU=Staff,DC=corp,DC=local" -attr *

The objects: users, groups, computers, OUs and the rest

Almost everything you manage in AD is one of a handful of object types. Users are security principals — they can log on and be granted rights. Computers are also security principals (a quirk people forget): a domain-joined machine has an account, a password, and group memberships, and the computer class actually derives from user. Groups bundle principals together so permissions can be assigned once; their membership lives in the multi-valued member attribute. Organizational Units (OUs) are structural containers you can link Group Policy to and delegate control over. Plain containers (like the default CN=Users and CN=Computers) look similar but can't have GPOs linked. Group Policy Objects, contacts, printers, and shared folders round out the set.

objectClass and objectCategory: what an object is

Two attributes answer "what kind of thing is this?" and they are not the same. objectClass is multi-valued — it lists the whole inheritance chain. A user object reports top; person; organizationalPerson; user, because the user class is built by inheriting from those above it. objectCategory is single-valued and indexed, so it's what searches actually filter on for speed. For a user, the category resolves to the person class; for a computer it's computer.

This matters in practice: filtering on (objectClass=user) returns users and computers (because computers inherit from user), whereas (&(objectCategory=person)(objectClass=user)) returns only real users. Get this wrong in an audit script and you'll quietly include every machine account in your "user" count.

class chain vs. category
PS> (Get-ADUser alice -Properties objectClass,objectCategory).objectClass
top
person
organizationalPerson
user

# the precise filter for "real users only" — exclude computer accounts
PS> Get-ADObject -LDAPFilter "(&(objectCategory=person)(objectClass=user))"

Attributes: the columns that decide everything

An object is its attributes. Some are single-valued (sAMAccountName), some multi-valued (memberOf, servicePrincipalName). Most are harmless plumbing — but a small set decides identity, privilege and trust, and those are exactly the ones defenders should watch and attackers go straight for.

The attributes worth memorising:

  • sAMAccountName — the legacy logon name (DOMAIN\alice), unique per domain, still what Kerberos and lots of tools key on.
  • userPrincipalName (UPN) — the modern, email-style logon name ([email protected]).
  • userAccountControl — a bitmask of account flags. Bits here turn on Kerberos pre-auth disabled (AS-REP roasting), trusted for delegation, password not required, and account disabled. One integer, enormous security weight.
  • servicePrincipalName (SPN) — maps a service to an account. Any account with an SPN can be Kerberoasted — you request a service ticket and crack it offline for the password.
  • memberOf — the groups this object belongs to (a back-link, computed from the groups' member lists). Privilege lives here.
  • adminCount — set to 1 by the AdminSDHolder process on accounts that are (or were) in protected admin groups. A flag of "this is a high-value account."
  • nTSecurityDescriptor — the object's ACL. Who can reset its password, write its membership, or take it over. This single attribute is the entire field of AD ACL abuse.
  • msDS-* family — newer attributes like msDS-AllowedToDelegateTo (constrained delegation), msDS-AllowedToActOnBehalfOfOtherIdentity (resource-based constrained delegation, RBCD), and msDS-KeyCredentialLink (Shadow Credentials). Modern attack surface lives in this prefix.
pull the attributes that matter
# dump everything on one account
PS> Get-ADUser alice -Properties *

# Kerberoastable accounts: a user with a Service Principal Name
PS> Get-ADUser -LDAPFilter "(servicePrincipalName=*)" -Properties servicePrincipalName

# AS-REP roastable: pre-auth not required = bit 0x400000 in UAC
PS> Get-ADUser -LDAPFilter "(userAccountControl:1.2.840.113556.1.4.803:=4194304)"

# protected / high-value accounts flagged by AdminSDHolder
PS> Get-ADUser -LDAPFilter "(adminCount=1)" -Properties adminCount

The schema: the rulebook every object obeys

If objects are rows and attributes are columns, the schema is the table definition. It is itself stored as objects inside AD — two kinds: classSchema objects (one per object type) and attributeSchema objects (one per attribute). The schema says what classes exist, what attributes each class may or must have, what type each attribute is, and whether it's single- or multi-valued.

Classes come in three flavours via objectClassCategory. Structural (value 1) classes can be instantiated — user, group, computer. Abstract (value 2) classes are templates that only pass attributes down to subclasses and can never be created directly — top is the abstract root every class descends from. Auxiliary (value 3) classes are like mix-ins: a bag of attributes you bolt onto a structural class without changing its inheritance.

The schema is shared by the entire forest — every domain controller carries an identical copy. That is what makes schema changes so heavy. There is exactly one DC allowed to write it (the Schema Master FSMO role), and you must be a member of Schema Admins to do it. A schema change is forest-wide and effectively permanent: attributes and classes can be deactivated but never truly deleted before Windows Server 2003+ defunct rules, and even then it's a one-way, high-blast-radius operation. This is why "extending the schema" makes seasoned admins nervous, and why Schema Admins is one of the most dangerous groups in the forest — membership there is forest-level power, on par with Enterprise Admins, and it should normally be empty until the moment a change is being made.

read the schema (don't write it)
# where does the schema live? read it from RootDSE
PS> (Get-ADRootDSE).schemaNamingContext
CN=Schema,CN=Configuration,DC=corp,DC=local

# inspect the definition of the "user" class itself
PS> Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext `
        -LDAPFilter "(&(objectClass=classSchema)(lDAPDisplayName=user))" -Properties *

# who can rewrite the rulebook for the whole forest?
PS> Get-ADGroupMember "Schema Admins"     # should usually be EMPTY

Naming contexts: the database is split into partitions

A single domain controller doesn't hold "Active Directory" as one undivided blob. It holds several naming contexts (also called directory partitions), each with its own replication scope. Three are always present.

The Domain partition (DC=corp,DC=local) holds the users, groups, computers and OUs for that one domain — and is replicated only to DCs within the same domain. The Configuration partition (CN=Configuration,…) holds forest-wide plumbing: the list of sites, subnets, services and the replication topology — replicated to every DC in the forest. The Schema partition (CN=Schema,CN=Configuration,…) holds the rulebook from the last section — also forest-wide. On top of these, application partitions let you store data (classically the DNS zones in DomainDnsZones and ForestDnsZones) with a custom replication scope you choose.

list the partitions on this forest
PS> Get-ADRootDSE | Select-Object namingContexts | %{ $_.namingContexts }
DC=corp,DC=local
CN=Configuration,DC=corp,DC=local
CN=Schema,CN=Configuration,DC=corp,DC=local
DC=DomainDnsZones,DC=corp,DC=local
DC=ForestDnsZones,DC=corp,DC=local

The Global Catalog: one searchable index of the whole forest

Each DC fully knows only its own domain partition. So how do you find a user in a sibling domain, or look up "every account in the forest with an SPN"? The Global Catalog (GC). A GC server keeps a partial, read-only replica of every domain in the forest — not all attributes, but a curated subset called the partial attribute set (PAS) (things like sAMAccountName, userPrincipalName, displayName, group memberships). You query it on port 3268 instead of 389, and a single search reaches the entire forest with no referrals.

The GC matters for security tooling: a forest-wide reconnaissance sweep against 3268 is fast and quiet, and it's how an attacker enumerates accounts across domains they don't even have a foothold in yet. It's also why Universal group membership and UPN-based logon depend on a reachable GC.

search the whole forest via the GC
# point at the Global Catalog port (3268) and the search spans every domain
PS> Get-ADObject -Server dc01.corp.local:3268 `
        -LDAPFilter "(servicePrincipalName=*)" -Properties servicePrincipalName

# which DCs are Global Catalog servers?
PS> Get-ADDomainController -Filter {IsGlobalCatalog -eq $true} | Select Name,Site

FSMO roles: the five jobs only one DC can do at a time

AD replication is multi-master — almost any DC can accept almost any change and it propagates to the rest. But a handful of operations would corrupt the directory if two DCs did them at once, so those are pinned to single role-holders: the Flexible Single Master Operations (FSMO) roles. There are five, split between forest-wide and per-domain.

Forest-wide (one per forest): the Schema Master is the only DC that may write the schema; the Domain Naming Master is the only DC that may add or remove domains. Per-domain (one per domain): the RID Master hands out pools of relative IDs so no two objects get the same SID; the PDC Emulator is the authority for password changes, account lockout, time sync, and is the role attackers most want to control (it's central to the Golden Ticket and many Kerberos abuses); the Infrastructure Master keeps cross-domain object references current in multi-domain forests. Seizing or impersonating these roles is a recurring theme in domain-takeover playbooks.

find the role holders
PS> netdom query fsmo
Schema master            dc01.corp.local
Domain naming master     dc01.corp.local
PDC                      dc01.corp.local
RID pool manager         dc01.corp.local
Infrastructure master    dc01.corp.local

PS> Get-ADForest | Select SchemaMaster,DomainNamingMaster
PS> Get-ADDomain | Select PDCEmulator,RIDMaster,InfrastructureMaster

Naming: DN, RDN, GUID, SID and UPN

An object can be referred to in several ways, and confusing them causes real bugs. The Distinguished Name (DN) is the full path through the tree — CN=alice,OU=Staff,DC=corp,DC=local — unique but fragile: rename or move the object and its DN changes. The leftmost component is the Relative Distinguished Name (RDN), CN=alice, unique only within its parent.

For anything that has to survive renames, AD uses two immutable identifiers. The objectGUID is a 128-bit value assigned at creation that never changes, even if the object is renamed or moved across the forest — it's the safe handle to bind to. The objectSID is the Security Identifier used for access control: it combines a domain identifier with a per-object RID (the part the RID Master hands out). The SID is what lands in your access token and in every ACL; the well-known RID 500 is the built-in Administrator, 512 is Domain Admins. Finally the UPN ([email protected]) is the friendly, email-style logon name resolved via the Global Catalog.

the four names of one account
PS> Get-ADUser alice -Properties distinguishedName,objectGUID,objectSID,userPrincipalName |
        Select distinguishedName,objectGUID,objectSID,userPrincipalName

distinguishedName : CN=alice,OU=Staff,DC=corp,DC=local
objectGUID        : 6f9619ff-8b86-d011-b42d-00c04fc964ff   # never changes
objectSID         : S-1-5-21-1004336348-1177238915-682003330-1108
userPrincipalName : [email protected]                       # RID 1108 ↑

Replication: how the copies stay in sync

Every writable DC holds a copy of its domain partition (plus the forest-wide Configuration and Schema), and changes have to flow between them. AD replication is multi-master and attribute-level: it doesn't ship whole objects, it ships the specific attributes that changed, tagged with version numbers (USNs) and timestamps so the latest write wins and conflicts resolve deterministically. Within a site DCs replicate within seconds; between sites it's scheduled and compressed. The practical takeaway for security work: a change you make on one DC isn't instantly true everywhere, and an attacker who can impersonate a DC can ask for replication of secrets — the DCSync attack abuses exactly this, requesting the replication of password hashes by pretending to be a domain controller.

Security: what attackers read, and what they target

Here's why the data model is the security model. Almost every AD attack begins with reading objects — and by default, any authenticated user can read a huge amount of the directory. Enumeration isn't a vulnerability, it's the intended behaviour, which is what makes AD recon so productive.

The pattern is always the same: query the object database for attributes that reveal weakness, then pivot. SPNs on user accounts give you Kerberoasting targets. userAccountControl bits reveal AS-REP-roastable and unconstrained-delegation accounts. msDS-AllowedToActOnBehalfOfOtherIdentity and msDS-KeyCredentialLink expose RBCD and Shadow Credentials paths. nTSecurityDescriptor ACLs map who can reset whose password or edit whose membership — the graph BloodHound walks. adminCount=1 and memberOf point straight at the high-value accounts. None of this requires an exploit; it requires reading the directory correctly, which is the entire skill this article is about. The defensive answer is to tighten what unprivileged users can read, harden the dangerous attributes (no needless SPNs, no unconstrained delegation, empty Schema Admins, tiered admin), and watch the queries — mass LDAP enumeration and GC sweeps are detectable signals if you're looking.

At a glance

ConceptWhat it isHow you read it
ObjectA row in the directory (user, group, computer, OU, GPO…)Get-ADObject -Properties *
objectClassMulti-valued inheritance chain (top→person→…→user)(objectClass=user)
objectCategorySingle-valued, indexed; precise filtering(objectCategory=person)
SchemaclassSchema + attributeSchema; forest-wide rulebook(Get-ADRootDSE).schemaNamingContext
Naming contextsDomain · Configuration · Schema · app partitionsGet-ADRootDSE namingContexts
Global CatalogPartial forest-wide read replica, port 3268-Server dc:3268
FSMO5 single-master jobs: Schema, Naming, RID, PDC, Infranetdom query fsmo
NamesDN (path) · RDN · GUID (stable) · SID (ACLs) · UPNGet-ADUser -Properties *
Reading AD the right wayBind to objectGUID, not DN, for anything that must survive renames. Filter with (&(objectCategory=person)(objectClass=user)) so computers don't pollute your user counts. Use the Global Catalog (3268) for forest-wide searches and the domain partition (389) for full attributes. And remember every dangerous setting — delegation, pre-auth, SPNs, ACLs — is just an attribute you can query and audit before an attacker does.
TL;DRActive Directory is a hierarchical, replicated LDAP database. Everything — users, computers, groups, OUs, GPOs — is an object defined by a schema (classSchema + attributeSchema, forest-wide, writable only by the Schema Master and Schema Admins). Objects carry attributes, and a few decide security: userAccountControl, servicePrincipalName, memberOf, adminCount, nTSecurityDescriptor and the msDS-* family. The data splits into naming contexts (Domain, Configuration, Schema, app partitions), with the Global Catalog on port 3268 indexing the whole forest and five FSMO roles pinning the operations only one DC may do. Objects are named four ways — DN, GUID, SID, UPN. Learn to read those attributes with Get-ADObject, Get-ADUser -Properties * and dsquery, and the domain stops being a black box — which is precisely why attackers learn it too.
Reactions

Related Articles