mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-30 13:19:40 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
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
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Anthropic Agent Skills Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: analyzing-cloud-storage-access-patterns
|
||||
description: >-
|
||||
Detect abnormal access patterns in AWS S3, GCS, and Azure Blob Storage by analyzing CloudTrail
|
||||
Data Events, GCS audit logs, and Azure Storage Analytics. Identifies after-hours bulk downloads,
|
||||
access from new IP addresses, unusual API calls (GetObject spikes), and potential data exfiltration
|
||||
using statistical baselines and time-series anomaly detection.
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Install dependencies: `pip install boto3 requests`
|
||||
2. Query CloudTrail for S3 Data Events using AWS CLI or boto3.
|
||||
3. Build access baselines: hourly request volume, per-user object counts, source IP history.
|
||||
4. Detect anomalies:
|
||||
- After-hours access (outside 8am-6pm local time)
|
||||
- Bulk downloads: >100 GetObject calls from single principal in 1 hour
|
||||
- New source IPs not seen in the prior 30 days
|
||||
- ListBucket enumeration spikes (reconnaissance indicator)
|
||||
5. Generate prioritized findings report.
|
||||
|
||||
```bash
|
||||
python scripts/agent.py --bucket my-sensitive-data --hours-back 24 --output s3_access_report.json
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### CloudTrail S3 Data Event
|
||||
```json
|
||||
{"eventName": "GetObject", "requestParameters": {"bucketName": "sensitive-data", "key": "financials/q4.xlsx"},
|
||||
"sourceIPAddress": "203.0.113.50", "userIdentity": {"arn": "arn:aws:iam::123456789012:user/analyst"}}
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
# API Reference: Cloud Storage Access Pattern Analysis
|
||||
|
||||
## AWS CLI - CloudTrail Lookup
|
||||
```bash
|
||||
aws cloudtrail lookup-events \
|
||||
--lookup-attributes AttributeKey=ResourceType,AttributeValue=AWS::S3::Object \
|
||||
--start-time 2024-01-15T00:00:00Z \
|
||||
--output json
|
||||
```
|
||||
|
||||
## CloudTrail S3 Data Event Structure
|
||||
```json
|
||||
{
|
||||
"EventTime": "2024-01-15T10:30:00Z",
|
||||
"EventName": "GetObject",
|
||||
"Username": "analyst",
|
||||
"CloudTrailEvent": "{\"sourceIPAddress\":\"10.0.0.1\",\"userAgent\":\"aws-cli\",\"requestParameters\":{\"bucketName\":\"data\",\"key\":\"file.csv\"},\"userIdentity\":{\"arn\":\"arn:aws:iam::123:user/analyst\"}}"
|
||||
}
|
||||
```
|
||||
|
||||
## Key S3 Event Names
|
||||
| Event | Meaning |
|
||||
|-------|---------|
|
||||
| GetObject | Object download |
|
||||
| PutObject | Object upload |
|
||||
| DeleteObject | Object deletion |
|
||||
| ListBucket / ListObjectsV2 | Bucket enumeration |
|
||||
| GetBucketPolicy | Policy read |
|
||||
| PutBucketPolicy | Policy modification |
|
||||
|
||||
## Detection Thresholds
|
||||
| Anomaly | Threshold | Severity |
|
||||
|---------|-----------|----------|
|
||||
| Bulk download | >100 GetObject/hr per user | Critical |
|
||||
| After-hours | Access outside 08:00-18:00 UTC | Medium |
|
||||
| New source IP | IP not in 30-day baseline | High |
|
||||
| Enumeration | >20 ListBucket per user | High |
|
||||
|
||||
## boto3 CloudTrail Client (alternative)
|
||||
```python
|
||||
import boto3
|
||||
client = boto3.client("cloudtrail")
|
||||
response = client.lookup_events(
|
||||
LookupAttributes=[{"AttributeKey":"ResourceType","AttributeValue":"AWS::S3::Object"}],
|
||||
StartTime=datetime(2024,1,15),
|
||||
MaxResults=50
|
||||
)
|
||||
events = response["Events"]
|
||||
```
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cloud Storage Access Pattern Analyzer - Detects abnormal S3/GCS/Azure Blob access via CloudTrail."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def query_cloudtrail_s3_events(bucket_name, hours_back=24):
|
||||
"""Query CloudTrail for S3 data events on a specific bucket."""
|
||||
start_time = (datetime.utcnow() - timedelta(hours=hours_back)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
cmd = [
|
||||
"aws", "cloudtrail", "lookup-events",
|
||||
"--lookup-attributes", f"AttributeKey=ResourceType,AttributeValue=AWS::S3::Object",
|
||||
"--start-time", start_time,
|
||||
"--output", "json",
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
logger.error("CloudTrail query failed: %s", result.stderr[:200])
|
||||
return []
|
||||
events = json.loads(result.stdout).get("Events", [])
|
||||
s3_events = []
|
||||
for event in events:
|
||||
ct_event = json.loads(event.get("CloudTrailEvent", "{}"))
|
||||
req_params = ct_event.get("requestParameters", {})
|
||||
if req_params.get("bucketName") == bucket_name or not bucket_name:
|
||||
s3_events.append({
|
||||
"timestamp": event.get("EventTime", ""),
|
||||
"event_name": event.get("EventName", ""),
|
||||
"username": event.get("Username", ""),
|
||||
"source_ip": ct_event.get("sourceIPAddress", ""),
|
||||
"user_agent": ct_event.get("userAgent", ""),
|
||||
"bucket": req_params.get("bucketName", ""),
|
||||
"key": req_params.get("key", ""),
|
||||
"user_arn": ct_event.get("userIdentity", {}).get("arn", ""),
|
||||
})
|
||||
logger.info("Retrieved %d S3 events for bucket '%s'", len(s3_events), bucket_name or "all")
|
||||
return s3_events
|
||||
|
||||
|
||||
def detect_bulk_downloads(events, threshold=100):
|
||||
"""Detect bulk GetObject operations from a single principal."""
|
||||
user_downloads = defaultdict(list)
|
||||
for event in events:
|
||||
if event["event_name"] == "GetObject":
|
||||
user_downloads[event["user_arn"]].append(event)
|
||||
alerts = []
|
||||
for user_arn, downloads in user_downloads.items():
|
||||
if len(downloads) >= threshold:
|
||||
keys = [d["key"] for d in downloads]
|
||||
alerts.append({
|
||||
"user_arn": user_arn,
|
||||
"download_count": len(downloads),
|
||||
"unique_keys": len(set(keys)),
|
||||
"source_ips": list({d["source_ip"] for d in downloads}),
|
||||
"first_access": downloads[0]["timestamp"],
|
||||
"last_access": downloads[-1]["timestamp"],
|
||||
"severity": "critical",
|
||||
"indicator": "Bulk download (potential exfiltration)",
|
||||
})
|
||||
logger.info("Found %d bulk download alerts", len(alerts))
|
||||
return alerts
|
||||
|
||||
|
||||
def detect_after_hours_access(events, business_start=8, business_end=18):
|
||||
"""Detect access outside business hours."""
|
||||
after_hours = []
|
||||
for event in events:
|
||||
try:
|
||||
ts = event["timestamp"]
|
||||
if isinstance(ts, str):
|
||||
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
else:
|
||||
dt = ts
|
||||
hour = dt.hour
|
||||
if hour < business_start or hour >= business_end:
|
||||
event["indicator"] = f"After-hours access at {hour:02d}:00 UTC"
|
||||
event["severity"] = "medium"
|
||||
after_hours.append(event)
|
||||
except (ValueError, AttributeError):
|
||||
continue
|
||||
logger.info("Found %d after-hours access events", len(after_hours))
|
||||
return after_hours
|
||||
|
||||
|
||||
def detect_new_source_ips(events, known_ips=None):
|
||||
"""Detect access from IP addresses not in the known baseline."""
|
||||
if known_ips is None:
|
||||
known_ips = set()
|
||||
new_ip_events = []
|
||||
for event in events:
|
||||
ip = event["source_ip"]
|
||||
if ip and ip not in known_ips and not ip.startswith("AWS Internal"):
|
||||
event["indicator"] = f"New source IP: {ip}"
|
||||
event["severity"] = "high"
|
||||
new_ip_events.append(event)
|
||||
unique_new = len({e["source_ip"] for e in new_ip_events})
|
||||
logger.info("Found %d events from %d new source IPs", len(new_ip_events), unique_new)
|
||||
return new_ip_events
|
||||
|
||||
|
||||
def detect_enumeration(events, threshold=20):
|
||||
"""Detect ListBucket/ListObjects enumeration patterns."""
|
||||
user_listings = defaultdict(int)
|
||||
for event in events:
|
||||
if event["event_name"] in ("ListBucket", "ListObjects", "ListObjectsV2"):
|
||||
user_listings[event["user_arn"]] += 1
|
||||
alerts = []
|
||||
for user_arn, count in user_listings.items():
|
||||
if count >= threshold:
|
||||
alerts.append({
|
||||
"user_arn": user_arn,
|
||||
"list_count": count,
|
||||
"severity": "high",
|
||||
"indicator": "Bucket enumeration spike (reconnaissance)",
|
||||
})
|
||||
return alerts
|
||||
|
||||
|
||||
def build_access_baseline(events):
|
||||
"""Build statistical baseline of normal access patterns."""
|
||||
hourly_counts = defaultdict(int)
|
||||
user_counts = defaultdict(int)
|
||||
ip_set = set()
|
||||
for event in events:
|
||||
try:
|
||||
ts = event["timestamp"]
|
||||
if isinstance(ts, str):
|
||||
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
hourly_counts[dt.hour] += 1
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
user_counts[event["user_arn"]] += 1
|
||||
if event["source_ip"]:
|
||||
ip_set.add(event["source_ip"])
|
||||
return {
|
||||
"hourly_distribution": dict(hourly_counts),
|
||||
"user_request_counts": dict(user_counts),
|
||||
"known_ips": list(ip_set),
|
||||
"total_events": len(events),
|
||||
}
|
||||
|
||||
|
||||
def generate_report(events, bulk_alerts, after_hours, new_ips, enum_alerts, baseline):
|
||||
"""Generate cloud storage access analysis report."""
|
||||
report = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"total_events_analyzed": len(events),
|
||||
"bulk_download_alerts": bulk_alerts,
|
||||
"after_hours_access": len(after_hours),
|
||||
"new_source_ip_events": len(new_ips),
|
||||
"enumeration_alerts": enum_alerts,
|
||||
"baseline_summary": {
|
||||
"known_ips": len(baseline.get("known_ips", [])),
|
||||
"total_baseline_events": baseline.get("total_events", 0),
|
||||
},
|
||||
"sample_after_hours": after_hours[:10],
|
||||
"sample_new_ips": new_ips[:10],
|
||||
}
|
||||
total_alerts = len(bulk_alerts) + len(enum_alerts) + (1 if new_ips else 0)
|
||||
print(f"CLOUD STORAGE REPORT: {len(events)} events, {total_alerts} alerts")
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Cloud Storage Access Pattern Analyzer")
|
||||
parser.add_argument("--bucket", default="", help="S3 bucket name to analyze")
|
||||
parser.add_argument("--hours-back", type=int, default=24)
|
||||
parser.add_argument("--bulk-threshold", type=int, default=100)
|
||||
parser.add_argument("--known-ips-file", help="File with known IP baselines")
|
||||
parser.add_argument("--output", default="s3_access_report.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
events = query_cloudtrail_s3_events(args.bucket, args.hours_back)
|
||||
baseline = build_access_baseline(events)
|
||||
known_ips = set(baseline.get("known_ips", []))
|
||||
if args.known_ips_file:
|
||||
with open(args.known_ips_file) as f:
|
||||
known_ips.update(line.strip() for line in f if line.strip())
|
||||
|
||||
bulk_alerts = detect_bulk_downloads(events, args.bulk_threshold)
|
||||
after_hours = detect_after_hours_access(events)
|
||||
new_ips = detect_new_source_ips(events, known_ips)
|
||||
enum_alerts = detect_enumeration(events)
|
||||
|
||||
report = generate_report(events, bulk_alerts, after_hours, new_ips, enum_alerts, baseline)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
logger.info("Report saved to %s", args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user