Skip to content
Back to the Lab
Red Team

Own One Node, Own the Cluster

A Red Team analysis of how WSFC identities, RBCD, shared secrets, and BloodHound paths turn one compromised node into cluster-wide control.

A Red Team visualization of one compromised Windows cluster node exposing every connected node and virtual identity.

A computer node in BloodHound usually represents one host. Compromise it, and the immediate blast radius appears to be the services, sessions, and credentials attached to that machine. Windows Server Failover Clustering changes that assumption. A cluster node participates in a distributed system whose virtual identities, configuration, and workloads can move between physical servers. What looks like control of one computer can therefore be control of an identity shared operationally across an entire cluster.

SpecterOps researcher Garrett Foster documented this boundary in Clustered Points of Failure. The research followed an Active Directory path that began with WriteAccountRestrictions over a computer object and resource-based constrained delegation. A Kerberos service ticket intended for a cluster identity was accepted on a physical member node even though the normal model says that a ticket is encrypted for the service account named in its SPN. The unexpected behavior was not a broken encryption primitive. It was the result of how Windows Server Failover Clustering distributes the secrets needed to keep virtual network names alive during failover.

This distinction matters. WSFC is not automatically vulnerable, and membership in a cluster does not grant an arbitrary domain user control. The dangerous path appears when an operator can control a relevant computer object, obtain cluster administrative influence, compromise a member node, or exploit excessive permissions held by a Cluster Name Object. The cluster then magnifies the original access because every member must be able to operate the same virtual identities.

The Red Team objective is not merely to place another Owned label in BloodHound. It is to determine whether a computer is part of a larger availability boundary, identify the Cluster Name Object and Virtual Computer Objects attached to it, calculate the real reach of their permissions, and prove the impact without destabilizing production infrastructure.

The Identities Behind a Windows Failover Cluster#

A failover cluster contains physical and logical security principals. The physical members are ordinary domain-joined servers with their own machine accounts. The Cluster Name Object, or CNO, is an Active Directory computer object representing the administrative network name of the cluster. A clustered role that exposes its own client access point can create a Virtual Computer Object, or VCO, with a separate computer account and DNS identity.

Microsoft documents that the failover-cluster creation process can create the CNO automatically. When an administrator adds a clustered role with a client access point, the CNO can create the corresponding VCO. This requires narrowly scoped Active Directory permissions, normally in the organizational unit where the cluster identities reside. Those permissions are operationally necessary, but excessive delegation can turn the CNO into a bridge toward unrelated computer objects or privileged directory operations.

ComponentNormal roleSecurity consequence
Physical nodeRuns the cluster service and hosted workloadCompromise can expose cluster state and virtual identities
CNORepresents the cluster administrative nameIts credentials and directory permissions affect the entire cluster
VCORepresents a clustered client access pointIts identity moves with the hosted role
Cluster databaseReplicates cluster configurationSensitive resource configuration is present across nodes
rhs.exeHosts and monitors cluster resourcesHolds tokens associated with active network-name resources
Hosted workloadProvides SQL, AD FS, SCCM, Exchange, Hyper-V, or file servicesDetermines the final business and Tier Zero impact

A critical modeling error is to evaluate each physical node independently. If AD FS, SCCM, Exchange, or another privileged service runs on the cluster, every node capable of hosting that role belongs to the workload’s security tier. A quiet secondary node is not a lower-value server. It is a dormant execution location with access to the same availability fabric.

WriteAccountRestrictions Is the First Edge, Not the Final Impact#

BloodHound uses the WriteAccountRestrictions edge when a principal can modify a set of account-restriction properties on a target object. One relevant member of that property set is msDS-AllowedToActOnBehalfOfOtherIdentity. Microsoft describes this attribute as the security descriptor used to determine whether a requestor may act on behalf of other identities to services running as the target account.

That attribute is the control point behind resource-based constrained delegation. If an operator controls a principal with a service principal name and can write the target computer’s delegation descriptor, the target can be configured to trust the controlled principal for delegation. Kerberos S4U flows may then produce a service ticket that impersonates another domain identity to a service on the target.

