Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CSRF-Aware Brute Force Engine v3.2

⚠️ AUTHORIZED PENETRATION TESTING USE ONLY Unauthorized access to computer systems is illegal. Ensure you have written authorization before testing.

Overview

A Python-based penetration testing tool that bypasses Anti-CSRF token mechanisms to perform brute-force authentication attacks. Built for DVWA (Damn Vulnerable Web Application) with full security-level awareness, live detection (no static per-level assumptions), and WAF/Bot-Management-aware response analysis.

Framework Alignment

Framework Mapping
MITRE ATT&CK T1110.001 (Brute Force), T1090.002 (Proxy Rotation), T1036.005 (UA Masquerading)
Cyber Kill Chain Phase 5 — Exploitation
PTES Exploitation Phase

What's New in v3.1 / v3.2

Nothing about credentials, HTTP method, or WAF markers is hardcoded in source anymore. Everything is either live-detected against the actual target or supplied explicitly via CLI flags.

Area v3.0 behavior v3.1 / v3.2 behavior
Login credentials dvwa_auto_login() defaulted to admin/password No default at all. --login-user/--login-pass required (or --cookie, or --no-auto-login)
Auto-login failure Logged, then silently continued the attack against an unauthenticated session Fail-fast (SystemExit) by default; --continue-on-auth-fail to opt back into the old behavior
CSRF token presence Assumed from the -s preset table (Low/Medium = no token, High/Impossible = token) Always probed live from the actual page every attempt
HTTP method Assumed from the -s preset table Detected live from <form method=...> every attempt; -m still forces an override
WAF/Bot block pages Not detected at all (counted as a wrong password) Detected via HTTP status (--waf-block-status) and body markers loaded from an operator-supplied --waf-markers file — no markers baked into source
Connection refused/reset Treated as a generic network error, retried forever with a flat 5s sleep Recognized as a likely WAF/Bot-Management IP quarantine signal; growing backoff, aborts after --conn-block-retries
"Every attempt looks identical" No detection --stale-session-threshold consecutive no-match responses trigger an explicit warning instead of silently grinding through the whole wordlist

The -s/--security preset table survives only as reference/fallback: it prints the documented Defense/Bypass text for that level, and supplies a fallback value only if a live probe genuinely can't find a <form> at all. It no longer decides what request the script actually sends.


DVWA Security Level Reference

Level Matrix (informational — method/token below are what a stock DVWA install uses; the script detects the real ones live, they can differ on your instance)

Level HTTP Method CSRF Token Server Delay Lockout Brute-Force Viable?
Low GET ❌ None None None ✅ Yes — trivial
Medium GET ❌ None sleep(2) None ✅ Yes — just slower
High GET user_token sleep(0-3) random None ✅ Yes — token is trivially automated
Impossible POST user_token None 3 failures → 15 min lock ❌ No — wrong attack vector

Low — No Protection

  • Defense: None
  • What to learn: Basic brute-force mechanics — wordlist iteration, HTTP request construction, success/failure detection
  • Bypass: Direct GET request with username + password + Login parameters

Medium — Server-Side Rate Limiting

  • Defense: sleep(2) on every failed login attempt (server-side)
  • What to learn: How speed/rate-based defenses work. The delay is server-side and cannot be bypassed from the client. Conceptually, parallel sessions from different IPs could help.
  • Bypass: Identical to Low, just 2 seconds slower per attempt. With 14M passwords (rockyou.txt), this turns a days-long attack into a months-long one.

High — CSRF Token (False Sense of Security)

  • Defense: Anti-CSRF token (user_token) embedded as a hidden form field + sleep(rand(0,3))
  • What to learn: The CSRF token is NOT a real brute-force barrier. It's a defense against Cross-Site Request Forgery (a different attack), not against direct brute-forcing. Since the attacker can simply GET the page, read the token, and include it in their request, it's trivially automated.
  • Bypass: Fetch fresh token per request (GET → regex extract → inject into payload). This is the level best suited to the "penetration" scenario — a genuine vulnerability exists behind a false sense of security.

