mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-16 16:03:17 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
326 lines
13 KiB
Python
326 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""Application cryptographic audit agent.
|
|
|
|
Audits application code and configurations for cryptographic weaknesses
|
|
including weak algorithms, insecure key sizes, hardcoded keys, deprecated
|
|
protocols, and missing certificate validation. Scans source files, config
|
|
files, and TLS endpoints.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import ssl
|
|
import socket
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
try:
|
|
from cryptography import x509
|
|
from cryptography.hazmat.backends import default_backend
|
|
HAS_CRYPTO = True
|
|
except ImportError:
|
|
HAS_CRYPTO = False
|
|
|
|
|
|
WEAK_PATTERNS = {
|
|
"weak_hash": {
|
|
"pattern": r'\b(MD5|md5|SHA1|sha1|SHA-1)\b(?!.*hmac)',
|
|
"severity": "HIGH",
|
|
"description": "Weak hash algorithm detected (MD5/SHA1)",
|
|
"recommendation": "Use SHA-256 or SHA-3",
|
|
},
|
|
"weak_cipher": {
|
|
"pattern": r'\b(DES|3DES|RC4|RC2|Blowfish|IDEA)\b',
|
|
"severity": "HIGH",
|
|
"description": "Weak cipher algorithm detected",
|
|
"recommendation": "Use AES-256-GCM or ChaCha20-Poly1305",
|
|
},
|
|
"ecb_mode": {
|
|
"pattern": r'\b(ECB|MODE_ECB|ecb)\b',
|
|
"severity": "CRITICAL",
|
|
"description": "ECB mode detected - does not provide semantic security",
|
|
"recommendation": "Use GCM, CBC with HMAC, or CTR mode",
|
|
},
|
|
"hardcoded_key": {
|
|
"pattern": r'(?:key|secret|password|token)\s*[=:]\s*["\'][A-Za-z0-9+/=]{16,}["\']',
|
|
"severity": "CRITICAL",
|
|
"description": "Possible hardcoded cryptographic key or secret",
|
|
"recommendation": "Use environment variables or a secrets manager",
|
|
},
|
|
"small_rsa_key": {
|
|
"pattern": r'(?:key_?size|bits)\s*[=:]\s*(?:512|768|1024)\b',
|
|
"severity": "CRITICAL",
|
|
"description": "RSA key size too small (<2048 bits)",
|
|
"recommendation": "Use minimum 2048-bit, preferably 4096-bit RSA keys",
|
|
},
|
|
"weak_random": {
|
|
"pattern": r'\b(?:random\.random|math\.random|Math\.random|rand\(\)|srand)\b',
|
|
"severity": "HIGH",
|
|
"description": "Non-cryptographic random number generator used",
|
|
"recommendation": "Use secrets module, os.urandom, or crypto.getRandomValues",
|
|
},
|
|
"ssl_no_verify": {
|
|
"pattern": r'(?:verify\s*=\s*False|CERT_NONE|SSL_VERIFY_NONE|InsecureRequestWarning|verify_ssl\s*=\s*False)',
|
|
"severity": "HIGH",
|
|
"description": "TLS certificate verification disabled",
|
|
"recommendation": "Enable certificate verification in all TLS connections",
|
|
},
|
|
"tls_v1": {
|
|
"pattern": r'(?:TLSv1[^._23]|SSLv[23]|PROTOCOL_TLS(?!v1_[23])|TLS_1_0|TLS_1_1)',
|
|
"severity": "HIGH",
|
|
"description": "Deprecated TLS/SSL protocol version detected",
|
|
"recommendation": "Use TLS 1.2 or TLS 1.3 minimum",
|
|
},
|
|
"padding_oracle": {
|
|
"pattern": r'(?:PKCS1v15|pkcs1_v1_5|PKCS5Padding)(?!.*OAEP)',
|
|
"severity": "MEDIUM",
|
|
"description": "PKCS#1 v1.5 padding used (vulnerable to padding oracle attacks)",
|
|
"recommendation": "Use OAEP padding for RSA, or switch to AEAD ciphers",
|
|
},
|
|
"static_iv": {
|
|
"pattern": r'(?:iv|IV|nonce)\s*[=:]\s*(?:b?["\'][^"\']{1,32}["\']|bytes\([^)]*\))',
|
|
"severity": "HIGH",
|
|
"description": "Possible static/hardcoded IV or nonce",
|
|
"recommendation": "Generate random IV/nonce for each encryption operation",
|
|
},
|
|
}
|
|
|
|
SCAN_EXTENSIONS = {
|
|
".py", ".js", ".ts", ".java", ".go", ".rb", ".php", ".cs", ".c", ".cpp",
|
|
".h", ".yaml", ".yml", ".json", ".xml", ".conf", ".cfg", ".ini", ".env",
|
|
".toml", ".properties",
|
|
}
|
|
|
|
|
|
def scan_file(file_path):
|
|
"""Scan a single file for cryptographic weaknesses."""
|
|
findings = []
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
lines = f.readlines()
|
|
except (IOError, PermissionError):
|
|
return findings
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
for rule_id, rule in WEAK_PATTERNS.items():
|
|
if re.search(rule["pattern"], line):
|
|
findings.append({
|
|
"rule": rule_id,
|
|
"file": file_path,
|
|
"line": line_num,
|
|
"content": line.strip()[:120],
|
|
"severity": rule["severity"],
|
|
"description": rule["description"],
|
|
"recommendation": rule["recommendation"],
|
|
})
|
|
return findings
|
|
|
|
|
|
def scan_directory(directory, extensions=None, exclude_dirs=None):
|
|
"""Recursively scan a directory for cryptographic weaknesses."""
|
|
if extensions is None:
|
|
extensions = SCAN_EXTENSIONS
|
|
if exclude_dirs is None:
|
|
exclude_dirs = {"node_modules", ".git", "__pycache__", "venv", ".venv",
|
|
"vendor", "dist", "build", ".tox"}
|
|
|
|
all_findings = []
|
|
files_scanned = 0
|
|
|
|
for root, dirs, files in os.walk(directory):
|
|
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
|
for fname in files:
|
|
ext = os.path.splitext(fname)[1].lower()
|
|
if ext not in extensions:
|
|
continue
|
|
full_path = os.path.join(root, fname)
|
|
findings = scan_file(full_path)
|
|
all_findings.extend(findings)
|
|
files_scanned += 1
|
|
|
|
print(f"[+] Scanned {files_scanned} files, found {len(all_findings)} issues")
|
|
return all_findings, files_scanned
|
|
|
|
|
|
def audit_tls_endpoint(host, port=443):
|
|
"""Audit TLS configuration of a remote endpoint."""
|
|
findings = []
|
|
print(f"[*] Auditing TLS: {host}:{port}")
|
|
|
|
try:
|
|
context = ssl.create_default_context()
|
|
with socket.create_connection((host, port), timeout=10) as sock:
|
|
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
|
cert_der = ssock.getpeercert(binary_form=True)
|
|
cipher = ssock.cipher()
|
|
protocol = ssock.version()
|
|
|
|
findings.append({
|
|
"check": "TLS Protocol",
|
|
"value": protocol,
|
|
"severity": "CRITICAL" if "TLSv1.0" in protocol or "TLSv1.1" in protocol
|
|
else "INFO",
|
|
"description": f"Negotiated protocol: {protocol}",
|
|
})
|
|
|
|
if cipher:
|
|
cipher_name, tls_ver, key_bits = cipher
|
|
findings.append({
|
|
"check": "Cipher Suite",
|
|
"value": cipher_name,
|
|
"severity": "HIGH" if any(w in cipher_name for w in ["RC4", "DES", "NULL", "EXPORT"])
|
|
else "INFO",
|
|
"description": f"Cipher: {cipher_name} ({key_bits} bits)",
|
|
})
|
|
|
|
if HAS_CRYPTO and cert_der:
|
|
cert = x509.load_der_x509_certificate(cert_der, default_backend())
|
|
not_after = cert.not_valid_after_utc if hasattr(cert, 'not_valid_after_utc') else cert.not_valid_after
|
|
days_remaining = (not_after - datetime.now(timezone.utc)).days
|
|
|
|
pub_key = cert.public_key()
|
|
key_size = getattr(pub_key, 'key_size', 0)
|
|
sig_algo = cert.signature_algorithm_oid._name if hasattr(cert.signature_algorithm_oid, '_name') else str(cert.signature_hash_algorithm)
|
|
|
|
findings.append({
|
|
"check": "Certificate Expiry",
|
|
"value": f"{days_remaining} days",
|
|
"severity": "CRITICAL" if days_remaining < 0
|
|
else "HIGH" if days_remaining < 30
|
|
else "MEDIUM" if days_remaining < 90
|
|
else "INFO",
|
|
"description": f"Expires: {not_after.isoformat()} ({days_remaining} days)",
|
|
})
|
|
findings.append({
|
|
"check": "Key Size",
|
|
"value": f"{key_size} bits",
|
|
"severity": "CRITICAL" if key_size < 2048
|
|
else "MEDIUM" if key_size < 4096
|
|
else "INFO",
|
|
})
|
|
findings.append({
|
|
"check": "Signature Algorithm",
|
|
"value": str(sig_algo),
|
|
"severity": "HIGH" if "sha1" in str(sig_algo).lower() else "INFO",
|
|
})
|
|
|
|
except ssl.SSLError as e:
|
|
findings.append({"check": "TLS Connection", "severity": "CRITICAL",
|
|
"description": f"SSL error: {e}"})
|
|
except socket.timeout:
|
|
findings.append({"check": "TLS Connection", "severity": "HIGH",
|
|
"description": "Connection timed out"})
|
|
except Exception as e:
|
|
findings.append({"check": "TLS Connection", "severity": "HIGH",
|
|
"description": f"Error: {e}"})
|
|
|
|
return findings
|
|
|
|
|
|
def format_summary(code_findings, tls_findings, files_scanned, target):
|
|
"""Print audit summary."""
|
|
all_findings = code_findings + tls_findings
|
|
print(f"\n{'='*60}")
|
|
print(f" Cryptographic Audit Report")
|
|
print(f"{'='*60}")
|
|
print(f" Target : {target}")
|
|
print(f" Files Scanned : {files_scanned}")
|
|
print(f" Code Findings : {len(code_findings)}")
|
|
print(f" TLS Findings : {len(tls_findings)}")
|
|
|
|
severity_counts = {}
|
|
for f in all_findings:
|
|
sev = f.get("severity", "INFO")
|
|
severity_counts[sev] = severity_counts.get(sev, 0) + 1
|
|
|
|
print(f"\n By Severity:")
|
|
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
|
|
count = severity_counts.get(sev, 0)
|
|
if count > 0:
|
|
print(f" {sev:10s}: {count}")
|
|
|
|
if code_findings:
|
|
by_rule = {}
|
|
for f in code_findings:
|
|
by_rule.setdefault(f["rule"], []).append(f)
|
|
print(f"\n Code Issues by Rule:")
|
|
for rule, items in sorted(by_rule.items(), key=lambda x: -len(x[1])):
|
|
print(f" {rule:25s}: {len(items)} ({items[0]['severity']})")
|
|
|
|
print(f"\n Top Code Findings:")
|
|
for f in code_findings[:15]:
|
|
if f["severity"] in ("CRITICAL", "HIGH"):
|
|
print(f" [{f['severity']:8s}] {f['file']}:{f['line']} - {f['description']}")
|
|
|
|
if tls_findings:
|
|
print(f"\n TLS Audit Results:")
|
|
for f in tls_findings:
|
|
print(f" [{f['severity']:8s}] {f.get('check', '')}: "
|
|
f"{f.get('value', f.get('description', ''))}")
|
|
|
|
return severity_counts
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Application cryptographic audit agent"
|
|
)
|
|
parser.add_argument("--target", required=True,
|
|
help="Source directory to scan or TLS host to audit")
|
|
parser.add_argument("--tls-port", type=int, default=443,
|
|
help="TLS port for endpoint audit (default: 443)")
|
|
parser.add_argument("--tls-only", action="store_true",
|
|
help="Only audit TLS endpoint, skip code scan")
|
|
parser.add_argument("--code-only", action="store_true",
|
|
help="Only scan code, skip TLS audit")
|
|
parser.add_argument("--exclude-dirs", nargs="+",
|
|
help="Additional directories to exclude from scan")
|
|
parser.add_argument("--output", "-o", help="Output JSON report path")
|
|
parser.add_argument("--verbose", "-v", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
code_findings = []
|
|
tls_findings = []
|
|
files_scanned = 0
|
|
|
|
if not args.tls_only and os.path.isdir(args.target):
|
|
exclude = {"node_modules", ".git", "__pycache__", "venv", ".venv", "vendor"}
|
|
if args.exclude_dirs:
|
|
exclude.update(args.exclude_dirs)
|
|
code_findings, files_scanned = scan_directory(args.target, exclude_dirs=exclude)
|
|
|
|
if not args.code_only and not os.path.isdir(args.target):
|
|
tls_findings = audit_tls_endpoint(args.target, args.tls_port)
|
|
elif not args.code_only and os.path.isdir(args.target):
|
|
pass # Can't audit TLS on a directory
|
|
|
|
severity_counts = format_summary(code_findings, tls_findings, files_scanned, args.target)
|
|
|
|
report = {
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"tool": "Crypto Audit",
|
|
"target": args.target,
|
|
"files_scanned": files_scanned,
|
|
"severity_counts": severity_counts,
|
|
"code_findings": code_findings,
|
|
"tls_findings": tls_findings,
|
|
"risk_level": (
|
|
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
|
|
else "HIGH" if severity_counts.get("HIGH", 0) > 0
|
|
else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0
|
|
else "LOW"
|
|
),
|
|
}
|
|
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
elif args.verbose:
|
|
print(json.dumps(report, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|