identity access management

Performing Privileged Account Discovery

Discover and inventory all privileged accounts across enterprise infrastructure including domain admins, local admins, service accounts, database admins, cloud IAM roles, and application admin accounts. Covers automated scanning, risk classification, and onboarding to PAM.

access-controldiscoveryiamidentityinventoryprivileged-access
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Discover and inventory all privileged accounts across enterprise infrastructure including domain admins, local admins, service accounts, database admins, cloud IAM roles, and application admin accounts. Covers automated scanning, risk classification, and onboarding to PAM.

When to Use

  • When conducting security assessments that involve performing privileged account discovery
  • When following incident response procedures for related security events
  • When performing scheduled security testing or auditing activities
  • When validating security controls through hands-on testing

Prerequisites

  • Familiarity with identity access management concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Objectives

  • Implement comprehensive performing privileged account discovery capability
  • Establish automated discovery and monitoring processes
  • Integrate with enterprise IAM and security tools
  • Generate compliance-ready documentation and reports
  • Align with NIST 800-53 access control requirements

Security Controls

Control NIST 800-53 Description
Account Management AC-2 Lifecycle management
Access Enforcement AC-3 Policy-based access control
Least Privilege AC-6 Minimum necessary permissions
Audit Logging AU-3 Authentication and access events
Identification IA-2 User and service identification

Verification

  • Implementation tested in non-production environment
  • Security policies configured and enforced
  • Audit logging enabled and forwarding to SIEM
  • Documentation and runbooks complete
  • Compliance evidence generated
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.9 KB

Privileged Account Discovery — API Reference

ldap3 Library

Python LDAP client used for querying Active Directory.

Connection Setup

from ldap3 import Server, Connection, ALL, SUBTREE
server = Server("ldaps://dc.example.com", get_info=ALL, use_ssl=True)
conn = Connection(server, user="DOMAIN\user", password="pass", auto_bind=True)

Key Search Filters

Purpose LDAP Filter
Privileged group (&(objectClass=group)(cn=Domain Admins))
Nested membership (memberOf:1.2.840.113556.1.4.1941:=<group_dn>)
Service accounts (&(objectClass=user)(servicePrincipalName=*))
AdminCount flag (&(objectClass=user)(adminCount=1))
Disabled accounts (&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))

LDAP Matching Rules (OIDs)

  • 1.2.840.113556.1.4.1941LDAP_MATCHING_RULE_IN_CHAIN (recursive group membership)
  • 1.2.840.113556.1.4.803LDAP_MATCHING_RULE_BIT_AND (bitwise AND for UAC flags)

UserAccountControl Flags

Flag Hex Description
ACCOUNTDISABLE 0x0002 Account is disabled
PASSWD_NOTREQD 0x0020 No password required
DONT_EXPIRE_PASSWORD 0x10000 Password never expires
NOT_DELEGATED 0x100000 Account is sensitive for delegation

Default Privileged Groups

Domain Admins, Enterprise Admins, Schema Admins, Administrators, Account Operators, Backup Operators, Server Operators, Print Operators, DnsAdmins.

Output Schema

{
  "report": "privileged_account_discovery",
  "domain": "DC=example,DC=com",
  "privileged_groups": [{"group": "Domain Admins", "member_count": 5, "members": []}],
  "service_accounts": [{"username": "svc_sql", "spns": ["MSSQLSvc/db01:1433"]}],
  "admin_count_users": ["oldadmin", "testuser"]
}

CLI Usage

python agent.py --server ldaps://dc.example.com --username "DOMAIN\analyst" --password "pass" --output report.json

Scripts 1

