mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-16 04:35:18 +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
188 lines
6.7 KiB
Python
188 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Azure Service Principal abuse detection agent."""
|
|
|
|
import json
|
|
import sys
|
|
import argparse
|
|
from datetime import datetime, timedelta
|
|
|
|
try:
|
|
from azure.identity import ClientSecretCredential
|
|
except ImportError:
|
|
ClientSecretCredential = None
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
print("Install: pip install requests")
|
|
sys.exit(1)
|
|
|
|
|
|
class AzureGraphClient:
|
|
"""Client for Microsoft Graph API to audit service principals."""
|
|
|
|
def __init__(self, tenant_id, client_id, client_secret):
|
|
self.tenant_id = tenant_id
|
|
self.token = self._get_token(tenant_id, client_id, client_secret)
|
|
self.headers = {"Authorization": f"Bearer {self.token}"}
|
|
|
|
def _get_token(self, tenant_id, client_id, client_secret):
|
|
resp = requests.post(
|
|
f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
|
|
data={
|
|
"grant_type": "client_credentials",
|
|
"client_id": client_id,
|
|
"client_secret": client_secret,
|
|
"scope": "https://graph.microsoft.com/.default",
|
|
},
|
|
timeout=15,
|
|
)
|
|
resp.raise_for_status()
|
|
return resp.json()["access_token"]
|
|
|
|
def _get(self, endpoint, params=None):
|
|
resp = requests.get(f"https://graph.microsoft.com/v1.0{endpoint}",
|
|
headers=self.headers, params=params, timeout=15)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
|
|
def list_service_principals(self):
|
|
return self._get("/servicePrincipals", params={"$top": 200}).get("value", [])
|
|
|
|
def get_sp_credentials(self, sp_id):
|
|
return self._get(f"/servicePrincipals/{sp_id}").get("passwordCredentials", [])
|
|
|
|
def get_sp_app_roles(self, sp_id):
|
|
return self._get(f"/servicePrincipals/{sp_id}/appRoleAssignments").get("value", [])
|
|
|
|
def get_sign_in_logs(self, sp_id, days=7):
|
|
since = (datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
return self._get("/auditLogs/signIns", params={
|
|
"$filter": f"appId eq '{sp_id}' and createdDateTime ge {since}",
|
|
"$top": 100,
|
|
}).get("value", [])
|
|
|
|
def get_directory_roles(self):
|
|
return self._get("/directoryRoles").get("value", [])
|
|
|
|
def get_role_members(self, role_id):
|
|
return self._get(f"/directoryRoles/{role_id}/members").get("value", [])
|
|
|
|
|
|
def audit_credential_expiry(client, principals):
|
|
"""Check for expired or soon-to-expire service principal credentials."""
|
|
findings = []
|
|
now = datetime.utcnow()
|
|
for sp in principals:
|
|
sp_id = sp.get("id", "")
|
|
display = sp.get("displayName", "")
|
|
creds = sp.get("passwordCredentials", [])
|
|
key_creds = sp.get("keyCredentials", [])
|
|
|
|
for cred in creds + key_creds:
|
|
end_str = cred.get("endDateTime", "")
|
|
if not end_str:
|
|
continue
|
|
try:
|
|
end = datetime.fromisoformat(end_str.rstrip("Z"))
|
|
except ValueError:
|
|
continue
|
|
|
|
if end < now:
|
|
findings.append({
|
|
"sp_name": display, "sp_id": sp_id,
|
|
"credential_id": cred.get("keyId", ""),
|
|
"issue": "Credential expired but not removed",
|
|
"severity": "MEDIUM", "expired": end_str,
|
|
})
|
|
elif end < now + timedelta(days=30):
|
|
findings.append({
|
|
"sp_name": display, "sp_id": sp_id,
|
|
"issue": f"Credential expires within 30 days ({end_str})",
|
|
"severity": "LOW",
|
|
})
|
|
|
|
if len(creds) > 2:
|
|
findings.append({
|
|
"sp_name": display, "sp_id": sp_id,
|
|
"issue": f"Multiple password credentials ({len(creds)}) — possible backdoor",
|
|
"severity": "HIGH",
|
|
})
|
|
|
|
return findings
|
|
|
|
|
|
def audit_privileged_sp_roles(client):
|
|
"""Find service principals with high-privilege directory roles."""
|
|
findings = []
|
|
high_priv_roles = {
|
|
"Global Administrator", "Application Administrator",
|
|
"Cloud Application Administrator", "Privileged Role Administrator",
|
|
}
|
|
roles = client.get_directory_roles()
|
|
for role in roles:
|
|
role_name = role.get("displayName", "")
|
|
if role_name not in high_priv_roles:
|
|
continue
|
|
members = client.get_role_members(role["id"])
|
|
for m in members:
|
|
if m.get("@odata.type") == "#microsoft.graph.servicePrincipal":
|
|
findings.append({
|
|
"sp_name": m.get("displayName", ""),
|
|
"sp_id": m.get("id", ""),
|
|
"role": role_name,
|
|
"issue": f"Service principal has {role_name} role",
|
|
"severity": "CRITICAL",
|
|
})
|
|
return findings
|
|
|
|
|
|
def run_audit(args):
|
|
"""Execute Azure service principal abuse detection audit."""
|
|
print(f"\n{'='*60}")
|
|
print(f" AZURE SERVICE PRINCIPAL ABUSE DETECTION")
|
|
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
|
|
print(f"{'='*60}\n")
|
|
|
|
client = AzureGraphClient(args.tenant_id, args.client_id, args.client_secret)
|
|
report = {}
|
|
|
|
principals = client.list_service_principals()
|
|
report["total_service_principals"] = len(principals)
|
|
print(f"--- SERVICE PRINCIPALS ({len(principals)}) ---")
|
|
for sp in principals[:10]:
|
|
print(f" {sp.get('displayName','')}: {sp.get('appId','')[:20]}...")
|
|
|
|
cred_findings = audit_credential_expiry(client, principals)
|
|
report["credential_findings"] = cred_findings
|
|
print(f"\n--- CREDENTIAL AUDIT ({len(cred_findings)} findings) ---")
|
|
for f in cred_findings[:15]:
|
|
print(f" [{f['severity']}] {f['sp_name']}: {f['issue']}")
|
|
|
|
role_findings = audit_privileged_sp_roles(client)
|
|
report["privileged_role_findings"] = role_findings
|
|
print(f"\n--- PRIVILEGED ROLE ASSIGNMENTS ({len(role_findings)}) ---")
|
|
for f in role_findings:
|
|
print(f" [{f['severity']}] {f['sp_name']}: {f['role']}")
|
|
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Azure SP Abuse Detection Agent")
|
|
parser.add_argument("--tenant-id", required=True, help="Azure AD tenant ID")
|
|
parser.add_argument("--client-id", required=True, help="App registration client ID")
|
|
parser.add_argument("--client-secret", required=True, help="App registration secret")
|
|
parser.add_argument("--output", help="Save report to JSON file")
|
|
args = parser.parse_args()
|
|
|
|
report = run_audit(args)
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|