mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-06 19:00:17 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Azure Service Principal Abuse Detection Script
|
||||
|
||||
Queries Microsoft Graph API to detect suspicious service principal
|
||||
activities including new credentials, privilege escalation, and
|
||||
unauthorized ownership.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def run_az_cli(args):
|
||||
"""Execute Azure CLI command and return parsed JSON output."""
|
||||
cmd = ["az"] + args + ["--output", "json"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return None, result.stderr
|
||||
try:
|
||||
return json.loads(result.stdout) if result.stdout.strip() else {}, None
|
||||
except json.JSONDecodeError:
|
||||
return result.stdout, None
|
||||
|
||||
|
||||
def check_recent_credential_additions(days=7):
|
||||
"""Check for service principals with recently added credentials."""
|
||||
print(f"[*] Checking for SP credentials added in last {days} days...")
|
||||
cutoff = (datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
sps, err = run_az_cli(["ad", "sp", "list", "--all"])
|
||||
if err:
|
||||
print(f"[!] Error listing service principals: {err}")
|
||||
return []
|
||||
|
||||
suspicious = []
|
||||
for sp in sps or []:
|
||||
display_name = sp.get("displayName", "Unknown")
|
||||
app_id = sp.get("appId", "")
|
||||
object_id = sp.get("id", "")
|
||||
|
||||
# Check password credentials
|
||||
for cred in sp.get("passwordCredentials", []):
|
||||
start_date = cred.get("startDateTime", "")
|
||||
if start_date and start_date > cutoff:
|
||||
suspicious.append({
|
||||
"type": "password_credential",
|
||||
"sp_name": display_name,
|
||||
"app_id": app_id,
|
||||
"object_id": object_id,
|
||||
"credential_start": start_date,
|
||||
"credential_end": cred.get("endDateTime", ""),
|
||||
"key_id": cred.get("keyId", "")
|
||||
})
|
||||
|
||||
# Check certificate credentials
|
||||
for cert in sp.get("keyCredentials", []):
|
||||
start_date = cert.get("startDateTime", "")
|
||||
if start_date and start_date > cutoff:
|
||||
suspicious.append({
|
||||
"type": "certificate_credential",
|
||||
"sp_name": display_name,
|
||||
"app_id": app_id,
|
||||
"object_id": object_id,
|
||||
"credential_start": start_date,
|
||||
"credential_end": cert.get("endDateTime", ""),
|
||||
"key_id": cert.get("keyId", "")
|
||||
})
|
||||
|
||||
if suspicious:
|
||||
print(f"[!] Found {len(suspicious)} recently added credentials:")
|
||||
for item in suspicious:
|
||||
print(f" - [{item['type']}] {item['sp_name']} (AppId: {item['app_id']})")
|
||||
print(f" Added: {item['credential_start']}")
|
||||
else:
|
||||
print("[+] No recently added credentials found")
|
||||
|
||||
return suspicious
|
||||
|
||||
|
||||
def check_privileged_sp_roles():
|
||||
"""Check for service principals with privileged directory roles."""
|
||||
print("\n[*] Checking service principals with privileged roles...")
|
||||
|
||||
privileged_roles = [
|
||||
"Global Administrator",
|
||||
"Application Administrator",
|
||||
"Cloud Application Administrator",
|
||||
"Privileged Role Administrator",
|
||||
"Exchange Administrator",
|
||||
"SharePoint Administrator",
|
||||
"User Administrator"
|
||||
]
|
||||
|
||||
findings = []
|
||||
roles, err = run_az_cli(["rest", "--method", "GET",
|
||||
"--url", "https://graph.microsoft.com/v1.0/directoryRoles"])
|
||||
if err:
|
||||
print(f"[!] Error fetching roles: {err}")
|
||||
return []
|
||||
|
||||
for role in (roles or {}).get("value", []):
|
||||
role_name = role.get("displayName", "")
|
||||
role_id = role.get("id", "")
|
||||
|
||||
if role_name not in privileged_roles:
|
||||
continue
|
||||
|
||||
members, err = run_az_cli(["rest", "--method", "GET",
|
||||
"--url", f"https://graph.microsoft.com/v1.0/directoryRoles/{role_id}/members"])
|
||||
if err:
|
||||
continue
|
||||
|
||||
for member in (members or {}).get("value", []):
|
||||
odata_type = member.get("@odata.type", "")
|
||||
if "servicePrincipal" in odata_type:
|
||||
findings.append({
|
||||
"role": role_name,
|
||||
"sp_name": member.get("displayName", "Unknown"),
|
||||
"sp_id": member.get("id", ""),
|
||||
"app_id": member.get("appId", "")
|
||||
})
|
||||
|
||||
if findings:
|
||||
print(f"[!] Found {len(findings)} service principals with privileged roles:")
|
||||
for f in findings:
|
||||
print(f" - {f['sp_name']} has role: {f['role']}")
|
||||
else:
|
||||
print("[+] No service principals with privileged roles found")
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_sp_ownership():
|
||||
"""Identify applications with non-admin owners (potential abuse vector)."""
|
||||
print("\n[*] Checking application ownership for potential abuse vectors...")
|
||||
|
||||
apps, err = run_az_cli(["ad", "app", "list", "--all"])
|
||||
if err:
|
||||
print(f"[!] Error listing applications: {err}")
|
||||
return []
|
||||
|
||||
risky_ownership = []
|
||||
for app in apps or []:
|
||||
app_name = app.get("displayName", "Unknown")
|
||||
app_id = app.get("appId", "")
|
||||
object_id = app.get("id", "")
|
||||
|
||||
owners, err = run_az_cli(["rest", "--method", "GET",
|
||||
"--url", f"https://graph.microsoft.com/v1.0/applications/{object_id}/owners"])
|
||||
if err:
|
||||
continue
|
||||
|
||||
owner_list = (owners or {}).get("value", [])
|
||||
if len(owner_list) > 3: # Flag apps with many owners
|
||||
risky_ownership.append({
|
||||
"app_name": app_name,
|
||||
"app_id": app_id,
|
||||
"owner_count": len(owner_list),
|
||||
"owners": [o.get("userPrincipalName", o.get("displayName", "Unknown")) for o in owner_list]
|
||||
})
|
||||
|
||||
if risky_ownership:
|
||||
print(f"[!] Found {len(risky_ownership)} applications with excessive owners:")
|
||||
for item in risky_ownership:
|
||||
print(f" - {item['app_name']}: {item['owner_count']} owners")
|
||||
else:
|
||||
print("[+] No applications with excessive ownership found")
|
||||
|
||||
return risky_ownership
|
||||
|
||||
|
||||
def generate_detection_report(cred_findings, role_findings, ownership_findings):
|
||||
"""Generate a consolidated detection report."""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
report = f"""
|
||||
{'='*60}
|
||||
Azure Service Principal Abuse Detection Report
|
||||
Generated: {timestamp}
|
||||
{'='*60}
|
||||
|
||||
## Summary
|
||||
- Recent Credential Additions: {len(cred_findings)}
|
||||
- Privileged Role Assignments: {len(role_findings)}
|
||||
- Risky Application Ownership: {len(ownership_findings)}
|
||||
- Overall Risk: {'HIGH' if cred_findings or role_findings else 'LOW'}
|
||||
"""
|
||||
|
||||
if cred_findings:
|
||||
report += "\n## Recently Added Credentials\n"
|
||||
for f in cred_findings:
|
||||
report += f" [{f['type']}] {f['sp_name']} - Added {f['credential_start']}\n"
|
||||
|
||||
if role_findings:
|
||||
report += "\n## Privileged Service Principals\n"
|
||||
for f in role_findings:
|
||||
report += f" {f['sp_name']} -> {f['role']}\n"
|
||||
|
||||
if ownership_findings:
|
||||
report += "\n## Risky Application Ownership\n"
|
||||
for f in ownership_findings:
|
||||
report += f" {f['app_name']} - {f['owner_count']} owners\n"
|
||||
|
||||
print(report)
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Azure Service Principal Abuse Detection")
|
||||
parser.add_argument("--days", type=int, default=7, help="Lookback period in days")
|
||||
parser.add_argument("--credentials", action="store_true", help="Check recent credential additions")
|
||||
parser.add_argument("--roles", action="store_true", help="Check privileged role assignments")
|
||||
parser.add_argument("--ownership", action="store_true", help="Check application ownership")
|
||||
parser.add_argument("--full", action="store_true", help="Run all checks")
|
||||
parser.add_argument("--output", type=str, help="Save report to file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
cred_findings = []
|
||||
role_findings = []
|
||||
ownership_findings = []
|
||||
|
||||
if args.full or args.credentials:
|
||||
cred_findings = check_recent_credential_additions(args.days)
|
||||
if args.full or args.roles:
|
||||
role_findings = check_privileged_sp_roles()
|
||||
if args.full or args.ownership:
|
||||
ownership_findings = check_sp_ownership()
|
||||
|
||||
if args.full or (args.credentials and args.roles):
|
||||
report = generate_detection_report(cred_findings, role_findings, ownership_findings)
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
f.write(report)
|
||||
print(f"\n[+] Report saved to {args.output}")
|
||||
Reference in New Issue
Block a user