mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-18 21:49:40 +03:00
Add 5 new cybersecurity skills batch 2 - oauth token theft, binary exploitation, STIX2 sharing, linux audit logs, timestomping detection
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Mahipal
|
||||
|
||||
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,18 @@
|
||||
---
|
||||
name: detecting-oauth-token-theft
|
||||
description: >
|
||||
Detect OAuth access token theft and misuse by analyzing sign-in logs for
|
||||
impossible travel, new device patterns, token replay from unusual IPs,
|
||||
and anomalous scope requests via Microsoft Graph and Okta APIs.
|
||||
domain: cybersecurity
|
||||
subdomain: identity-security
|
||||
tags: [oauth, token-theft, identity-attacks, impossible-travel]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Detecting OAuth Token Theft
|
||||
|
||||
Analyze OAuth sign-in telemetry for indicators of token theft including
|
||||
impossible travel, device fingerprint changes, and token replay attacks.
|
||||
@@ -0,0 +1,51 @@
|
||||
# API Reference: Detecting OAuth Token Theft
|
||||
|
||||
## Microsoft Graph Sign-In Logs
|
||||
```bash
|
||||
# Query sign-in logs
|
||||
curl -H "Authorization: Bearer $MS_TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=createdDateTime ge 2025-01-01&\$top=100"
|
||||
```
|
||||
|
||||
### Sign-In Event Fields
|
||||
| Field | Description |
|
||||
|-------|------------|
|
||||
| userPrincipalName | User email/UPN |
|
||||
| ipAddress | Source IP address |
|
||||
| location.city | Geo city |
|
||||
| location.geoCoordinates | Lat/lon |
|
||||
| deviceDetail.deviceId | Device identifier |
|
||||
| resourceDisplayName | Target resource |
|
||||
| status.errorCode | 0 = success |
|
||||
| riskState | none, confirmedCompromised, remediated |
|
||||
|
||||
## Okta System Log API
|
||||
```bash
|
||||
# Query events
|
||||
curl -H "Authorization: SSWS $OKTA_TOKEN" \
|
||||
"https://your-org.okta.com/api/v1/logs?filter=eventType eq \"user.session.start\"&since=2025-01-01"
|
||||
```
|
||||
|
||||
## Detection Logic
|
||||
| Detection | Method |
|
||||
|-----------|--------|
|
||||
| Impossible travel | Haversine distance / time > 900 km/h |
|
||||
| Token replay | Same user, 3+ IPs within 5 min window |
|
||||
| New device | Device ID not in known device inventory |
|
||||
| Suspicious scopes | 2+ sensitive OAuth scopes requested |
|
||||
|
||||
## Sensitive OAuth Scopes (Microsoft)
|
||||
| Scope | Risk |
|
||||
|-------|------|
|
||||
| Mail.ReadWrite | Email access |
|
||||
| Mail.Send | Send-as capability |
|
||||
| Files.ReadWrite.All | Full file access |
|
||||
| Directory.ReadWrite.All | AD modification |
|
||||
| Application.ReadWrite.All | App registration |
|
||||
|
||||
## MITRE ATT&CK Mapping
|
||||
| Technique | Description |
|
||||
|-----------|------------|
|
||||
| T1528 | Steal Application Access Token |
|
||||
| T1550.001 | Application Access Token reuse |
|
||||
| T1078.004 | Cloud Accounts |
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
"""OAuth token theft detection agent.
|
||||
|
||||
Analyzes sign-in logs for impossible travel, new device sign-ins,
|
||||
token replay from unusual IPs, and anomalous scope requests.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
import datetime
|
||||
import collections
|
||||
|
||||
try:
|
||||
import requests
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
HAS_REQUESTS = False
|
||||
|
||||
|
||||
EARTH_RADIUS_KM = 6371
|
||||
|
||||
|
||||
def haversine(lat1, lon1, lat2, lon2):
|
||||
"""Calculate great-circle distance between two points in km."""
|
||||
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
|
||||
dlat = lat2 - lat1
|
||||
dlon = lon2 - lon1
|
||||
a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
|
||||
return 2 * EARTH_RADIUS_KM * math.asin(math.sqrt(a))
|
||||
|
||||
|
||||
def detect_impossible_travel(sign_ins, max_speed_kmh=900):
|
||||
"""Detect impossible travel based on geo and time between logins."""
|
||||
alerts = []
|
||||
by_user = collections.defaultdict(list)
|
||||
for event in sign_ins:
|
||||
by_user[event.get("user", "")].append(event)
|
||||
|
||||
for user, events in by_user.items():
|
||||
sorted_events = sorted(events, key=lambda e: e.get("timestamp", ""))
|
||||
for i in range(1, len(sorted_events)):
|
||||
prev, curr = sorted_events[i - 1], sorted_events[i]
|
||||
if not all(k in prev for k in ("lat", "lon")) or not all(k in curr for k in ("lat", "lon")):
|
||||
continue
|
||||
dist = haversine(prev["lat"], prev["lon"], curr["lat"], curr["lon"])
|
||||
try:
|
||||
t1 = datetime.datetime.fromisoformat(prev["timestamp"].replace("Z", "+00:00"))
|
||||
t2 = datetime.datetime.fromisoformat(curr["timestamp"].replace("Z", "+00:00"))
|
||||
hours = max((t2 - t1).total_seconds() / 3600, 0.001)
|
||||
except (ValueError, KeyError):
|
||||
continue
|
||||
speed = dist / hours
|
||||
if speed > max_speed_kmh and dist > 100:
|
||||
alerts.append({
|
||||
"type": "impossible_travel",
|
||||
"user": user,
|
||||
"from_ip": prev.get("ip", ""),
|
||||
"to_ip": curr.get("ip", ""),
|
||||
"distance_km": round(dist, 1),
|
||||
"time_hours": round(hours, 2),
|
||||
"speed_kmh": round(speed, 1),
|
||||
"severity": "HIGH",
|
||||
})
|
||||
return alerts
|
||||
|
||||
|
||||
def detect_token_replay(sign_ins):
|
||||
"""Detect token replay from multiple IPs in short timeframe."""
|
||||
alerts = []
|
||||
by_user = collections.defaultdict(list)
|
||||
for event in sign_ins:
|
||||
by_user[event.get("user", "")].append(event)
|
||||
|
||||
for user, events in by_user.items():
|
||||
sorted_events = sorted(events, key=lambda e: e.get("timestamp", ""))
|
||||
window = []
|
||||
for event in sorted_events:
|
||||
try:
|
||||
ts = datetime.datetime.fromisoformat(event["timestamp"].replace("Z", "+00:00"))
|
||||
except (ValueError, KeyError):
|
||||
continue
|
||||
window = [e for e in window
|
||||
if (ts - datetime.datetime.fromisoformat(
|
||||
e["timestamp"].replace("Z", "+00:00"))).total_seconds() < 300]
|
||||
window.append(event)
|
||||
unique_ips = set(e.get("ip") for e in window if e.get("ip"))
|
||||
if len(unique_ips) >= 3:
|
||||
alerts.append({
|
||||
"type": "token_replay",
|
||||
"user": user,
|
||||
"ips": list(unique_ips),
|
||||
"window_seconds": 300,
|
||||
"severity": "CRITICAL",
|
||||
})
|
||||
return alerts
|
||||
|
||||
|
||||
def detect_new_device(sign_ins, known_devices=None):
|
||||
"""Detect sign-ins from previously unseen devices."""
|
||||
known = set(known_devices or [])
|
||||
alerts = []
|
||||
for event in sign_ins:
|
||||
device_id = event.get("device_id", event.get("user_agent", ""))
|
||||
if device_id and device_id not in known:
|
||||
alerts.append({
|
||||
"type": "new_device",
|
||||
"user": event.get("user", ""),
|
||||
"device": device_id,
|
||||
"ip": event.get("ip", ""),
|
||||
"timestamp": event.get("timestamp", ""),
|
||||
"severity": "MEDIUM",
|
||||
})
|
||||
known.add(device_id)
|
||||
return alerts
|
||||
|
||||
|
||||
def detect_suspicious_scopes(sign_ins):
|
||||
"""Detect OAuth requests with overly broad or sensitive scopes."""
|
||||
sensitive_scopes = {
|
||||
"Mail.ReadWrite", "Mail.Send", "Files.ReadWrite.All",
|
||||
"Directory.ReadWrite.All", "User.ReadWrite.All",
|
||||
"Application.ReadWrite.All", "RoleManagement.ReadWrite.Directory",
|
||||
}
|
||||
alerts = []
|
||||
for event in sign_ins:
|
||||
scopes = set(event.get("scopes", []))
|
||||
dangerous = scopes & sensitive_scopes
|
||||
if len(dangerous) >= 2:
|
||||
alerts.append({
|
||||
"type": "suspicious_scopes",
|
||||
"user": event.get("user", ""),
|
||||
"scopes": list(dangerous),
|
||||
"app": event.get("app_name", ""),
|
||||
"severity": "HIGH",
|
||||
})
|
||||
return alerts
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="OAuth token theft detection agent")
|
||||
parser.add_argument("--log-file", help="JSON file with sign-in events")
|
||||
parser.add_argument("--max-speed", type=int, default=900, help="Max travel speed km/h (default: 900)")
|
||||
parser.add_argument("--output", "-o", help="Output JSON report path")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("[*] OAuth Token Theft Detection Agent")
|
||||
report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z", "alerts": []}
|
||||
|
||||
if args.log_file:
|
||||
with open(args.log_file) as f:
|
||||
sign_ins = json.load(f)
|
||||
else:
|
||||
sign_ins = [
|
||||
{"user": "alice@corp.com", "ip": "203.0.113.10", "lat": 40.7128, "lon": -74.0060,
|
||||
"timestamp": "2025-06-15T10:00:00Z", "device_id": "device-A"},
|
||||
{"user": "alice@corp.com", "ip": "198.51.100.50", "lat": 51.5074, "lon": -0.1278,
|
||||
"timestamp": "2025-06-15T10:30:00Z", "device_id": "device-B"},
|
||||
{"user": "bob@corp.com", "ip": "10.0.0.1", "lat": 37.7749, "lon": -122.4194,
|
||||
"timestamp": "2025-06-15T09:00:00Z", "device_id": "device-C",
|
||||
"scopes": ["Mail.ReadWrite", "Mail.Send", "Files.ReadWrite.All"]},
|
||||
]
|
||||
print("[DEMO] Using sample sign-in events")
|
||||
|
||||
report["alerts"].extend(detect_impossible_travel(sign_ins, args.max_speed))
|
||||
report["alerts"].extend(detect_token_replay(sign_ins))
|
||||
report["alerts"].extend(detect_new_device(sign_ins))
|
||||
report["alerts"].extend(detect_suspicious_scopes(sign_ins))
|
||||
|
||||
by_type = collections.Counter(a["type"] for a in report["alerts"])
|
||||
print(f"[*] Total alerts: {len(report['alerts'])}")
|
||||
for alert_type, count in by_type.items():
|
||||
print(f" {alert_type}: {count}")
|
||||
for a in report["alerts"]:
|
||||
print(f" [{a['severity']}] {a['type']}: {a.get('user', '')} - {a.get('distance_km', a.get('ips', a.get('device', '')))}")
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(json.dumps({"total_alerts": len(report["alerts"]), "by_type": dict(by_type)}, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user