npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
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
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.1941—LDAP_MATCHING_RULE_IN_CHAIN(recursive group membership)1.2.840.113556.1.4.803—LDAP_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.jsonScripts 1
agent.py5.4 KB
#!/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()