mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +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
183 lines
6.7 KiB
Python
183 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
# For authorized penetration testing and lab environments only
|
|
"""Forced Browsing Authentication Bypass Agent - Tests for unprotected endpoints."""
|
|
|
|
import json
|
|
import logging
|
|
import argparse
|
|
from datetime import datetime
|
|
from urllib.parse import urljoin
|
|
|
|
import requests
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DEFAULT_ADMIN_PATHS = [
|
|
"/admin", "/administrator", "/admin-panel", "/wp-admin", "/cpanel",
|
|
"/phpmyadmin", "/adminer", "/manager", "/console", "/debug",
|
|
"/actuator", "/actuator/env", "/actuator/health", "/actuator/beans",
|
|
"/swagger-ui", "/swagger-ui.html", "/api-docs", "/graphql", "/graphiql",
|
|
"/.env", "/server-status", "/server-info", "/.git/HEAD", "/.git/config",
|
|
"/web.config", "/phpinfo.php", "/robots.txt", "/sitemap.xml",
|
|
]
|
|
|
|
SENSITIVE_EXTENSIONS = [
|
|
".bak", ".old", ".orig", ".save", ".swp", ".tmp", ".config",
|
|
".sql", ".gz", ".tar", ".zip", ".env",
|
|
]
|
|
|
|
|
|
def load_wordlist(wordlist_path):
|
|
"""Load directory/file wordlist from file."""
|
|
with open(wordlist_path, "r") as f:
|
|
return [line.strip() for line in f if line.strip() and not line.startswith("#")]
|
|
|
|
|
|
def test_endpoint(base_url, path, session_cookie=None, timeout=10):
|
|
"""Test a single endpoint with and without authentication."""
|
|
url = urljoin(base_url, path)
|
|
unauth_resp = requests.get(url, timeout=timeout, allow_redirects=False, verify=False)
|
|
|
|
auth_resp = None
|
|
if session_cookie:
|
|
auth_resp = requests.get(
|
|
url, cookies={"session": session_cookie},
|
|
timeout=timeout, allow_redirects=False, verify=False,
|
|
)
|
|
|
|
result = {
|
|
"path": path,
|
|
"url": url,
|
|
"unauth_status": unauth_resp.status_code,
|
|
"unauth_size": len(unauth_resp.content),
|
|
}
|
|
if auth_resp:
|
|
result["auth_status"] = auth_resp.status_code
|
|
result["auth_size"] = len(auth_resp.content)
|
|
result["auth_bypass"] = (
|
|
unauth_resp.status_code == 200 and auth_resp.status_code == 200
|
|
and abs(result["unauth_size"] - result["auth_size"]) < 100
|
|
)
|
|
return result
|
|
|
|
|
|
def enumerate_directories(base_url, wordlist, session_cookie=None):
|
|
"""Enumerate directories and test authentication enforcement."""
|
|
findings = []
|
|
for word in wordlist:
|
|
path = f"/{word}" if not word.startswith("/") else word
|
|
try:
|
|
result = test_endpoint(base_url, path, session_cookie)
|
|
if result["unauth_status"] in (200, 301, 302, 403):
|
|
findings.append(result)
|
|
logger.info(
|
|
"Found: %s (status: %d, size: %d)",
|
|
path, result["unauth_status"], result["unauth_size"],
|
|
)
|
|
except requests.RequestException:
|
|
continue
|
|
return findings
|
|
|
|
|
|
def test_http_method_bypass(base_url, path):
|
|
"""Test HTTP method-based authentication bypass."""
|
|
methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]
|
|
results = {}
|
|
for method in methods:
|
|
try:
|
|
resp = requests.request(method, urljoin(base_url, path), timeout=10, verify=False)
|
|
results[method] = resp.status_code
|
|
except requests.RequestException:
|
|
results[method] = None
|
|
logger.info("Method bypass test for %s: %s", path, results)
|
|
return results
|
|
|
|
|
|
def test_path_traversal_bypass(base_url, path):
|
|
"""Test path normalization bypass techniques."""
|
|
variants = [
|
|
path,
|
|
path.upper(),
|
|
path.replace("/", "/./"),
|
|
f"/public/..{path}",
|
|
path.replace("/", "%2f"),
|
|
f"/;{path}",
|
|
f"/.;{path}",
|
|
f"{path}/",
|
|
f"{path}.json",
|
|
]
|
|
results = []
|
|
for variant in variants:
|
|
try:
|
|
resp = requests.get(urljoin(base_url, variant), timeout=10, verify=False)
|
|
results.append({"path": variant, "status": resp.status_code, "size": len(resp.content)})
|
|
except requests.RequestException:
|
|
continue
|
|
return results
|
|
|
|
|
|
def check_sensitive_files(base_url):
|
|
"""Check for exposed backup and configuration files."""
|
|
sensitive_paths = [
|
|
".env", ".git/HEAD", ".git/config", "web.config", "wp-config.php.bak",
|
|
"config.php.old", ".htpasswd", "database.yml", "phpinfo.php",
|
|
]
|
|
exposed = []
|
|
for path in sensitive_paths:
|
|
try:
|
|
resp = requests.get(urljoin(base_url, path), timeout=10, verify=False)
|
|
if resp.status_code == 200 and len(resp.content) > 0:
|
|
exposed.append({"path": path, "status": resp.status_code, "size": len(resp.content)})
|
|
logger.warning("EXPOSED: %s (size: %d bytes)", path, len(resp.content))
|
|
except requests.RequestException:
|
|
continue
|
|
return exposed
|
|
|
|
|
|
def generate_report(findings, method_results, sensitive_files):
|
|
"""Generate pentest finding report for forced browsing results."""
|
|
report = {
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"total_endpoints_found": len(findings),
|
|
"auth_bypass_candidates": [f for f in findings if f.get("auth_bypass")],
|
|
"accessible_without_auth": [f for f in findings if f["unauth_status"] == 200],
|
|
"http_method_bypass": method_results,
|
|
"sensitive_files_exposed": sensitive_files,
|
|
}
|
|
bypasses = len(report["auth_bypass_candidates"])
|
|
logger.info("Report: %d endpoints, %d auth bypasses, %d sensitive files",
|
|
len(findings), bypasses, len(sensitive_files))
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Forced Browsing Authentication Bypass Agent")
|
|
parser.add_argument("--target", required=True, help="Target base URL")
|
|
parser.add_argument("--wordlist", help="Path to wordlist file")
|
|
parser.add_argument("--session-cookie", help="Valid session cookie for auth comparison")
|
|
parser.add_argument("--admin-paths", action="store_true", help="Test common admin paths")
|
|
parser.add_argument("--output", default="forced_browsing_report.json")
|
|
args = parser.parse_args()
|
|
|
|
wordlist = DEFAULT_ADMIN_PATHS if args.admin_paths else []
|
|
if args.wordlist:
|
|
wordlist = load_wordlist(args.wordlist)
|
|
|
|
findings = enumerate_directories(args.target, wordlist, args.session_cookie)
|
|
method_results = {}
|
|
for f in findings:
|
|
if f["unauth_status"] in (401, 403):
|
|
method_results[f["path"]] = test_http_method_bypass(args.target, f["path"])
|
|
|
|
sensitive = check_sensitive_files(args.target)
|
|
report = generate_report(findings, method_results, sensitive)
|
|
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
logger.info("Report saved to %s", args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|