The graph edge does not mean every attempt succeeds. The operator still needs a suitable controlled identity, reachable Kerberos services, an SPN matching the intended service, and an authorization context accepted by the destination. Protected users, delegation restrictions, service behavior, network controls, and host configuration can alter the result. A responsible assessment records those prerequisites before attempting validation.

The unusual cluster behavior begins after a service ticket exists. In a normal host-centric mental model, a ticket encrypted with the CNO’s long-term key should be usable only by the CNO service. A physical node has a different machine-account key. Yet the node currently hosting the CNO resource must answer for the virtual name, and any other node must be able to take over that job without waiting for a new identity to be provisioned.

Why One Ticket Can Follow a Cluster Role#

A Kerberos AP-REQ carries a service ticket encrypted with the destination service’s long-term key. The client does not know that key. The Key Distribution Center and the service do. This is what normally binds the ticket to the account represented by the requested SPN.

WSFC must preserve that model while allowing a virtual service to move. Foster’s research traced the implementation through the cluster database, clusres.dll, cryptographic checkpoints, ResourceData, and the Resource Host Subsystem. The protected CNO and VCO credentials are available to cluster members so whichever node owns the network-name resource can authenticate as that virtual identity. The state retains the known-good password and the proposed or previous password, allowing tickets issued around a password rotation to remain usable.

The security consequence is architectural: the cluster nodes do not share their own machine-account passwords, but they do receive the material required to operate the CNO and VCO identities. Compromise of one member can therefore expose trust that administrators may have assigned only to the logical cluster identity.

flowchart TD
    A["Low-trust principal"] -->|WriteAccountRestrictions| B["Cluster-related computer"]
    B -->|RBCD trust| C["CNO or VCO service ticket"]
    C --> D["Active WSFC node"]
    D --> E["Other cluster nodes"]
    D --> F["Tier Zero workload"]
    G["WSFCHound OpenGraph"] -. maps .-> D
    G -. maps .-> E

The diagram represents a conditional attack path, not a universal property of every cluster. The opening control edge must exist, the Kerberos and service prerequisites must be satisfied, and the affected cluster must host something worth reaching. The value of the model is that it forces the assessor to expand the impact beyond the first computer node.

Why Familiar Remote-Execution Checks Can Mislead#

During the published research, common Impacket workflows behaved unexpectedly against the cluster administrative namespace. Tools such as atexec.py and wmiexec.py normally redirect output through ADMIN$. Against the cluster network name, the exposed administrative shares could instead be limited to ClusterStorage$ and IPC$. Attempts to read output from ADMIN$ returned STATUS_BAD_NETWORK_NAME or appeared to hang.

That result can lead an operator to conclude that the ticket is invalid. In reality, authentication may have succeeded while the tool’s output channel failed. The distinction is important during an authorized assessment: authentication, authorization, execution, and output retrieval are separate stages. A failure at the final stage is not proof that the earlier stages failed.

It is equally important not to improvise on a production cluster. Moving roles, creating scheduled tasks, changing delegation, or touching shared storage can interrupt critical services. Safe validation should begin with read-only discovery, logs, directory permissions, SPN analysis, and a lab reproduction. Production impact should be demonstrated with the least invasive evidence agreed in the rules of engagement.

Standard BloodHound Data Does Not Describe the Whole Cluster#

Active Directory provides useful clues. The CNO has distinctive MSServerCluster SPNs. VCOs expose cluster virtual-server SPNs. Access-control entries can reveal which CNO is permitted to create or modify a VCO. Those relationships still do not reliably enumerate every physical member in every deployment, especially when naming and OU layout are inconsistent.

WSFCHound addresses this gap by enriching an existing BloodHound graph through OpenGraph. The collector introduces a WSFC_Cluster node and four core relationships: ClusterNode, ClusterCNO, ClusterVCO, and ClusterAdmin. The graph can then join ordinary AD privilege edges to the infrastructure boundary that actually receives the impact.

The following queries are read-only and come from the WSFCHound data model:

MATCH (c:WSFC_Cluster)
RETURN c
ORDER BY c.name
MATCH p = (n:Computer)-[:ClusterNode]->(c:WSFC_Cluster)
RETURN p
MATCH p = ()-[:ClusterAdmin]->(c:WSFC_Cluster)
RETURN p

The more useful assessment query connects entry points to the enriched graph:

MATCH p = shortestPath(
  (entry)-[*1..8]->(cluster:WSFC_Cluster)
)
WHERE entry.objectid IN $ownedObjectIds
RETURN p

The exact syntax and parameter handling should be tested against the deployed BloodHound version. OpenGraph schemas evolve independently of the built-in AD graph, so a report should record the WSFCHound commit, BloodHound version, collection time, and credentials used for each data source.

Build a Read-Only Cluster Identity Inventory#

Before extending BloodHound, build an independent inventory that can be compared with the graph. The following PowerShell script queries Active Directory for computer objects carrying cluster-related SPNs, records their delegation state, expands their access-control entries, and writes deterministic JSON. It does not modify an object, request a Kerberos ticket, connect to a cluster, or perform exploitation.

Run it from an authorized administrative workstation with the ActiveDirectory module. The account needs permission to read the target directory objects and their security descriptors.

param(
    [string]$SearchBase,
    [string]$OutputPath = ".\wsfc-ad-inventory.json"
)

$ErrorActionPreference = "Stop"
Import-Module ActiveDirectory

$root = Get-ADRootDSE
$domain = Get-ADDomain

if ([string]::IsNullOrWhiteSpace($SearchBase)) {
    $SearchBase = $domain.DistinguishedName
}

$ldapFilter = "(|(servicePrincipalName=MSServerCluster/*)(servicePrincipalName=MSServerClusterMgmtAPI/*)(servicePrincipalName=MSClusterVirtualServer/*))"

$queryParams = @{
    LDAPFilter = $ldapFilter
    SearchBase = $SearchBase
    Properties = @(
        "servicePrincipalName"
        "msDS-AllowedToActOnBehalfOfOtherIdentity"
        "whenCreated"
        "whenChanged"
        "pwdLastSet"
        "userAccountControl"
        "dNSHostName"
        "objectSID"
        "objectGUID"
    )
}

$computers = Get-ADComputer @queryParams

