mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-14 03:15:16 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
140 lines
5.3 KiB
Python
140 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for detecting service account abuse in Active Directory and cloud environments."""
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
def query_ad_service_accounts():
|
|
"""Query Active Directory for service accounts via PowerShell."""
|
|
ps_cmd = (
|
|
"Get-ADUser -Filter {ServicePrincipalName -ne '$null'} "
|
|
"-Properties ServicePrincipalName,LastLogonDate,Enabled,PasswordLastSet,"
|
|
"PasswordNeverExpires,AdminCount,MemberOf "
|
|
"| Select-Object SamAccountName,Enabled,LastLogonDate,PasswordLastSet,"
|
|
"PasswordNeverExpires,AdminCount,@{N='SPNCount';E={$_.ServicePrincipalName.Count}} "
|
|
"| ConvertTo-Json"
|
|
)
|
|
try:
|
|
result = subprocess.check_output(
|
|
["powershell", "-NoProfile", "-Command", ps_cmd],
|
|
text=True, errors="replace", timeout=30
|
|
)
|
|
data = json.loads(result) if result.strip().startswith(("[", "{")) else []
|
|
return data if isinstance(data, list) else [data]
|
|
except (subprocess.SubprocessError, json.JSONDecodeError):
|
|
return []
|
|
|
|
|
|
def check_interactive_logons(days=7):
|
|
"""Find service accounts with interactive logon events (type 2/10)."""
|
|
ps_cmd = (
|
|
f"Get-WinEvent -FilterHashtable @{{LogName='Security';Id=4624;"
|
|
f"StartTime=(Get-Date).AddDays(-{days})}} "
|
|
"| Where-Object {($_.Properties[8].Value -eq 2 -or $_.Properties[8].Value -eq 10) "
|
|
"-and $_.Properties[5].Value -match 'svc_|service'} "
|
|
"| Select-Object TimeCreated,@{N='Account';E={$_.Properties[5].Value}},"
|
|
"@{N='LogonType';E={$_.Properties[8].Value}},"
|
|
"@{N='SourceIP';E={$_.Properties[18].Value}} "
|
|
"| ConvertTo-Json"
|
|
)
|
|
try:
|
|
result = subprocess.check_output(
|
|
["powershell", "-NoProfile", "-Command", ps_cmd],
|
|
text=True, errors="replace", timeout=30
|
|
)
|
|
data = json.loads(result) if result.strip() else []
|
|
return data if isinstance(data, list) else [data]
|
|
except (subprocess.SubprocessError, json.JSONDecodeError):
|
|
return []
|
|
|
|
|
|
def analyze_logon_patterns(events):
|
|
"""Detect anomalous logon patterns for service accounts."""
|
|
anomalies = []
|
|
account_sources = {}
|
|
for evt in events:
|
|
acct = evt.get("Account", "unknown")
|
|
src = evt.get("SourceIP", "unknown")
|
|
account_sources.setdefault(acct, []).append(src)
|
|
|
|
for acct, sources in account_sources.items():
|
|
unique = set(sources)
|
|
if len(unique) > 3:
|
|
anomalies.append({
|
|
"account": acct,
|
|
"issue": "Service account logged in from multiple sources",
|
|
"source_count": len(unique),
|
|
"sources": list(unique)[:10],
|
|
})
|
|
return anomalies
|
|
|
|
|
|
def check_account_risks(accounts):
|
|
"""Identify risky service account configurations."""
|
|
risks = []
|
|
for acct in accounts:
|
|
name = acct.get("SamAccountName", "unknown")
|
|
issues = []
|
|
if acct.get("PasswordNeverExpires"):
|
|
issues.append("Password never expires")
|
|
if acct.get("AdminCount") == 1:
|
|
issues.append("Has AdminCount=1 (privileged)")
|
|
if acct.get("Enabled") is False:
|
|
issues.append("Account disabled but has SPNs")
|
|
pw_set = acct.get("PasswordLastSet")
|
|
if pw_set and isinstance(pw_set, str):
|
|
try:
|
|
pw_date = datetime.fromisoformat(pw_set.replace("Z", "+00:00"))
|
|
age = (datetime.now(timezone.utc) - pw_date).days
|
|
if age > 365:
|
|
issues.append(f"Password {age} days old")
|
|
except ValueError:
|
|
pass
|
|
if issues:
|
|
risks.append({"account": name, "issues": issues, "risk_count": len(issues)})
|
|
return risks
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Detect service account abuse in AD environments"
|
|
)
|
|
parser.add_argument("--days", type=int, default=7, help="Lookback days for logon events")
|
|
parser.add_argument("--output", "-o", help="Output JSON report path")
|
|
parser.add_argument("--verbose", "-v", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
print("[*] Service Account Abuse Detection Agent")
|
|
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}}
|
|
|
|
accounts = query_ad_service_accounts()
|
|
report["findings"]["service_accounts"] = len(accounts)
|
|
print(f"[*] Found {len(accounts)} service accounts with SPNs")
|
|
|
|
risks = check_account_risks(accounts)
|
|
report["findings"]["risky_accounts"] = risks
|
|
print(f"[*] Risky accounts: {len(risks)}")
|
|
|
|
logons = check_interactive_logons(args.days)
|
|
report["findings"]["interactive_logons"] = len(logons)
|
|
anomalies = analyze_logon_patterns(logons)
|
|
report["findings"]["logon_anomalies"] = anomalies
|
|
print(f"[*] Logon anomalies: {len(anomalies)}")
|
|
|
|
total_issues = len(risks) + len(anomalies)
|
|
report["risk_level"] = "CRITICAL" if total_issues >= 5 else "HIGH" if total_issues >= 3 else "MEDIUM" if total_issues > 0 else "LOW"
|
|
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
print(f"[*] Report saved to {args.output}")
|
|
else:
|
|
print(json.dumps(report, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|