Impossible — Account Lockout (Proper Defense)

  • Defense: Account lockout after 3 failed attempts (15-minute cooldown), CSRF token, POST method, PDO prepared statements (no SQLi)
  • What to learn: This is a properly defended brute-force mechanism. The lockout is tied to the account, not just the source IP — proxy rotation can't bypass it.
  • Why brute-force is wrong here: Even with 1000 proxies, the account itself locks after 3 failures. You'd need to wait 15 minutes between every 3 attempts. With rockyou.txt that's ~70 years.
  • Better approach: Analyze for DoS potential — can an attacker intentionally lock a legitimate user's account by sending 3 bad attempts? That's a denial-of-service vulnerability in the lockout mechanism itself.

Quick Start Commands

--login-user/--login-pass authenticate the script's own session. They are always required unless you pass --cookie (inject an existing session) or --no-auto-login (target needs no login at all). This is deliberately separate from -u/--username, which is the brute-force target — the account whose password you're actually trying to crack.

Low Level

python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb \
    -w wordlists/sample.txt \
    -s low \
    --login-user admin --login-pass '<known-admin-password>'

Medium Level

# Same as Low but expect ~2s per failed attempt (server-side delay)
python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb \
    -w wordlists/sample.txt \
    -s medium \
    --login-user admin --login-pass '<known-admin-password>'

High Level (CSRF Token Bypass)

# Token is auto-extracted and injected per request — same command works
# whether or not the target actually has a token, method/token are
# detected live either way
python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb \
    -w wordlists/sample.txt \
    -s high \
    --login-user admin --login-pass '<known-admin-password>' -v

Impossible Level (Educational — Will Fail)

# ⚠ Brute-force is the WRONG attack vector here
python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb \
    -w wordlists/sample.txt \
    -s impossible \
    --login-user admin --login-pass '<known-admin-password>'

Using an Existing Browser Session (No Auto-Login)

# Copy cookies from DevTools → Application → Cookies
python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb -w wordlists/sample.txt \
    --cookie "PHPSESSID=abc123; security=high" \
    --no-auto-login

WAF/Bot-Management-Aware Run

# waf_markers.txt is a starting template — populate it from a real observed
# block page for your WAF before relying on it (see comments in the file)
python3 csrf_brute.py \
    -t http://target/vulnerabilities/brute/ \
    -u gordonb -w wordlists/sample.txt \
    -s high --login-user admin --login-pass '<known-admin-password>' \
    --waf-markers waf_markers.txt \
    --waf-block-status 403,999 \
    --conn-block-retries 3 --conn-block-backoff 45 \
    -v

Rockyou.txt + Fast Jitter

python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb \
    -w /usr/share/wordlists/rockyou.txt \
    -s high --login-user admin --login-pass '<known-admin-password>' \
    --jitter-min 0.1 --jitter-max 0.3

With Burp Suite Proxy + Verbose

python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb -w wordlists/sample.txt \
    -s high --login-user admin --login-pass '<known-admin-password>' \
    --proxy http://127.0.0.1:8080 -v

Custom Non-DVWA Target

python3 csrf_brute.py \
    -t http://target.local/auth/login \
    -u admin -w rockyou.txt \
    --login-user admin --login-pass '<known-password>' \
    -m POST \
    --token-name csrf_token \
    --failure "Invalid credentials" \
    --success "Dashboard"

Save Cracked Credentials to a File

python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u gordonb -w wordlists/sample.txt -s low \
    --login-user admin --login-pass '<known-admin-password>' \
    -o cracked.txt

Resuming an Interrupted Run

# A previous run against a 14M-line wordlist got killed around line 1400 —
# don't redo everything from the top, pick up where it left off. The line
# number is a RAW file line number (same as `wc -l`/a text editor), not a
# count of attempts made (comment/blank lines in the file count too).
python3 csrf_brute.py \
    -t http://127.0.0.1:4280/vulnerabilities/brute/ \
    -u admin -w /usr/share/wordlists/rockyou.txt \
    -s low --login-user gordonb --login-pass abc123 \
    --resume-from 1400

Installation

pip install requests

CLI Reference

