the_open_door
coffee
ESEN
← Back to index

RID cycling

ATT&CKT1087.002

In Active Directory every account (user, computer or group) has a SID of the form S-1-5-21-<domain-id>-<RID>, where the RID (Relative Identifier) is the number that distinguishes that account within the domain. RIDs are assigned predictably: 500 is the built-in Administrator, 501 the guest, 502 the krbtgt, and from 1000 onward they are handed out sequentially to created accounts. RID cycling (or RID brute forcing) walks that range asking the domain controller, over SMB/MS-RPC (LSAR/SAMR), which name maps to each RID, thereby rebuilding the user list.

The trick is that, in many configurations, this probe works with a guest or null session, with no valid credentials (see smb-guest-enum). When direct user enumeration (--users, enum4linux --U) is blocked but the guest account is not disabled, RID brute is usually still open because it resolves SID to name through a different path.

Mechanism

netexec/crackmapexec walks the RID range in one go; with a null or guest session:

netexec smb <dc> -u guest -p '' --rid-brute
netexec smb <dc> -u '' -p '' --rid-brute 20000       # widening the default range (4000)

The output lists each RID with its account and type (SidTypeUser, SidTypeGroup, SidTypeAlias). Filtering by SidTypeUser yields the domain user list:

498: <DOM>\Enterprise Read-only Domain Controllers (SidTypeGroup)
500: <DOM>\Administrator (SidTypeUser)
1104: <DOM>\jsmith (SidTypeUser)
1105: <DOM>\mholliday (SidTypeUser)

impacket-lookupsid does the same over SAMR/LSAR and takes the range as its last argument; rpcclient allows it by hand, combining lookupnames, enumdomusers and queryuser:

impacket-lookupsid.py 'guest@<dc>' 20000            # with creds: <domain>/<user>:<pass>@<dc>
impacket-lookupsid.py '<domain>/<user>:<pass>@<dc>' | grep SidTypeUser
rpcclient -U "" -N <dc> -c "enumdomusers"           # if direct enum is open
rpcclient -U "" -N <dc> -c "queryuser 0x457"        # detail of a specific RID (hex)
enum4linux-ng -R <dc>                                 # built-in RID cycling, YAML/JSON output

With enum4linux-ng the sweep is built in (-R) and the output comes normalized. That listing is raw material for a password-spraying or for hunting hints in the description field. The list also includes machine accounts (HOST$), valid for other attacks (RBCD, coercion, Delegate to me).

Requirements

SMB/RPC access to the DC and a session that resolves SID to name: usually null or guest, though any valid credential works and tends to return more. It does not require the direct user-enumeration right, which is why it works where --users fails.

How to detect it. The sweep resolves SID to name in bulk, and that pattern is the signature. On the DC, with SAM access auditing enabled, Event ID 4661 (a handle to an object was requested) appears in series, along with SamrLookupIdsInDomain/LsarLookupSids operations over consecutive RIDs (1000, 1001, 1002…). It is usually preceded by an anonymous or guest 4624 (see smb-guest-enum). The key is not one lone query (legitimate) but hundreds sequential from a single source in seconds. On the wire, MS-RPC traffic to \samr/\lsarpc with incremental lookups is equally recognizable.

Defence. Disable the guest account (net user guest /active:no) and restrict anonymous/guest access to SAMR (RestrictAnonymous, RestrictAnonymousSAM = 1 in the Domain Controllers GPO). Windows Server 2019/2022 already restricts it by default, but many migrations leave it open. It relies on guest/null enumeration and feeds password-spraying and user enumeration.

Toolsrpcclientenum4linuxlookupsid.pyenum4linux-ng

Machines where it's used