Files
mukul975 915ea611e5 Add 10 new cybersecurity skills with full folder anatomy
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
2026-03-11 00:47:03 +01:00

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()