$records = foreach ($computer in $computers) {
    $spns = @($computer.servicePrincipalName)
    $types = [System.Collections.Generic.List[string]]::new()

    if ($spns -match "^MSServerCluster/") {
        $types.Add("CNO")
    }

    if ($spns -match "^MSClusterVirtualServer/") {
        $types.Add("VCO")
    }

    if (
        ($spns -match "^MSServerClusterMgmtAPI/") -and
        -not ($spns -match "^MSClusterVirtualServer/")
    ) {
        $types.Add("ClusterRelated")
    }

    $acl = Get-Acl -Path ("AD:\" + $computer.DistinguishedName)
    $aces = foreach ($ace in $acl.Access) {
        [ordered]@{
            identity = $ace.IdentityReference.Value
            accessControlType = $ace.AccessControlType.ToString()
            rights = $ace.ActiveDirectoryRights.ToString()
            objectType = $ace.ObjectType.Guid
            inheritedObjectType = $ace.InheritedObjectType.Guid
            isInherited = [bool]$ace.IsInherited
            inheritanceType = $ace.InheritanceType.ToString()
        }
    }

    $rbcdRaw = $computer.'msDS-AllowedToActOnBehalfOfOtherIdentity'
    $rbcdSddl = $null

    if ($null -ne $rbcdRaw) {
        $descriptor = [System.DirectoryServices.ActiveDirectorySecurity]::new()
        $descriptor.SetSecurityDescriptorBinaryForm([byte[]]$rbcdRaw)
        $rbcdSddl = $descriptor.GetSecurityDescriptorSddlForm("All")
    }

    [ordered]@{
        name = $computer.Name
        samAccountName = $computer.SamAccountName
        distinguishedName = $computer.DistinguishedName
        dnsHostName = $computer.dNSHostName
        objectGuid = $computer.ObjectGUID.Guid
        objectSid = $computer.ObjectSID.Value
        inferredTypes = @($types | Sort-Object -Unique)
        servicePrincipalNames = @($spns | Sort-Object)
        rbcdConfigured = $null -ne $rbcdRaw
        rbcdSecurityDescriptor = $rbcdSddl
        whenCreatedUtc = $computer.whenCreated.ToUniversalTime().ToString("o")
        whenChangedUtc = $computer.whenChanged.ToUniversalTime().ToString("o")
        passwordLastSetFileTime = [long]$computer.pwdLastSet
        userAccountControl = [int]$computer.userAccountControl
        accessControlEntries = @($aces)
    }
}

$result = [ordered]@{
    generatedAtUtc = [DateTime]::UtcNow.ToString("o")
    domain = $domain.DNSRoot
    searchBase = $SearchBase
    objectCount = @($records).Count
    objects = @($records | Sort-Object distinguishedName)
}

$json = $result | ConvertTo-Json -Depth 10
$json | Set-Content -Path $OutputPath -Encoding utf8
$json

The LDAP filter deliberately collects candidates instead of declaring every result to be a confirmed cluster relationship. SPNs reveal roles, but the mapping between CNO, VCO, and physical nodes may require cluster APIs or WSFCHound collection. Preserve that uncertainty in the evidence.

Create a hash immediately after collection:

.\Get-WsfcAdInventory.ps1 -OutputPath C:\Evidence\wsfc-ad-inventory.json
Get-FileHash -Path C:\Evidence\wsfc-ad-inventory.json -Algorithm SHA256

The result should be reviewed for three conditions. First, find a non-Tier Zero principal with control rights over a cluster-related object. Second, identify CNOs with create, reset-password, generic write, ownership, or DACL rights outside their intended cluster OU. Third, locate existing msDS-AllowedToActOnBehalfOfOtherIdentity values and verify every trustee against an approved delegation inventory.

An ACE is not automatically exploitable. Effective control depends on group nesting, inherited and explicit denies, object-specific GUIDs, owner behavior, AdminSDHolder protection, and whether the principal can authenticate from the assessment position. The collector exposes the raw evidence so those decisions remain reviewable.

Detect the Delegation Change Before the Ticket Is Used#

Directory Service event 5136 records object modifications when the appropriate audit policy and SACL are enabled. For RBCD, the high-value attribute is msDS-AllowedToActOnBehalfOfOtherIdentity. A change to this binary security descriptor is unusual in many environments, but cluster provisioning, migration, and legitimate delegation management can create expected events.

The following Microsoft Sentinel query isolates the mutation and retains the actor, target, originating domain controller, operation, and raw value:

let Lookback = 7d;
let ApprovedActors = dynamic([
    "CONTOSO\\cluster-provisioning",
    "CONTOSO\\identity-automation"
]);
SecurityEvent
| where TimeGenerated >= ago(Lookback)
| where EventID == 5136
| extend AttributeName = tostring(EventData.AttributeLDAPDisplayName)
| extend AttributeValue = tostring(EventData.AttributeValue)
| extend OperationType = tostring(EventData.OperationType)
| extend ObjectDn = tostring(EventData.ObjectDN)
| extend ObjectGuid = tostring(EventData.ObjectGUID)
| extend Actor = strcat(SubjectDomainName, "\\", SubjectUserName)
| where AttributeName =~ "msDS-AllowedToActOnBehalfOfOtherIdentity"
| extend ApprovedActor = Actor in~ (ApprovedActors)
| project TimeGenerated, Computer, Actor, ApprovedActor, ObjectDn, ObjectGuid, OperationType, AttributeValue
| order by TimeGenerated desc

Event 5136 shows the directory mutation, not the complete attack. Enrich the target DN with the inventory and BloodHound graph. Raise severity when the target is a CNO, VCO, cluster node, or system that hosts an administrative session. Add context when the actor is low privilege, the source workstation is unexpected, or the modification is followed by Kerberos activity for a cluster SPN.

Detection should also watch the virtual identities themselves. CNO and VCO authentication can legitimately originate from multiple members, but the source pool should be finite. Build an approved map of each CNO and VCO to its physical cluster nodes. A successful authentication from outside that pool is more meaningful than a generic alert for a machine account logon.

let ClusterIdentitySources = datatable(Identity:string, AllowedSources:dynamic)
[
    "CONTOSO\\SQLCLUSTER$", dynamic(["SQLNODE01", "SQLNODE02"]),
    "CONTOSO\\ADFSCLUSTER$", dynamic(["ADFSNODE01", "ADFSNODE02"])
];
SecurityEvent
| where EventID in (4624, 4769)
| extend Identity = case(
    EventID == 4624,
    strcat(TargetDomainName, "\\", TargetUserName),
    strcat(AccountDomain, "\\", Account)
)
| extend Source = toupper(coalesce(WorkstationName, Computer))
| join kind=inner ClusterIdentitySources on Identity
| where Source !in~ (AllowedSources)
| project TimeGenerated, EventID, Identity, Source, AllowedSources, IpAddress, ServiceName, LogonType

This is a template, not a drop-in universal rule. Event fields vary between connectors, Event 4769 is emitted on domain controllers, and Computer may identify the log collector rather than the authentication source. Validate the parsed fields with known cluster failovers before enabling an alert.

The Most Important Signals Form a Sequence#

No single event proves cluster takeover. A reliable analytic correlates a change in effective control with authentication and cluster operations.

StageEvidenceInterpretation
DiscoveryLDAP queries for cluster SPNs and CNO/VCO objectsMay be administration, assessment, or adversary reconnaissance
Control changeEvent 5136 on the RBCD attribute or object DACLEstablishes a new delegation or permission path
Ticket activityEvent 4769 for CNO/VCO SPNsShows use of the virtual service identity
Host accessEvent 4624, service creation, WMI, or task activityConnects the ticket to execution behavior
Cluster actionFailoverClustering operational logs and ownership changesShows the role moving or administrative APIs being used
ExpansionSame virtual identity appears across multiple nodesIndicates cluster-wide use rather than one-host access

The correlation window should be long enough to survive ticket caching and delayed operator action. Directory changes may precede ticket use by minutes or hours. Conversely, an adversary who inherits an old unauthorized RBCD value may generate no fresh 5136 event. Continuous posture checks are therefore necessary alongside event detection.

Host telemetry adds the process context. SpecterOps found that rhs.exe hosts resource logic and obtains tokens for CNO and VCO identities. The existence of those tokens on legitimate cluster nodes is expected behavior. An alert should focus on abnormal access to the process, cluster credential material, HKLM\Cluster resource configuration, or execution descendants that do not match the established baseline.

Monitor cluster ownership changes as context rather than treating every failover as malicious. Maintenance, node health, storage interruptions, and patching routinely move roles. The suspicious sequence is a new delegation or administrative path followed by virtual-identity authentication, remote execution, and role movement initiated from an unapproved principal or workstation.

Safe Validation in an Authorized Lab#

Build a disposable forest with at least one domain controller, three Windows Server cluster nodes, and a noncritical clustered file-server role. Keep it isolated from production trusts, DNS forwarding, backup networks, and identity synchronization. Snapshot the domain controller and every cluster node before changing delegation or ownership.

The validation should start without exploitation. Collect SharpHound data, run WSFCHound, import the OpenGraph payload, and verify that CNO, VCO, node, and administrator relationships are visible. Run the PowerShell inventory and compare its SPN and ACL data with the graph. Trigger a normal failover and document which nodes generate CNO/VCO authentication so the detection baseline reflects real behavior.

Next, create a synthetic low-privilege principal and a synthetic target computer. Delegate only the exact WriteAccountRestrictions condition under study. Confirm that BloodHound renders the intended edge and that a benign change to the RBCD attribute produces event 5136. Remove the value immediately and verify that the graph and directory inventory return to the original state.

If the engagement requires proof of the full path, define a written stop condition and use only synthetic identities. Do not target a live Tier Zero session or production workload. Capture Kerberos, directory, host, and FailoverClustering logs; prove which stage succeeded; then restore the entire lab. The finding is stronger when it identifies the permission and trust boundary than when it contains only a screenshot of code execution.

The evidence package should include the graph path, effective ACL expansion, SPN inventory, CNO-to-VCO mapping, member-node list, hosted roles, event timestamps, tool versions, and cryptographic hashes of exported data. This separates a reproducible finding from a claim based only on transient shell access.

Hardening the Cluster as One Security Boundary#

The first control is tiering. Determine the highest-value workload that can run on the cluster. Assign every node, CNO, VCO, cluster administrator, management endpoint, backup path, and automation account to that tier. A cluster hosting AD FS or SCCM is not a collection of ordinary member servers.

Separate workloads that belong to different security tiers. High availability is not a justification for placing a Tier Zero identity service and a lower-trust application on the same administrative fabric. Shared administrators, storage, service accounts, backup tooling, and cluster nodes create paths in both directions.

Review the CNO’s directory rights. Microsoft guidance supports narrowly scoped computer-object creation in the OU intended for cluster identities. Broad GenericAll, GenericWrite, WriteDacl, reset-password, or create-child rights at the domain root or across unrelated OUs should be removed. Prestaged CNO and VCO objects should have documented owners and an approved permission baseline.

Remove unauthorized RBCD descriptors and investigate how they appeared. Do not clear an unfamiliar value without determining whether a production service depends on it. Export the original security descriptor, identify every SID, validate the application owner, and use change control for remediation.

Protect cluster administration endpoints and monitor their use. Limit WinRM, WMI, SMB, RPC, and Failover Cluster Manager access to designated management networks. Require privileged administrators to use hardened workstations. Avoid interactive Tier Zero logons on lower-trust cluster nodes because a single exposed session can turn a cluster path into domain-wide escalation.

Protect the cluster database and related registry paths as credential-bearing assets. Security products, monitoring agents, backup operators, and troubleshooting accounts should receive only the access they require. An account permitted to inspect sensitive cluster internals can cross an identity boundary even when it has no obvious Domain Admin membership.

Finally, keep two inventories. The configuration management database describes what should exist, while WSFCHound and Active Directory collection describe what does exist. Differences between them are findings. An unregistered member, unexpected cluster administrator, new VCO, unexplained delegation descriptor, or virtual identity authenticating outside its node pool deserves investigation.

Report the Blast Radius, Not Only the Primitive#

A useful Red Team report should not stop at “RBCD was possible.” It should identify the starting principal, the exact effective permission, the target object, the cluster relationship, the virtual identities exposed, every physical member, the hosted workloads, and the highest-trust session or service reachable through the path.

Risk depends on what the cluster hosts. A file cluster may expose sensitive data. A Hyper-V cluster may expose many workloads. An Exchange database availability group can intersect with historically privileged directory permissions. An SCCM cluster can influence managed endpoints. An AD FS or AD CS deployment can affect authentication beyond the original Windows domain.

SpecterOps reported clusters in 82 percent of the BloodHound Enterprise customer environments it examined, with an average of 141 clusters among those environments. That is vendor telemetry, not a representative measurement of all enterprises. It still shows why cluster relationships deserve first-class attack-path modeling rather than an appendix in a server inventory.

Document prerequisites with the same care as impact. A graph edge may be traversable in theory but blocked by protected-user policy, SPN mismatch, network segmentation, service configuration, or missing access to the cluster API. Mark each prerequisite as observed, inferred, untested, or absent. That makes the final risk rating defensible.

Conclusion#

Windows Server Failover Clustering turns availability into a distributed identity boundary. CNO and VCO credentials must follow their resources, and cluster members must retain enough state to keep Kerberos authentication working through failover and password rotation. That design means a path into one member can carry more authority than its hostname suggests.

The decisive Red Team question is not “Can this computer be compromised?” It is “What shared identity and workload can this computer operate?” BloodHound answers the directory portion, WSFCHound adds the hidden cluster relationships, and host plus cluster telemetry verifies where those identities are active.

Assess the path safely. Inventory cluster SPNs and ACLs, model CNOs and VCOs, inspect WriteAccountRestrictions and RBCD, baseline authentication sources, and correlate directory changes with Kerberos and cluster events. When one node belongs to a Tier Zero cluster, report it as a path to the cluster, not as an isolated machine finding.

References#

Clustered Points of Failure

WSFCHound

WriteAccountRestrictions

OpenGraph Graph Data Overview

Failover Cluster Accounts Overview

Prestage Cluster Computer Objects in Active Directory Domain Services

Cluster Shared Volumes Overview

ms-DS-Allowed-To-Act-On-Behalf-Of-Other-Identity Attribute

Steal or Forge Kerberos Tickets