agent.py5.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Privileged Account Discovery agent — enumerates privileged accounts across
Active Directory using ldap3 and flags shadow admin paths."""

import argparse
import json
import sys
from datetime import datetime
from pathlib import Path

try:
    from ldap3 import Server, Connection, ALL, SUBTREE
except ImportError:
    print("Install ldap3: pip install ldap3", file=sys.stderr)
    sys.exit(1)


PRIVILEGED_GROUPS = [
    "Domain Admins", "Enterprise Admins", "Schema Admins",
    "Administrators", "Account Operators", "Backup Operators",
    "Server Operators", "Print Operators", "DnsAdmins",
]


def connect_ldap(server_url: str, username: str, password: str, use_ssl: bool = True) -> Connection:
    """Establish LDAP connection."""
    srv = Server(server_url, get_info=ALL, use_ssl=use_ssl)
    conn = Connection(srv, user=username, password=password, auto_bind=True)
    return conn


def get_domain_dn(conn: Connection) -> str:
    """Extract domain distinguished name from RootDSE."""
    return conn.server.info.other.get("defaultNamingContext", [""])[0]


def enumerate_privileged_groups(conn: Connection, base_dn: str) -> list[dict]:
    """Find all privileged group memberships recursively."""
    results = []
    for group_name in PRIVILEGED_GROUPS:
        search_filter = f"(&(objectClass=group)(cn={group_name}))"
        conn.search(base_dn, search_filter, search_scope=SUBTREE,
                    attributes=["cn", "member", "distinguishedName"])
        for entry in conn.entries:
            members = entry.member.values if hasattr(entry.member, "values") else []
            results.append({
                "group": str(entry.cn),
                "dn": str(entry.distinguishedName),
                "member_count": len(members),
                "members": [str(m) for m in members],
            })
    return results


def find_nested_memberships(conn: Connection, base_dn: str, group_dn: str) -> list[str]:
    """Resolve nested group memberships using LDAP_MATCHING_RULE_IN_CHAIN."""
    search_filter = f"(memberOf:1.2.840.113556.1.4.1941:={group_dn})"
    conn.search(base_dn, search_filter, search_scope=SUBTREE,
                attributes=["sAMAccountName", "objectClass"])
    return [str(e.sAMAccountName) for e in conn.entries]


def find_service_accounts(conn: Connection, base_dn: str) -> list[dict]:
    """Discover service accounts via servicePrincipalName."""
    search_filter = "(&(objectClass=user)(servicePrincipalName=*))"
    conn.search(base_dn, search_filter, search_scope=SUBTREE,
                attributes=["sAMAccountName", "servicePrincipalName",
                             "adminCount", "whenChanged", "userAccountControl"])
    accounts = []
    for entry in conn.entries:
        uac = int(str(entry.userAccountControl)) if hasattr(entry, "userAccountControl") else 0
        accounts.append({
            "username": str(entry.sAMAccountName),
            "spns": [str(s) for s in entry.servicePrincipalName.values],
            "admin_count": str(entry.adminCount) if hasattr(entry, "adminCount") else "0",
            "password_never_expires": bool(uac & 0x10000),
            "last_changed": str(entry.whenChanged),
        })
    return accounts


def find_admin_count_users(conn: Connection, base_dn: str) -> list[str]:
    """Find users with adminCount=1 (shadow admins or orphaned flags)."""
    search_filter = "(&(objectClass=user)(adminCount=1))"
    conn.search(base_dn, search_filter, search_scope=SUBTREE,
                attributes=["sAMAccountName"])
    return [str(e.sAMAccountName) for e in conn.entries]


def generate_report(server_url: str, username: str, password: str, use_ssl: bool) -> dict:
    """Run full privileged account discovery and build JSON report."""
    conn = connect_ldap(server_url, username, password, use_ssl)
    base_dn = get_domain_dn(conn)

    priv_groups = enumerate_privileged_groups(conn, base_dn)
    svc_accounts = find_service_accounts(conn, base_dn)
    admin_count_users = find_admin_count_users(conn, base_dn)
    total_priv = sum(g["member_count"] for g in priv_groups)

    conn.unbind()
    return {
        "report": "privileged_account_discovery",
        "generated_at": datetime.utcnow().isoformat() + "Z",
        "domain": base_dn,
        "privileged_groups": priv_groups,
        "total_privileged_members": total_priv,
        "service_accounts": svc_accounts,
        "admin_count_users": admin_count_users,
        "summary": {
            "privileged_groups_found": len(priv_groups),
            "service_accounts": len(svc_accounts),
            "admin_count_flagged_users": len(admin_count_users),
        },
    }


def main():
    parser = argparse.ArgumentParser(description="Privileged Account Discovery Agent")
    parser.add_argument("--server", required=True, help="LDAP server URL (ldaps://dc.example.com)")
    parser.add_argument("--username", required=True, help="Bind DN or UPN")
    parser.add_argument("--password", required=True, help="Bind password")
    parser.add_argument("--no-ssl", action="store_true", help="Disable SSL")
    parser.add_argument("--output", help="Output JSON file path")
    args = parser.parse_args()

    report = generate_report(args.server, args.username, args.password, not args.no_ssl)
    output = json.dumps(report, indent=2)
    if args.output:
        Path(args.output).write_text(output, encoding="utf-8")
        print(f"Report written to {args.output}")
    else:
        print(output)


if __name__ == "__main__":
    main()
Keep exploring