Flag Description Default
-t, --target Full target URL required
-u, --username Brute-force target username required
-w, --wordlist Password wordlist required
--resume-from 1-indexed raw line number in the wordlist to start from (same numbering as wc -l) — skip lines already tried in a killed/interrupted run 1 (no skip)
-s, --security DVWA level: low medium high impossibleinformational only, method/token are always live-detected None
-m, --method Force HTTP method, overriding live <form method=...> detection Auto-detected live
--login-user Username to authenticate the script's own session (auto-login) required unless --cookie/--no-auto-login
--login-pass Password for --login-user required unless --cookie/--no-auto-login
--no-auto-login Skip the DVWA auto-login step entirely off
--continue-on-auth-fail Don't abort when auto-login fails; continue anyway off (fail-fast by default)
-c, --cookie Inject browser cookies instead of auto-login None
--no-token Skip CSRF token extraction entirely off (always probed live)
--token-name CSRF token field name user_token
--failure Failure string in response Username and/or password incorrect
--success Success string in response Welcome to the password protected area
--waf-markers Path to a file of WAF/Bot block-page substrings (one per line, # comments allowed) None (body-content WAF detection disabled)
--waf-block-status Comma-separated HTTP status codes treated as a WAF/Bot block 403,999
--stale-session-threshold Warn after this many consecutive no-match responses (likely dead session) 5
--conn-block-retries Abort after this many consecutive connection-refused/reset errors 3
--conn-block-backoff Seconds to back off after a connection error, × consecutive-failure count 30.0
--jitter-min Min delay between requests (sec) 0.5
--jitter-max Max delay between requests (sec) 2.0
--no-ua-rotate Disable User-Agent rotation off
--proxy Single HTTP proxy None
--proxy-list Proxy list file for rotation None
--lockout-after Rotate proxy after N attempts 2
-o, --output File to append cracked credentials None
-v, --verbose Show step-level debug output off

Algorithm Architecture

┌───────────────────────────────────────────────────────────────┐
│  STEP 0: DVWA Auth (--login-user/--login-pass, or --cookie)   │
│          Auto-login failure → fail-fast SystemExit by default │
│  ↓                                                             │
│  STEP 1: Session Init + security cookie override               │
│  ↓                                                             │
│  STEP 2: GET page (fetch live HTML)                       ←──┐ │
│  ↓                                                           │ │
│  STEP 3: Extract CSRF token (live probe) + detect method     │ │
│          from live <form method=...> — no static assumption  │ │
│  ↓                                                           │ │
│  STEP 4: Build payload (creds + token + Login)                │ │
│  ↓                                                           │ │
│  STEP 5: Execute GET/POST + evasion (jitter, UA rotation)     │ │
│  ↓                                                           │ │
│  STEP 6: Analyze response                                     │ │
│          → success / failure / WAF-block / unmatched          │ │
│          → N consecutive "unmatched" triggers a warning        │ │
│  ↓                                                           │ │
│  STEP 7: Reset token → Loop back to Step 2 ──────────────────┘ │
│                                                                 │
│  Connection refused/reset at any point → NOT a generic error, │
│  treated as a possible WAF/Bot IP quarantine: growing backoff,│
│  abort after --conn-block-retries                              │
└───────────────────────────────────────────────────────────────┘

How Token Automation Works

The CSRF token is 100% dynamic and automated. Every iteration:

  1. GET → Fresh HTML fetched from server
  2. Regexuser_token nonce extracted from <input type="hidden">, and the <form method=...> attribute is read in the same pass
  3. Inject → Token placed into GET/POST payload
  4. Consume → Server validates and invalidates the token
  5. Discard → Old token deleted, loop restarts

If no token is found on the live page, the script proceeds without one — it never assumes a level "doesn't have a token" from a static table.


Bugs Fixed

v3.0 — False Positives (Every attempt showed as "FOUND")

Root cause: DVWA's navigation sidebar contains a Logout link on every authenticated page. The old heuristic matched "logout" in the body → 100% false success rate. Fix: Strict matching — only the verified success string triggers success. No more heuristic fallbacks.

v3.0 — Cookie Conflict (Security level ignored)

Root cause: DVWA sets security=impossible as a server cookie on initial page load. Python's session.cookies.set("security", "low") created a duplicate cookie instead of overwriting. The server's impossible value always took precedence. Fix: Clear all existing security cookies from the jar before setting the desired level.

v3.0 — Wrong HTTP Method (Payload silently ignored)

Root cause: DVWA's brute force page uses <form method="GET"> but the script was sending POST. Server ignored the POST body entirely. Fix: Level-aware method selection (Low/Medium/High → GET, Impossible → POST). Superseded in v3.2 by live <form> detection, since the level-aware table itself turned out to be an assumption that doesn't hold on every DVWA install.

v3.1 — Token Presence Wrongly Assumed From the Level Table

Root cause: Some DVWA installs (e.g. certain digininja/DVWA manual setups) embed user_token on every security level, not just High/Impossible. Trusting DVWA_PRESETS["low"]["has_token"] = False meant the script never sent a token Low/Medium actually required, and the server silently rejected every attempt. Fix: Token presence is now probed live on every attempt (required=False) instead of trusted from the table.

v3.1 — WAF/Bot Block Pages Counted as Wrong Passwords

Root cause: The script sends the password as a GET query parameter; a wordlist entry resembling an attack payload (' OR 1=1--, <script>, ../, ;id) can trip a WAF's signature rules. The response was indistinguishable from a normal "wrong password" failure. Fix: analyze_response() checks HTTP status (--waf-block-status) and operator-supplied body markers (--waf-markers) before falling through to the normal success/failure check.

v3.2 — Silent Auth Failure (Every Attempt Identical, No Error)

Root cause: dvwa_auto_login() defaulted to admin/password. When that credential was wrong for the target instance, the login failure was logged but its return value was never checkedinit_session() returned a session that had never actually authenticated, and the attack loop ran anyway, producing an identical byte-for-byte response (the login page, redirected to) for every single wordlist entry. Fix: No default credentials — --login-user/--login-pass required explicitly. init_session() now checks the login result and aborts (SystemExit) by default on failure. A running counter of consecutive "no known string matched" responses also triggers an explicit warning (--stale-session-threshold), so a dead session is caught even if it happens mid-run rather than at startup.

v3.2 — WAF/Bot-Management IP Quarantine Misread as a Network Blip

Root cause: Observed live against a NetScaler ADC in front of a WAF/Bot-Management-protected DVWA instance — after a burst of non-browser-shaped requests, the ADC stopped completing the TCP handshake from the source IP entirely (ConnectionError, no HTTP response, no block page). The old code caught this as a generic requests.RequestException, slept a flat 5 seconds, and retried indefinitely — likely extending the quarantine. Fix: ConnectionError is now caught distinctly from other request exceptions, with a growing backoff (--conn-block-backoff) and a hard abort after --conn-block-retries consecutive failures, with an explicit message pointing at the likely cause.

v3.2 — Trailing Slash Silently Redirected Every Attempt to the DVWA Home Page

Root cause: login_url = target_url.rstrip("/") stripped the trailing slash every DVWA doc/example uses (.../vulnerabilities/brute/.../vulnerabilities/brute). Found live against dvwa.local: the no-slash path doesn't resolve to the vulnerable page on that install — it redirects straight to the DVWA "Welcome" home page (no <form> at all). Every attempt then hit that same redirected page and produced an identical response — the exact same symptom as the silent-auth-failure bug above, but with a completely unrelated root cause. This one is easy to miss because the login itself succeeds and nothing errors; it only shows up as "every attempt looks the same" once you're actually looking at the response body (-v, or the --stale-session-threshold warning). Fix: The trailing slash is no longer stripped — target_url is only whitespace-trimmed and given a http:// prefix if missing.


Project Structure

akin/
├── csrf_brute.py          # Main engine v3.2
├── waf_markers.txt         # WAF/Bot block-page marker template (--waf-markers) — populate from a real block page
├── proxies.txt             # Proxy list template (for lockout bypass)
├── wordlists/
│   └── sample.txt          # Sample wordlist (20 passwords, includes "password")
├── checklist                # Broader auth-testing checklist (rate limiting, lockout, CSRF, enumeration, MFA)
├── GITCOMMITMESSAGES.md    # Suggested commit messages for pending changes (nothing auto-committed)
└── README.md               # This file

Legal Notice

This tool is provided for educational and authorized security testing purposes only.

About

csrf-aware brute force engine

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages