mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
915ea611e5
Skills added: - implementing-privileged-access-workstation (IAM, PAW hardening) - detecting-suspicious-oauth-application-consent (cloud security, Graph API) - performing-hardware-security-module-integration (cryptography, PKCS#11) - analyzing-android-malware-with-apktool (malware analysis, androguard) - hunting-for-unusual-service-installations (threat hunting, T1543.003) - detecting-shadow-it-cloud-usage (cloud security, proxy/DNS log analysis) - performing-active-directory-forest-trust-attack (red team, impacket) - implementing-deception-based-detection-with-canarytoken (deception, Canary API) - analyzing-office365-audit-logs-for-compromise (cloud security, BEC detection) - hunting-for-startup-folder-persistence (threat hunting, T1547.001) Each skill includes SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
230 lines
9.6 KiB
Python
230 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for AD forest trust enumeration and security assessment using impacket."""
|
|
|
|
import json
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
try:
|
|
from impacket.dcerpc.v5 import transport, lsat, lsad
|
|
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
|
|
from impacket.dcerpc.v5.samr import SID_NAME_USE
|
|
from impacket.smbconnection import SMBConnection
|
|
except ImportError:
|
|
transport = None
|
|
|
|
try:
|
|
import ldap3
|
|
from ldap3 import Server, Connection, ALL, SUBTREE
|
|
except ImportError:
|
|
ldap3 = None
|
|
|
|
TRUST_DIRECTION = {0: "Disabled", 1: "Inbound", 2: "Outbound", 3: "Bidirectional"}
|
|
TRUST_TYPE = {1: "Downlevel (Windows NT)", 2: "Uplevel (Windows 2000+)", 3: "MIT Kerberos", 4: "DCE"}
|
|
TRUST_ATTRIBUTES = {
|
|
0x00000001: "NON_TRANSITIVE",
|
|
0x00000002: "UPLEVEL_ONLY",
|
|
0x00000004: "QUARANTINED_DOMAIN (SID Filtering Enabled)",
|
|
0x00000008: "FOREST_TRANSITIVE",
|
|
0x00000010: "CROSS_ORGANIZATION",
|
|
0x00000020: "WITHIN_FOREST",
|
|
0x00000040: "TREAT_AS_EXTERNAL",
|
|
0x00000080: "USES_RC4_ENCRYPTION",
|
|
0x00000200: "CROSS_ORGANIZATION_NO_TGT_DELEGATION",
|
|
0x00000400: "PIM_TRUST",
|
|
}
|
|
|
|
|
|
def enumerate_trusts_ldap(dc_host, domain, username, password):
|
|
"""Enumerate AD trust relationships via LDAP trustedDomain objects."""
|
|
if not ldap3:
|
|
return {"error": "ldap3 not installed: pip install ldap3"}
|
|
server = Server(dc_host, get_info=ALL, use_ssl=False)
|
|
base_dn = ",".join(f"DC={p}" for p in domain.split("."))
|
|
conn = Connection(server, user=f"{domain}\\{username}", password=password, auto_bind=True)
|
|
conn.search(
|
|
search_base=f"CN=System,{base_dn}",
|
|
search_filter="(objectClass=trustedDomain)",
|
|
search_scope=SUBTREE,
|
|
attributes=[
|
|
"cn", "trustPartner", "trustDirection", "trustType",
|
|
"trustAttributes", "securityIdentifier", "whenCreated",
|
|
"flatName", "trustPosixOffset",
|
|
],
|
|
)
|
|
trusts = []
|
|
for entry in conn.entries:
|
|
attrs = entry.entry_attributes_as_dict
|
|
direction_val = int(attrs.get("trustDirection", [0])[0])
|
|
type_val = int(attrs.get("trustType", [0])[0])
|
|
attr_val = int(attrs.get("trustAttributes", [0])[0])
|
|
decoded_attrs = []
|
|
for bit, name in TRUST_ATTRIBUTES.items():
|
|
if attr_val & bit:
|
|
decoded_attrs.append(name)
|
|
sid_filtering = bool(attr_val & 0x00000004)
|
|
forest_trust = bool(attr_val & 0x00000008)
|
|
trusts.append({
|
|
"trust_partner": str(attrs.get("trustPartner", [""])[0]),
|
|
"flat_name": str(attrs.get("flatName", [""])[0]),
|
|
"trust_direction": TRUST_DIRECTION.get(direction_val, str(direction_val)),
|
|
"trust_type": TRUST_TYPE.get(type_val, str(type_val)),
|
|
"trust_attributes_raw": attr_val,
|
|
"trust_attributes": decoded_attrs,
|
|
"sid_filtering_enabled": sid_filtering,
|
|
"forest_transitive": forest_trust,
|
|
"when_created": str(attrs.get("whenCreated", [""])[0]),
|
|
})
|
|
conn.unbind()
|
|
return trusts
|
|
|
|
|
|
def enumerate_foreign_principals(dc_host, domain, username, password):
|
|
"""Find foreign security principals (cross-forest members) in the domain."""
|
|
if not ldap3:
|
|
return {"error": "ldap3 not installed"}
|
|
server = Server(dc_host, get_info=ALL, use_ssl=False)
|
|
base_dn = ",".join(f"DC={p}" for p in domain.split("."))
|
|
conn = Connection(server, user=f"{domain}\\{username}", password=password, auto_bind=True)
|
|
conn.search(
|
|
search_base=f"CN=ForeignSecurityPrincipals,{base_dn}",
|
|
search_filter="(objectClass=foreignSecurityPrincipal)",
|
|
search_scope=SUBTREE,
|
|
attributes=["cn", "objectSid", "whenCreated", "memberOf"],
|
|
)
|
|
principals = []
|
|
for entry in conn.entries:
|
|
attrs = entry.entry_attributes_as_dict
|
|
sid = str(attrs.get("cn", [""])[0])
|
|
member_of = [str(g) for g in attrs.get("memberOf", [])]
|
|
principals.append({
|
|
"sid": sid,
|
|
"member_of_groups": member_of,
|
|
"when_created": str(attrs.get("whenCreated", [""])[0]),
|
|
"is_well_known": sid.startswith("S-1-5-") and sid.count("-") == 3,
|
|
})
|
|
conn.unbind()
|
|
custom_principals = [p for p in principals if not p["is_well_known"]]
|
|
return {
|
|
"total_foreign_principals": len(principals),
|
|
"custom_foreign_principals": len(custom_principals),
|
|
"principals": custom_principals[:30],
|
|
}
|
|
|
|
|
|
def lookup_sid_cross_forest(dc_host, domain, username, password, target_sid):
|
|
"""Resolve a SID across forest trust using LSA LookupSids RPC call."""
|
|
if not transport:
|
|
return {"error": "impacket not installed: pip install impacket"}
|
|
rpctransport = transport.SMBTransport(dc_host, filename=r"\lsarpc")
|
|
rpctransport.set_credentials(username, password, domain)
|
|
dce = rpctransport.get_dce_rpc()
|
|
dce.connect()
|
|
dce.bind(lsat.MSRPC_UUID_LSAT)
|
|
resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED)
|
|
policy_handle = resp["PolicyHandle"]
|
|
try:
|
|
from impacket.dcerpc.v5.dtypes import RPC_SID
|
|
sid = RPC_SID()
|
|
sid.fromCanonical(target_sid)
|
|
resp = lsat.hLsarLookupSids2(dce, policy_handle, [sid])
|
|
names = []
|
|
for item in resp["TranslatedNames"]["Names"]:
|
|
names.append({
|
|
"name": item["Name"],
|
|
"sid_type": SID_NAME_USE.enumItems(item["Use"]).name if hasattr(SID_NAME_USE, 'enumItems') else str(item["Use"]),
|
|
"domain_index": item["DomainIndex"],
|
|
})
|
|
return {"target_sid": target_sid, "resolved_names": names}
|
|
except Exception as e:
|
|
return {"target_sid": target_sid, "error": str(e)}
|
|
finally:
|
|
dce.disconnect()
|
|
|
|
|
|
def assess_trust_risk(trusts, foreign_principals):
|
|
"""Assess security risk of trust relationships."""
|
|
findings = []
|
|
for trust in trusts:
|
|
risk = 0
|
|
issues = []
|
|
if not trust.get("sid_filtering_enabled"):
|
|
risk += 40
|
|
issues.append("SID filtering DISABLED — SID history attacks possible")
|
|
if trust.get("trust_direction") == "Bidirectional":
|
|
risk += 15
|
|
issues.append("Bidirectional trust increases attack surface")
|
|
if trust.get("forest_transitive"):
|
|
risk += 10
|
|
issues.append("Forest transitive trust — all domains reachable")
|
|
if "USES_RC4_ENCRYPTION" in trust.get("trust_attributes", []):
|
|
risk += 20
|
|
issues.append("RC4 encryption — vulnerable to trust key cracking")
|
|
risk = min(risk, 100)
|
|
findings.append({
|
|
"trust_partner": trust.get("trust_partner"),
|
|
"risk_score": risk,
|
|
"risk_level": "CRITICAL" if risk >= 70 else "HIGH" if risk >= 50 else "MEDIUM" if risk >= 25 else "LOW",
|
|
"issues": issues,
|
|
"recommendation": "Enable SID filtering and migrate to AES encryption"
|
|
if risk >= 50 else "Review trust configuration",
|
|
})
|
|
return findings
|
|
|
|
|
|
def full_audit(dc_host, domain, username, password):
|
|
"""Run comprehensive forest trust security audit."""
|
|
trusts = enumerate_trusts_ldap(dc_host, domain, username, password)
|
|
foreign = enumerate_foreign_principals(dc_host, domain, username, password)
|
|
risk = assess_trust_risk(trusts if isinstance(trusts, list) else [], foreign)
|
|
critical = sum(1 for r in risk if r["risk_level"] == "CRITICAL")
|
|
high = sum(1 for r in risk if r["risk_level"] == "HIGH")
|
|
no_sid_filter = sum(1 for t in (trusts if isinstance(trusts, list) else []) if not t.get("sid_filtering_enabled"))
|
|
return {
|
|
"audit_type": "AD Forest Trust Security Assessment",
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"domain": domain,
|
|
"summary": {
|
|
"total_trusts": len(trusts) if isinstance(trusts, list) else 0,
|
|
"trusts_without_sid_filtering": no_sid_filter,
|
|
"foreign_principals": foreign.get("custom_foreign_principals", 0),
|
|
"critical_findings": critical,
|
|
"high_findings": high,
|
|
},
|
|
"trusts": trusts,
|
|
"foreign_principals": foreign,
|
|
"risk_assessment": risk,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="AD Forest Trust Security Audit Agent")
|
|
parser.add_argument("--dc", required=True, help="Domain Controller hostname or IP")
|
|
parser.add_argument("--domain", required=True, help="Domain name (e.g., corp.local)")
|
|
parser.add_argument("--username", required=True, help="Domain username")
|
|
parser.add_argument("--password", required=True, help="Domain password")
|
|
sub = parser.add_subparsers(dest="command")
|
|
sub.add_parser("trusts", help="Enumerate trust relationships")
|
|
sub.add_parser("foreign", help="List foreign security principals")
|
|
p_sid = sub.add_parser("lookup-sid", help="Cross-forest SID lookup")
|
|
p_sid.add_argument("--sid", required=True, help="Target SID to resolve")
|
|
sub.add_parser("full", help="Full trust security audit")
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "trusts":
|
|
result = enumerate_trusts_ldap(args.dc, args.domain, args.username, args.password)
|
|
elif args.command == "foreign":
|
|
result = enumerate_foreign_principals(args.dc, args.domain, args.username, args.password)
|
|
elif args.command == "lookup-sid":
|
|
result = lookup_sid_cross_forest(args.dc, args.domain, args.username, args.password, args.sid)
|
|
elif args.command == "full" or args.command is None:
|
|
result = full_audit(args.dc, args.domain, args.username, args.password)
|
|
else:
|
|
parser.print_help()
|
|
return
|
|
print(json.dumps(result, indent=2, default=str))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|