mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-16 04:35:18 +03:00
27c6414ca5
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
152 lines
6.0 KiB
Python
152 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for hunting living-off-the-cloud (LOTC) techniques using cloud service logs."""
|
|
|
|
import json
|
|
import argparse
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
|
|
try:
|
|
from elasticsearch import Elasticsearch
|
|
except ImportError:
|
|
Elasticsearch = None
|
|
|
|
CLOUD_C2_DOMAINS = [
|
|
"*.blob.core.windows.net", "*.s3.amazonaws.com", "*.storage.googleapis.com",
|
|
"*.azurewebsites.net", "*.cloudfront.net", "*.execute-api.amazonaws.com",
|
|
"*.cloudfunctions.net", "*.run.app", "*.appspot.com",
|
|
"pastebin.com", "raw.githubusercontent.com", "gist.githubusercontent.com",
|
|
"discord.com/api/webhooks", "hooks.slack.com", "api.telegram.org",
|
|
"notion.so", "docs.google.com", "drive.google.com",
|
|
"*.firebaseio.com", "*.azureedge.net", "*.ngrok.io",
|
|
]
|
|
|
|
SUSPICIOUS_PATTERNS = {
|
|
"azure_storage_exfil": {
|
|
"description": "Large uploads to Azure Blob Storage",
|
|
"query": {"bool": {"must": [
|
|
{"match": {"event.action": "PutBlob"}},
|
|
{"range": {"http.request.bytes": {"gte": 10485760}}}
|
|
]}},
|
|
},
|
|
"aws_s3_staging": {
|
|
"description": "Unusual S3 bucket creation or large PutObject",
|
|
"query": {"bool": {"must": [
|
|
{"terms": {"event.action": ["CreateBucket", "PutObject"]}},
|
|
{"range": {"@timestamp": {"gte": "now-24h"}}}
|
|
]}},
|
|
},
|
|
"saas_c2_channel": {
|
|
"description": "Outbound connections to SaaS APIs used for C2",
|
|
"query": {"bool": {"must": [
|
|
{"terms": {"dns.question.name": [
|
|
"api.telegram.org", "discord.com", "hooks.slack.com",
|
|
"pastebin.com", "notion.so"
|
|
]}},
|
|
{"match": {"process.name": {"query": "powershell.exe cmd.exe rundll32.exe", "operator": "or"}}}
|
|
]}},
|
|
},
|
|
"cloud_function_invoke": {
|
|
"description": "Suspicious invocation of cloud functions for payload delivery",
|
|
"query": {"bool": {"must": [
|
|
{"regexp": {"url.domain": ".*\\.(cloudfunctions\\.net|execute-api\\.amazonaws\\.com|azurewebsites\\.net)"}},
|
|
{"terms": {"process.name": ["certutil.exe", "bitsadmin.exe", "curl.exe", "wget.exe"]}}
|
|
]}},
|
|
},
|
|
"github_raw_download": {
|
|
"description": "Downloads from raw GitHub content indicating payload staging",
|
|
"query": {"bool": {"must": [
|
|
{"wildcard": {"url.domain": "*githubusercontent.com"}},
|
|
{"terms": {"process.name": ["powershell.exe", "cmd.exe", "wscript.exe", "cscript.exe"]}}
|
|
]}},
|
|
},
|
|
}
|
|
|
|
|
|
def hunt_lotc_elastic(es_host, es_index, api_key=None, hours=24):
|
|
"""Run LOTC hunting queries against Elasticsearch/Elastic SIEM."""
|
|
if Elasticsearch is None:
|
|
return {"error": "elasticsearch-py not installed"}
|
|
kwargs = {"hosts": [es_host]}
|
|
if api_key:
|
|
kwargs["api_key"] = api_key
|
|
es = Elasticsearch(**kwargs)
|
|
results = {"timestamp": datetime.utcnow().isoformat(), "hunts": [], "total_hits": 0}
|
|
for hunt_name, hunt_def in SUSPICIOUS_PATTERNS.items():
|
|
body = {"query": hunt_def["query"], "size": 100, "sort": [{"@timestamp": "desc"}]}
|
|
resp = es.search(index=es_index, body=body)
|
|
hits = resp["hits"]["total"]["value"]
|
|
events = []
|
|
for hit in resp["hits"]["hits"]:
|
|
src = hit["_source"]
|
|
events.append({
|
|
"timestamp": src.get("@timestamp"),
|
|
"host": src.get("host", {}).get("name"),
|
|
"process": src.get("process", {}).get("name"),
|
|
"command_line": src.get("process", {}).get("command_line"),
|
|
"destination": src.get("url", {}).get("domain") or src.get("dns", {}).get("question", {}).get("name"),
|
|
"user": src.get("user", {}).get("name"),
|
|
})
|
|
results["hunts"].append({
|
|
"name": hunt_name,
|
|
"description": hunt_def["description"],
|
|
"hits": hits,
|
|
"events": events,
|
|
})
|
|
results["total_hits"] += hits
|
|
return results
|
|
|
|
|
|
def analyze_dns_logs(log_file):
|
|
"""Analyze DNS query logs for cloud C2 domain patterns."""
|
|
findings = []
|
|
cloud_regex = re.compile(
|
|
r"(blob\.core\.windows\.net|s3\.amazonaws\.com|storage\.googleapis\.com|"
|
|
r"cloudfunctions\.net|execute-api\.amazonaws\.com|azurewebsites\.net|"
|
|
r"ngrok\.io|firebaseio\.com|pastebin\.com|githubusercontent\.com|"
|
|
r"api\.telegram\.org|discord\.com|hooks\.slack\.com)", re.I
|
|
)
|
|
with open(log_file, "r") as f:
|
|
for line_num, line in enumerate(f, 1):
|
|
match = cloud_regex.search(line)
|
|
if match:
|
|
findings.append({
|
|
"line": line_num,
|
|
"matched_domain": match.group(0),
|
|
"raw": line.strip()[:200],
|
|
})
|
|
return {
|
|
"file": str(log_file),
|
|
"total_matches": len(findings),
|
|
"findings": findings[:500],
|
|
"cloud_services_detected": list(set(f["matched_domain"] for f in findings)),
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Hunt for Living-off-the-Cloud (LOTC) techniques")
|
|
sub = parser.add_subparsers(dest="command")
|
|
|
|
hunt = sub.add_parser("hunt", help="Run LOTC hunts against Elasticsearch")
|
|
hunt.add_argument("--es-host", required=True, help="Elasticsearch host URL")
|
|
hunt.add_argument("--index", default="logs-*", help="Index pattern")
|
|
hunt.add_argument("--api-key", help="Elasticsearch API key")
|
|
hunt.add_argument("--hours", type=int, default=24, help="Lookback hours")
|
|
|
|
dns = sub.add_parser("dns", help="Analyze DNS logs for cloud C2 domains")
|
|
dns.add_argument("--log-file", required=True, help="Path to DNS query log file")
|
|
|
|
args = parser.parse_args()
|
|
if args.command == "hunt":
|
|
result = hunt_lotc_elastic(args.es_host, args.index, args.api_key, args.hours)
|
|
elif args.command == "dns":
|
|
result = analyze_dns_logs(args.log_file)
|
|
else:
|
|
parser.print_help()
|
|
return
|
|
print(json.dumps(result, indent=2, default=str))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|