Source: netwrix/pingcastle @
d0f3425
How It Works
PingCastle is an Active Directory security assessment tool that evaluates domain configurations against ~200 built-in risk rules, producing a scored HTML report. The philosophy is “80% of security findings in 20% of the time” — it doesn’t enumerate everything about a domain but instead focuses on the highest-impact misconfigurations: stale accounts, trust issues, privileged group weaknesses, and anomalies.
The tool operates in several modes: healthcheck (the primary AD assessment), cartography (multi-domain trust mapping), Entra ID (cloud) assessment, individual scanners (ZeroLogon, MS17-010, BlueKeep, spooler, etc.), and data export. Each mode follows the same pattern: connect to AD via ADWS/LDAP/RPC, collect data into a typed data model, run rules against it, score the results, and generate an HTML report with embedded JavaScript for interactivity.
PingCastle also supports a commercial/agent deployment model. It can retrieve licenses and configuration from an API endpoint, upload results centrally, and restrict scanning to licensed domains — the open-source tier is free for internal use but has some feature limitations (e.g., honeypot data is capped in the basic edition).
Architecture
The solution has three projects:
| Project | Framework | Purpose |
|---|---|---|
| PingCastle | net8.0-windows | Main executable — AD interaction, healthcheck engine, scanners, reports |
| PingCastleCommon | net8.0 | Shared library — data models, rule framework, cloud/Graph abstractions, templates |
| PingCastleAutoUpdater | — | Agent-side auto-update utility |
Key directories in the main project:
ADWS/— AD connection layer (ADWS via WCF, LDAP, domain locator)RPC/— Hand-rolled RPC clients for NRPC, LSARPC, SAMR, DCOM, Spooler (MIDL format strings for x86/x64)Healthcheck/— TheHealthcheckAnalyzer— the 6700-line core that collects all AD dataScanners/— 17 standalone vulnerability scannersReport/— HTML report generators (map builder, network/Hilbert map)Cloud/— Entra ID assessment via MS Graph APIGraph/— Compromise graph construction and reporting
The PingCastleCommon library holds the data models (HealthcheckData, HealthCheckCloudData), the rule engine (RuleSet<T>, RuleBase<T>), and ~211 rule implementations across AD and Cloud subdirectories.
The Spine
Entry point: Program.Main() initializes DI, loads licenses, parses command-line arguments, and dispatches to Tasks.
Program.Main(args)
→ InitializeServiceProvider() // DI + factory registry
→ License validation
→ ParseCommandLine(args) // maps flags → PossibleTasks
→ actions[action].Invoke() // delegates to Tasks methods
→ Tasks.CompleteTasks() // email upload, API submission
Healthcheck flow (the primary path):
Tasks.AnalysisTask<T>()
→ HealthcheckAnalyzer.PerformAnalyze(parameters)
→ ADWebService(server, port, credential) // connects via ADWS or LDAP
→ GenerateGeneralData() // domain info, schema, forest
→ GenerateUserData() // users, password stats
→ GenerateComputerData() // machines, OS breakdown
→ GenerateTrustData() // trust relationships
→ GeneratePrivilegedGroupAndPermissionsData()
→ GenerateDelegationData() // delegation/GPO
→ GeneratePKIData() // certificate authorities
→ GenerateDomainControllerData() // DCs + null session + RPC
→ ... (exchange, DNS, WSUS, anomalies)
→ RuleSet<HealthcheckData>.ComputeRiskRules(data) // run all 188+ rules
→ scores accumulated into GlobalScore, StaleObjectsScore, etc.
Scanner flow is simpler: Tasks.ScannerTask() → PingCastleFactory.LoadScanner(type) → IScanner.Initialize() → IScanner.Export().
Cloud flow: Tasks.AnalyzeTask() → Cloud.Analyzer → MS Graph API calls → RuleSet<HealthCheckCloudData>.ComputeRiskRules().
Key Patterns
Attribute-driven rule system. Each rule is a class decorated with attributes that declare its risk model, scoring, maturity level, and compliance framework mappings:
[RuleModel("A-AdminSDHolder", RiskRuleCategory.Anomalies, RiskModelCategory.TemporaryAdmins)]
[RuleComputation(RuleComputationType.TriggerOnThreshold, 50, Threshold: 50, Order: 1)]
[RuleComputation(RuleComputationType.TriggerOnThreshold, 45, Threshold: 45, Order: 2)]
[RuleANSSI("R40", "paragraph.3.6.3.1")]
[RuleMitreAttackMitigation(MitreAttackMitigation.PrivilegedAccountManagement)]
public class HeatlcheckRuleAnomalyAdminSDHolder : RuleBase<HealthcheckData>
{
protected override int? AnalyzeDataNew(HealthcheckData healthcheckData) { ... }
}Scoring uses RuleComputationType — TriggerOnThreshold, TriggerOnPresence, PerDiscover, TriggerIfLessThan — allowing graduated point assignments based on finding severity/count. Rules also carry STIG, ANSSI, DUR-ANSSI, and MITRE ATT&CK cross-references as attributes.
Multi-protocol AD access. ADWebService tries ADWS (WCF/NetTcp) first on Windows, falls back to LDAP. On Unix it uses LDAP only. The IADConnection interface abstracts over ADWSConnection, LDAPConnection, and WindowsFileConnection. RPC protocols (NRPC, LSARPC, SAMR, DCOM, Spooler) are implemented with hand-written MIDL stubs.
Data collection then analysis separation. The HealthcheckAnalyzer populates a HealthcheckData object (a 2400-line POCO with ~200 properties) entirely before any rules run. This means rules are pure functions of the data model — they never touch AD directly.
Plugin discovery via assembly scanning. PingCastleFactoryRegistry registers assemblies, and PingCastleFactory scans them for IScanner and IExport implementations. Rules are discovered similarly via RuleSet<T> which reflects over the assembly for RuleBase<T> subclasses.
Template-based HTML reports. Reports use embedded gzipped CSS/JS (Bootstrap, vis.js, jQuery) with a responsive template. The TemplateManager handles decompression. Reports include interactive network maps, Hilbert space-filling curve visualizations, and risk control matrices.
Non-Obvious Details
RPC is hand-rolled. The
PingCastle/RPC/directory contains raw MIDL format strings for x86 and x64 architectures, implementing NRPC, SAMR, LSARPC, and Spooler protocols directly. This avoids external dependencies but means the code handles platform-specific RPC marshalling manually.
License is embedded as a base64 string. The "basic edition" license is compiled into
Program.csas a constant (BasicEditionLicense). This allows the tool to run out-of-the-box without configuration, with commercial features gated by a license loaded from a.configfile or retrieved via API.
Thread.Abort is used in carto mode. The cartography feature spawns 100 threads with a
BlockingQueueand callsThread.Abort()in thefinallyblock for cleanup. This is a deprecated .NET pattern and can cause resource corruption.
Honeypot accounts are limited in the basic edition.
LimitHoneyPot = truein the basic license restricts detection of decoy accounts — a deliberate feature gate for the open-source tier.
Unix support exists but is constrained. On Unix platforms, PingCastle falls back to LDAP-only connections (no ADWS) and skips null session and RPC-based checks entirely. The main executable targets
net8.0-windowsspecifically.
HealthcheckAnalyzer is 6700 lines. This single file contains all AD data collection logic. While well-organized with clearly named
Generate*methods, it's a god-class that would benefit from decomposition.
Framework cross-references are deep. Rules carry STIG, ANSSI DUR-ANSSI, MITRE ATT&CK, and CERT-FR references as attributes. The
RuleAttribute.csfile alone is 589 lines just defining these cross-reference types.
Assessment
Strengths:
- Mature rule engine. 211 rules with graduated scoring, compliance framework mapping, and a clean attribute-driven model. Adding a new rule is a single class file with attributes.
- Multi-protocol coverage. ADWS, LDAP, and raw RPC protocols (NRPC, SAMR, LSARPC, DCOM, Spooler) give it access to data layers that LDAP-only tools can’t reach.
- Practical output. HTML reports with interactive visualizations are immediately useful for non-technical stakeholders. XML/JSON export enables pipeline integration.
- Dual cloud/on-prem. The same architecture handles both Active Directory and Entra ID assessments, sharing the rule framework.
- Well-structured DI migration. The codebase is mid-way through adopting Microsoft.Extensions.DependencyInjection — service registration exists alongside manual instantiation with fallback paths.
Concerns:
- God-class analyzer.
HealthcheckAnalyzer.csat 6700 lines is difficult to navigate and test in isolation. The data collection methods should be extracted into focused classes. - Windows-only primary path. The main executable targets
net8.0-windowswith Windows Forms references. WhilePingCastleCommonis cross-platform, the primary use case requires Windows. - Thread safety in carto. Using
Thread.Abort()and a 100-thread pool for multi-domain scanning is fragile. ModernTask/asyncpatterns would be safer and more efficient. - License coupling. License checks are interleaved throughout the execution path, and the embedded base64 license creates an awkward trust boundary for security-conscious users.
- No test project visible. The repository lacks a test suite, which is concerning for a security assessment tool where correctness directly impacts trustworthiness of results.
Recommendations for contributors:
- New rules: extend
RuleBase<HealthcheckData>inPingCastleCommon/Healthcheck/Rules/, use the[RuleModel]and[RuleComputation]attributes. Follow existing naming convention (HeatlcheckRule{Category}{Finding}). - New scanners: implement
IScanner(or extendScannerBase) inPingCastle/Scanners/. They’re auto-discovered via assembly scanning. - Report changes: modify templates in
PingCastleCommon/Template/and generators inPingCastle/Report/.
Related
- analyzing-sharphound — Another AD security tool; compares PingCastle’s rule-based scoring approach to BloodHound’s graph-based attack path analysis.