mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-31 00:17:41 +03:00
feat: add 5 new cybersecurity skills - Azure storage audit, supply chain simulation, Azure PIM, Empire artifacts, NTLM relay
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright 2025 Mahipal
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: performing-supply-chain-attack-simulation
|
||||
description: Simulate and detect software supply chain attacks including typosquatting detection via Levenshtein distance, dependency confusion testing against private registries, package hash verification with pip, and known vulnerability scanning with pip-audit.
|
||||
domain: cybersecurity
|
||||
subdomain: application-security
|
||||
tags: [supply-chain, typosquatting, dependency-confusion, package-verification, pip-audit, PyPI, software-composition-analysis]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Performing Supply Chain Attack Simulation
|
||||
|
||||
## Overview
|
||||
|
||||
Software supply chain attacks exploit trust in package registries through typosquatting (registering names similar to popular packages), dependency confusion (publishing higher-version public packages matching private names), and compromised package distribution. This skill detects these attack vectors by computing Levenshtein distance between package names and popular PyPI packages, verifying package integrity via SHA-256 hash comparison, scanning for known CVEs with pip-audit, and testing dependency resolution order for confusion vulnerabilities.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `pip-audit`, `Levenshtein`, `requests`
|
||||
- Access to PyPI JSON API (https://pypi.org/pypi/{package}/json)
|
||||
- Network access for package metadata retrieval
|
||||
|
||||
## Key Detection Areas
|
||||
|
||||
1. **Typosquatting** — compare package names against top PyPI packages using edit distance thresholds
|
||||
2. **Dependency confusion** — check if internal package names exist on public PyPI with higher version numbers
|
||||
3. **Hash verification** — download packages and verify SHA-256 digests match published hashes
|
||||
4. **Vulnerability scanning** — audit installed packages against OSV and PyPA advisory databases
|
||||
5. **Metadata anomalies** — flag packages with suspicious author emails, missing homepages, or very recent first upload dates
|
||||
|
||||
## Output
|
||||
|
||||
JSON report with risk scores per package, detected attack vectors, hash verification results, and CVE findings.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Supply Chain Attack Simulation Reference
|
||||
|
||||
## Tool Installation
|
||||
|
||||
```bash
|
||||
pip install pip-audit python-Levenshtein requests
|
||||
```
|
||||
|
||||
## PyPI JSON API
|
||||
|
||||
```bash
|
||||
# Get package metadata
|
||||
curl https://pypi.org/pypi/{package_name}/json
|
||||
|
||||
# Get specific version
|
||||
curl https://pypi.org/pypi/{package_name}/{version}/json
|
||||
```
|
||||
|
||||
### Response Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"info": {
|
||||
"name": "requests",
|
||||
"version": "2.31.0",
|
||||
"author": "Kenneth Reitz",
|
||||
"author_email": "me@kennethreitz.org",
|
||||
"home_page": "https://requests.readthedocs.io",
|
||||
"summary": "Python HTTP for Humans."
|
||||
},
|
||||
"urls": [
|
||||
{
|
||||
"filename": "requests-2.31.0.tar.gz",
|
||||
"packagetype": "sdist",
|
||||
"digests": {
|
||||
"sha256": "942c5a758f98d790eaed1a29cb6eefc7f0edf3fcb0fce8aea3fbd5951d bfcfeb"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## pip-audit CLI
|
||||
|
||||
```bash
|
||||
# Audit current environment
|
||||
pip-audit
|
||||
|
||||
# JSON output
|
||||
pip-audit --format json
|
||||
|
||||
# Audit requirements file
|
||||
pip-audit -r requirements.txt
|
||||
|
||||
# Hash-checking mode (pinned deps only)
|
||||
pip-audit --require-hashes -r requirements.txt
|
||||
|
||||
# Fix vulnerabilities automatically
|
||||
pip-audit --fix
|
||||
```
|
||||
|
||||
## pip Hash Verification
|
||||
|
||||
```bash
|
||||
# Download with hash verification
|
||||
pip download --no-deps --require-hashes -r requirements.txt
|
||||
|
||||
# requirements.txt with hashes
|
||||
requests==2.31.0 \
|
||||
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7f0edf3fcb0fce8aea3fbd5951dbfcfeb
|
||||
```
|
||||
|
||||
## pypi-scan Typosquatting Detection
|
||||
|
||||
```bash
|
||||
# Install
|
||||
pip install pypi-scan
|
||||
|
||||
# Scan for typosquatting of a package
|
||||
pypi-scan --package requests
|
||||
|
||||
# Scan with custom edit distance
|
||||
pypi-scan --package numpy --edit-distance 2
|
||||
```
|
||||
|
||||
## Levenshtein Distance Examples
|
||||
|
||||
| Package | Target | Distance | Risk |
|
||||
|---------|--------|----------|------|
|
||||
| `reqeusts` | `requests` | 1 | High |
|
||||
| `requets` | `requests` | 1 | High |
|
||||
| `request` | `requests` | 1 | High |
|
||||
| `numpys` | `numpy` | 1 | High |
|
||||
| `pandsa` | `pandas` | 1 | High |
|
||||
| `flaask` | `flask` | 1 | High |
|
||||
|
||||
## Dependency Confusion Test Structure
|
||||
|
||||
```json
|
||||
[
|
||||
{"name": "my-internal-lib", "version": "1.2.0"},
|
||||
{"name": "company-utils", "version": "0.5.3"},
|
||||
{"name": "private-auth-sdk", "version": "2.1.0"}
|
||||
]
|
||||
```
|
||||
|
||||
## MITRE ATT&CK Mapping
|
||||
|
||||
| Technique | ID | Description |
|
||||
|-----------|-----|-------------|
|
||||
| Supply Chain Compromise | T1195.001 | Compromise software dependencies |
|
||||
| Trusted Developer Utilities | T1127 | Abuse package manager trust |
|
||||
| Ingress Tool Transfer | T1105 | Download malicious packages |
|
||||
|
||||
## Metadata Anomaly Indicators
|
||||
|
||||
| Indicator | Risk | Description |
|
||||
|-----------|------|-------------|
|
||||
| Disposable email author | High | Author uses throwaway email service |
|
||||
| No homepage/repo URL | Medium | Package has no verifiable source |
|
||||
| No author info | Medium | Anonymous package publication |
|
||||
| Very short description | Low | Minimal package documentation |
|
||||
| Recent first upload + popular name variant | Critical | Likely typosquatting attempt |
|
||||
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Simulate and detect software supply chain attacks: typosquatting, dependency confusion, hash verification."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def get_levenshtein_distance(s1, s2):
|
||||
"""Compute Levenshtein edit distance between two strings."""
|
||||
if len(s1) < len(s2):
|
||||
return get_levenshtein_distance(s2, s1)
|
||||
if len(s2) == 0:
|
||||
return len(s1)
|
||||
prev_row = range(len(s2) + 1)
|
||||
for i, c1 in enumerate(s1):
|
||||
curr_row = [i + 1]
|
||||
for j, c2 in enumerate(s2):
|
||||
insertions = prev_row[j + 1] + 1
|
||||
deletions = curr_row[j] + 1
|
||||
substitutions = prev_row[j] + (c1 != c2)
|
||||
curr_row.append(min(insertions, deletions, substitutions))
|
||||
prev_row = curr_row
|
||||
return prev_row[-1]
|
||||
|
||||
|
||||
TOP_PYPI_PACKAGES = [
|
||||
"requests", "numpy", "pandas", "flask", "django", "boto3", "scipy",
|
||||
"tensorflow", "torch", "scikit-learn", "pillow", "matplotlib",
|
||||
"cryptography", "pyyaml", "sqlalchemy", "celery", "redis", "psycopg2",
|
||||
"paramiko", "beautifulsoup4", "selenium", "pytest", "setuptools",
|
||||
"urllib3", "certifi", "idna", "charset-normalizer", "pip", "wheel",
|
||||
"packaging", "six", "python-dateutil", "jinja2", "markupsafe",
|
||||
"pydantic", "fastapi", "uvicorn", "httpx", "aiohttp", "grpcio"
|
||||
]
|
||||
|
||||
|
||||
def check_typosquatting(package_name, threshold=2):
|
||||
"""Check if package name is suspiciously similar to popular packages."""
|
||||
matches = []
|
||||
for popular in TOP_PYPI_PACKAGES:
|
||||
if package_name == popular:
|
||||
continue
|
||||
distance = get_levenshtein_distance(package_name.lower(), popular.lower())
|
||||
if 0 < distance <= threshold:
|
||||
matches.append({
|
||||
"popular_package": popular,
|
||||
"edit_distance": distance,
|
||||
"risk": "High" if distance == 1 else "Medium"
|
||||
})
|
||||
return matches
|
||||
|
||||
|
||||
def query_pypi_metadata(package_name):
|
||||
"""Fetch package metadata from PyPI JSON API."""
|
||||
try:
|
||||
import requests
|
||||
resp = requests.get(
|
||||
f"https://pypi.org/pypi/{package_name}/json",
|
||||
timeout=10
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def check_dependency_confusion(private_packages):
|
||||
"""Check if private package names exist on public PyPI."""
|
||||
findings = []
|
||||
for pkg_info in private_packages:
|
||||
name = pkg_info["name"]
|
||||
internal_version = pkg_info.get("version", "0.0.0")
|
||||
metadata = query_pypi_metadata(name)
|
||||
if metadata:
|
||||
public_version = metadata.get("info", {}).get("version", "0.0.0")
|
||||
findings.append({
|
||||
"package": name,
|
||||
"internal_version": internal_version,
|
||||
"public_version": public_version,
|
||||
"risk": "Critical",
|
||||
"message": f"Private package '{name}' exists on public PyPI as version {public_version}",
|
||||
"attack_vector": "dependency_confusion"
|
||||
})
|
||||
else:
|
||||
findings.append({
|
||||
"package": name,
|
||||
"internal_version": internal_version,
|
||||
"risk": "Info",
|
||||
"message": f"Private package '{name}' not found on public PyPI (safe)"
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def verify_package_hash(package_name, expected_hash=None):
|
||||
"""Download package and verify SHA-256 hash against PyPI published digests."""
|
||||
metadata = query_pypi_metadata(package_name)
|
||||
if not metadata:
|
||||
return {"package": package_name, "status": "error", "message": "Package not found on PyPI"}
|
||||
|
||||
releases = metadata.get("urls", [])
|
||||
if not releases:
|
||||
return {"package": package_name, "status": "error", "message": "No release files found"}
|
||||
|
||||
sdist = None
|
||||
for release in releases:
|
||||
if release.get("packagetype") == "sdist":
|
||||
sdist = release
|
||||
break
|
||||
if not sdist:
|
||||
sdist = releases[0]
|
||||
|
||||
published_sha256 = sdist.get("digests", {}).get("sha256", "")
|
||||
result = {
|
||||
"package": package_name,
|
||||
"version": metadata["info"]["version"],
|
||||
"filename": sdist["filename"],
|
||||
"published_sha256": published_sha256,
|
||||
"packagetype": sdist["packagetype"]
|
||||
}
|
||||
|
||||
if expected_hash:
|
||||
if expected_hash == published_sha256:
|
||||
result["status"] = "verified"
|
||||
result["message"] = "Hash matches expected value"
|
||||
else:
|
||||
result["status"] = "mismatch"
|
||||
result["risk"] = "Critical"
|
||||
result["message"] = "Hash does NOT match expected value — possible tampering"
|
||||
result["expected_hash"] = expected_hash
|
||||
else:
|
||||
result["status"] = "retrieved"
|
||||
result["message"] = "Published hash retrieved for manual verification"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def run_pip_audit():
|
||||
"""Run pip-audit to scan installed packages for known vulnerabilities."""
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
["pip-audit", "--format", "json", "--progress-spinner", "off"],
|
||||
capture_output=True, text=True, timeout=120
|
||||
)
|
||||
if proc.returncode == 0 or proc.stdout:
|
||||
return json.loads(proc.stdout) if proc.stdout.strip() else []
|
||||
return [{"error": proc.stderr.strip()}]
|
||||
except FileNotFoundError:
|
||||
return [{"error": "pip-audit not installed. Run: pip install pip-audit"}]
|
||||
except subprocess.TimeoutExpired:
|
||||
return [{"error": "pip-audit timed out after 120 seconds"}]
|
||||
except json.JSONDecodeError:
|
||||
return [{"error": "Failed to parse pip-audit output"}]
|
||||
|
||||
|
||||
def analyze_metadata_anomalies(package_name):
|
||||
"""Detect suspicious metadata patterns in a PyPI package."""
|
||||
metadata = query_pypi_metadata(package_name)
|
||||
if not metadata:
|
||||
return {"package": package_name, "status": "not_found"}
|
||||
|
||||
info = metadata["info"]
|
||||
anomalies = []
|
||||
|
||||
if not info.get("home_page") and not info.get("project_url"):
|
||||
anomalies.append({
|
||||
"check": "missing_homepage",
|
||||
"severity": "Medium",
|
||||
"message": "Package has no homepage or project URL"
|
||||
})
|
||||
|
||||
if not info.get("author") and not info.get("author_email"):
|
||||
anomalies.append({
|
||||
"check": "missing_author",
|
||||
"severity": "Medium",
|
||||
"message": "Package has no author information"
|
||||
})
|
||||
|
||||
if info.get("author_email") and any(
|
||||
domain in info["author_email"]
|
||||
for domain in ["mailinator.com", "guerrillamail.com", "tempmail.com", "throwaway.email"]
|
||||
):
|
||||
anomalies.append({
|
||||
"check": "disposable_email",
|
||||
"severity": "High",
|
||||
"message": f"Author uses disposable email: {info['author_email']}"
|
||||
})
|
||||
|
||||
summary = info.get("summary", "")
|
||||
if not summary or len(summary) < 10:
|
||||
anomalies.append({
|
||||
"check": "missing_description",
|
||||
"severity": "Low",
|
||||
"message": "Package has no meaningful description"
|
||||
})
|
||||
|
||||
return {
|
||||
"package": package_name,
|
||||
"version": info.get("version"),
|
||||
"author": info.get("author"),
|
||||
"author_email": info.get("author_email"),
|
||||
"anomalies": anomalies,
|
||||
"anomaly_count": len(anomalies)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Simulate and detect software supply chain attacks"
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", help="Attack simulation type")
|
||||
|
||||
typo_parser = subparsers.add_parser("typosquat", help="Check for typosquatting")
|
||||
typo_parser.add_argument("packages", nargs="+", help="Package names to check")
|
||||
typo_parser.add_argument("--threshold", type=int, default=2, help="Max edit distance (default: 2)")
|
||||
|
||||
confusion_parser = subparsers.add_parser("confusion", help="Test dependency confusion")
|
||||
confusion_parser.add_argument("--packages", required=True, help="JSON file with private packages [{name, version}]")
|
||||
|
||||
hash_parser = subparsers.add_parser("verify-hash", help="Verify package hash")
|
||||
hash_parser.add_argument("package", help="Package name")
|
||||
hash_parser.add_argument("--expected-hash", help="Expected SHA-256 hash to compare")
|
||||
|
||||
subparsers.add_parser("audit", help="Run pip-audit vulnerability scan")
|
||||
|
||||
meta_parser = subparsers.add_parser("metadata", help="Check metadata anomalies")
|
||||
meta_parser.add_argument("packages", nargs="+", help="Package names to analyze")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "typosquat":
|
||||
results = []
|
||||
for pkg in args.packages:
|
||||
matches = check_typosquatting(pkg, args.threshold)
|
||||
results.append({"package": pkg, "typosquat_matches": matches, "is_suspicious": len(matches) > 0})
|
||||
print(json.dumps({"scan_type": "typosquatting", "results": results, "timestamp": datetime.now(timezone.utc).isoformat()}, indent=2))
|
||||
|
||||
elif args.command == "confusion":
|
||||
with open(args.packages) as f:
|
||||
private_pkgs = json.load(f)
|
||||
results = check_dependency_confusion(private_pkgs)
|
||||
print(json.dumps({"scan_type": "dependency_confusion", "results": results, "timestamp": datetime.now(timezone.utc).isoformat()}, indent=2))
|
||||
|
||||
elif args.command == "verify-hash":
|
||||
result = verify_package_hash(args.package, args.expected_hash)
|
||||
print(json.dumps({"scan_type": "hash_verification", "result": result, "timestamp": datetime.now(timezone.utc).isoformat()}, indent=2))
|
||||
|
||||
elif args.command == "audit":
|
||||
results = run_pip_audit()
|
||||
print(json.dumps({"scan_type": "vulnerability_audit", "results": results, "timestamp": datetime.now(timezone.utc).isoformat()}, indent=2))
|
||||
|
||||
elif args.command == "metadata":
|
||||
results = [analyze_metadata_anomalies(pkg) for pkg in args.packages]
|
||||
print(json.dumps({"scan_type": "metadata_analysis", "results": results, "timestamp": datetime.now(timezone.utc).isoformat()}, indent=2))
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user