Step 147. ★ DVWA All Difficulty Levels + the Three-Tier Summary Table — What It Means to “Completely” Know One Vulnerability

Step 147. ★ DVWA All Difficulty Levels + the Three-Tier Summary Table — What It Means to "Completely" Know One Vulnerability

Level 2 — Introduction to Security and Attack Skill Basics | Difficulty ★★★★☆ | Estimated time: 5 hours

Prerequisites: Steps 135–146 complete. You have changed DVWA’s Security difficulty (Low/Medium/High).

  • What you need: a lab with DVWA running, Python 3 (sqlite3 — for measuring defense principles), a notes app
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.

Truly knowing one vulnerability means being able to say all three of these: why it arises (the principle), how you break it (the attack), how you block it (the defense). If you’ve been looting DVWA’s modules centered on Low difficulty, today you finish the remaining modules and read the defense code of Medium and High. Then you fold all that knowledge into a single "three-tier summary table" — this table becomes a lifelong asset, the skeleton of all your web study from now on.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Explain at the code level how defense code evolves as DVWA’s difficulty rises
  • Confirm by measurement why the same attack gets blocked on Medium and High
  • Give at least one example of a blacklist defense’s limit (a bypass case)
  • Organize 8+ vulnerability types into a "principle | attack | defense" three-tier table
  • Have the perspective to evaluate even each defense’s bypass possibility

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment DVWA (wargame) + Python 3 sqlite3 (measuring defense principles)
Today’s commands Switching DVWA Security difficulty, the "View Source" button, sqlite3 parameter binding
Concepts needed Prepared statements, output encoding, whitelists, the limits of blacklists
Today’s artifact A vulnerability three-tier summary table (at least 8 rows)

2-1. DVWA’s Difficulty Structure — One Vulnerability, Four Sets of Code

Each DVWA module has different server code per difficulty. Change the difficulty in the left menu’s "DVWA Security," and read that difficulty’s code via "View Source" at the bottom of each module screen. Low has no defense, Medium adds a blacklist filter, High adds a denser filter or an alternative implementation. From a screen where you only attacked to a screen where you read code — that is today’s turning point.

2-2. The Three Fundamental Principles of Defense

Function names differ by language, but the principles of defense are only three.

  1. Prepared statements: transmit the SQL skeleton and the data separately — data can never become a command. The correct answer to SQLi defense.
  2. Output encoding: convert < into "display characters" like &lt; when printing — a script is never interpreted as a tag. The correct answer to XSS defense.
  3. Whitelist: reject everything outside an allowed list — the same principle as Step 144’s file-inclusion defense.

2-3. The Fate of Blacklists — Why Medium Falls

Medium difficulty’s archetype is the blacklist. It deletes the <script> string, removes ; and &&, escapes quotes. Yet attackers find bypasses like <scr<script>ipt> (delete and it merges back together) or quote-free numeric injection. "A list of bad things" is always incomplete — in today’s measurement you’ll see this actually happen.


3. Follow Along

3-1. The Defense-Difficulty Experiment — Low/Medium/High Through sqlite3

Even without DVWA, the difference in defense principles reproduces honestly. With Python’s built-in sqlite3, we feed the same attack string into three kinds of code (this book was measured on 2026-09-09).

Input (the core of defense_levels.py)

import sqlite3

db = sqlite3.connect(":memory:")
cur = db.cursor()
cur.execute("CREATE TABLE users (name TEXT, pw TEXT, role TEXT)")
cur.executemany("INSERT INTO users VALUES (?, ?, ?)", [
    ("admin", "s3cr3t-admin", "administrator"),
    ("alice", "alice-pass", "user"),
    ("guest", "guest123", "visitor"),
])

ATTACK = "' OR '1'='1"

# Low: string concatenation (no defense)
q = f"SELECT name, role FROM users WHERE name = '{ATTACK}'"
print("Low  :", cur.execute(q).fetchall())

# Medium: quote escaping (' -> '')
escaped = ATTACK.replace("'", "''")
q = f"SELECT name, role FROM users WHERE name = '{escaped}'"
print("Med  :", cur.execute(q).fetchall())

# High/Impossible: prepared statement
print("High :", cur.execute(
    "SELECT name, role FROM users WHERE name = ?", (ATTACK,)).fetchall())

Output (measured 2026-09-09):

Low  : Executed query: SELECT name, role FROM users WHERE name = '' OR '1'='1'
       3 rows: [('admin', 'administrator'), ('alice', 'user'), ('guest', 'visitor')]
Med  : Executed query: SELECT name, role FROM users WHERE name = ''' OR ''1''=''1'
       0 rows: []
High : 0 rows: []

How to read it: on Low, the input’s quote closed the string and '1'='1' executed as a condition — all three member rows got looted. Medium’s escaping blocked it by neutralizing the quotes; High’s prepared statement blocked it because the query structure itself never changed. On the surface Medium and High have the same result, but the way they blocked differs — and the next experiment exposes that difference.

3-2. The Blacklist’s Hole — Injection That Needs No Quotes

Medium escaping blocks only "injection with quotes." In a numeric slot, no quotes are needed at all.

Input

uid_attack = "1 OR 1=1"   # no quotes, so escaping is meaningless
q = f"SELECT item, price FROM items WHERE id = {uid_attack}"
print("Executed query:", q)
print("Result:", cur.execute(q).fetchall())

Output (measured 2026-09-09):

Executed query: SELECT item, price FROM items WHERE id = 1 OR 1=1
Result: [('pencil', 500), ('eraser', 700), ('secret notebook', 9999)]

How to read it: a Medium-grade defense that escapes quotes is completely powerless before numeric injection. This is the substance of "blacklists are incomplete." A prepared statement, by contrast, treats the input as data even here and stays safe. The habit of asking not "what did it block" but "what couldn’t it block" when reading defense code — that is today’s core instinct.

3-3. Clearing DVWA’s Remaining Modules (wargame practice, output example)

Do this in your own DVWA lab. The output is an output example. Finish the modules you haven’t cleared yet (Brute Force, File Upload, CSP, JavaScript, etc.).

Brute Force (Low): a web-form attack with hydra — a combination of Steps 122 and 146.

hydra -l admin -P rockyou.txt DVWA-address http-get-form \
  "/vulnerabilities/brute/:username=^USER^&password=^PASS^:F=incorrect"
[80][http-get-form] host: DVWA-address   login: admin   password: password

File Upload (Low): with no extension check, a .php web shell uploads as-is (review of Steps 141–142). Medium checks only Content-Type, so changing just the header in a proxy bypasses it.

How to read it: each time you clear a module, open "View Source" and find at which line of code the attack you just passed was allowed. This habit becomes the raw material for the 3-4 summary table.

3-4. Reading the High Code — Tracing the Evolution of Defense

Pick one DVWA module and read the Low→Medium→High code side by side. The command injection module as an example (screen example):

// Low: executed as-is
$target = $_REQUEST['ip'];
$cmd = shell_exec('ping -c 4 ' . $target);

// Medium: blacklist substitution
$substitutions = array('&&' => '', ';' => '');
$target = str_replace(array_keys($substitutions), $substitutions, $target);

// High: a longer blacklist ('|', '&', ' ', '$', etc. added)

How to read it: Low→Medium is the evolution "nothing → deleting bad characters," and High lengthens that list. So even High isn’t theoretically perfect — a connector not on the list (e.g., newline %0a) may remain. In the Impossible code, a whitelist-style validation appears: explode splits the IP into four chunks and checks each is numeric. The destination of evolution is always "the style that defines what is allowed."

3-5. Writing the Three-Tier Summary Table — Today’s Artifact

Make a table in your notes: vulnerability | principle (one line) | representative payload | defense code/principle | bypass possibility.

Input (a written example — remake it into your own table)

Vulnerability | Principle               | Representative payload    | Defense principle    | Bypass possibility
SQLi        | Data becomes a query      | ' OR '1'='1               | Prepared statements  | Practically impossible with prepared statements
XSS         | Input becomes a script    | <script>alert(1)</script> | Output encoding      | Exists when context-specific encoding is missed
CSRF        | Browser auto-attaches auth | <img src=transfer URL>   | CSRF token           | Endpoints missing token verification
Upload      | File lands in an executable path | Upload shell.php | Extension whitelist + storage separation | Check bypasses (double extensions, etc.)
Command injection | Input becomes a shell command | 127.0.0.1; id | Argument-array passing (no shell) | Remains if blacklist-based
LFI/RFI     | Input becomes a file path | ?page=../../../../etc/passwd | Path whitelist   | Impossible with a whitelist
Authentication | Default passwords, enumeration, trust flaws | admin/admin | Lockout + unified messages + server-side authority | Slow distributed attacks remain
Info exposure | Leftover files are public | /.git/config           | Deploy hygiene + access denial | New paths keep appearing

How to read it: the "defense principle" column must carry the principle (prepared statements, output encoding, whitelist), not function names. Functions change when the language changes; the principle is the same everywhere.


4. Missions & Exercises

Mission — Completing Every Difficulty and a Lifelong Asset

  1. Run the 3-1 sqlite3 experiment yourself and record the result differences of Low/Medium/High
  2. Reproduce "the hole in Medium-grade defense" with the 3-2 numeric injection, and confirm a prepared statement blocks it
  3. Clear every unfinished DVWA module, and write one line per module on the per-difficulty "View Source" code differences
  4. Complete a three-tier summary table in the 3-5 format — at least 8 types, bypass-possibility column included
  5. For each row of the table, mark whether it’s "something I reproduced myself" or "confirmed from code only"

Exercises

Exercise 1. Explain the principle by which a prepared statement fundamentally blocks SQLi, using "separation of the query skeleton and the data."

Exercise 2. Explain why blacklist defense is structurally incomplete, using today’s measured numeric-injection case.

Exercise 3. Why is XSS defense "output encoding" — explain why it’s handled at output rather than blocked at input.

Exercise 4. From the perspective that "no defense is perfect," explain why the table needs a "bypass possibility" column.


5. Model Answers & Completion Criteria

Mission Model Answer

Expected results of the sqlite3 experiment: Low 3 rows leaked → Medium 0 rows → High 0 rows; but the numeric variant (1 OR 1=1) passes the escaping defense and leaks 3 rows, while a prepared statement yields 0 rows (measured 2026-09-09). Use the 3-5 example as the skeleton of your table, but fill it with the payloads and code phrases of the modules you actually cleared.

How to verify: ① is the experiment output copied down? ② did you confirm even the numeric bypass? ③ does the table have 8+ rows? ④ is the defense column written as "principles"? ⑤ is there a reproduced-myself/code-only distinction marked?

Exercise Answers

Answer 1. A prepared statement sends the SQL skeleton (SELECT ... WHERE name = ?) to the DB first, fixing the grammar, and the data is transmitted afterward only as a "value." Even if the input contains quotes, it cannot change the already-fixed grammar — there simply is no path for data to become a command.

Answer 2. A blacklist must enumerate every "bad input" in advance, but attack expressions mutate infinitely. In today’s measurement, quote escaping (Medium-grade) blocked injection with quotes, yet was meaningless against the quote-free numeric injection 1 OR 1=1. An incomplete list means an incomplete defense.

Answer 3. Input must also be stored for legitimate uses (search terms, nicknames), so blocking at the input stage breaks normal use, and old stored data remains dangerous. The danger occurs "at the output moment when the browser interprets it as a tag," so encoding that converts < to &lt; at output time is the precise defense point.

Answer 4. Because recording a defense without its bypass possibility invites the illusion "this defense exists, so it’s safe." Rate limits partially fall to distributed attacks; filters to mutated payloads. The bypass-possibility column is both the defense’s humility and your study list for the next attack techniques.

Completion Criteria Checklist

  • [ ] I reproduced the Low/Medium/High defense differences myself with sqlite3
  • [ ] I confirmed the hole in blacklist defense with numeric injection
  • [ ] I cleared every DVWA module
  • [ ] I read and recorded per-module difficulty code differences via "View Source"
  • [ ] I completed a three-tier summary table of 8+ vulnerability types
  • [ ] I wrote one line each on every defense’s bypass possibility
  • [ ] I can state the three defense principles (prepared statements, output encoding, whitelists)

6. Common Pitfalls & Fixes

Wall 1. I changed the difficulty but the attack still works

Symptom: you raised it to Medium but the Low payload still lands.
Cause: DVWA’s Security setting didn’t save (it’s cookie-based), or you’re looking at a different module.
Fix: after changing the difficulty on the DVWA Security page and pressing Submit, use the module screen’s "View Source" to confirm the current code really is the Medium code. If the code didn’t change, the difficulty didn’t.

Wall 2. I can’t read the PHP code in "View Source"

Symptom: you look at the code but can’t tell where it’s vulnerable.
Cause: normal. If you’re not used to PHP syntax, code looks like a wall.
Fix: don’t read line by line — find only "the line where input enters" ($_GET, $_REQUEST) and "the line where input is used" (shell_exec, the SQL statement, echo). If there’s no processing between them, it’s Low; if there’s a substitution like str_replace, it’s a blacklist.

Wall 3. Blocked by a filter on Medium, can’t proceed

Symptom: <script> gets deleted so XSS won’t work.
Cause: blacklists often don’t re-check after deleting — which is why a bypass like <scr<script>ipt>, where cutting out the middle merges it back together, works.
Fix: confirm with an echo "what did the server delete," and overlap the deleted string inside your payload. Checking by removing one blocked point at a time is the textbook way of filter bypass.

Wall 4. The Medium result isn’t 0 rows in the sqlite3 experiment

Symptom: you escaped, yet results come out.
Cause: you may not have applied the escape to the attack string, or put the original inside the f-string.
Fix: always print the "Executed query" and confirm with your eyes. Checking whether the query looks like ''' OR ''1''=''1' (doubled quotes) is the whole of the debugging.

Wall 5. The summary table becomes "copying what I learned"

Symptom: the table is filled, but you freeze when asked to explain it.
Cause: rows filled with words alone, without personal reproduction, don’t stay in memory.
Fix: write "the date and environment I reproduced it in" on each row. Mark rows you couldn’t reproduce as "code confirmation only," and verify them by hand one at a time when you have time. The table is not a finished product but a note you keep revising for life.


7. Summary

Today’s Concepts

Concept One-line explanation
Prepared statement Separates query skeleton and data — fundamentally blocks SQLi
Output encoding Converts dangerous characters to display characters at output time — XSS defense
Whitelist Defines what is allowed and rejects everything else
Blacklist Enumerates and removes bad things — incomplete, bypasses exist
Numeric injection Injection needing no quotes — the classic escape bypass
Three-tier summary table A lifelong asset organizing vulnerabilities by principle-attack-defense (+bypass possibility)

Today’s Commands

Command What it does
DVWA Security → change difficulty Switch the defense code level
Module screen "View Source" Read that difficulty’s server code
cur.execute("... WHERE name = ?", (input,)) Prepared statement (sqlite3 measured)
input.replace("'", "''") Escape defense (Medium simulation)
1 OR 1=1 (in a numeric slot) Testing the escape bypass

An Instinct More Important Than Commands

In past chapters you were the attacker. Reading code today added one more field of view — that the same screen is, to a defender, the problem of "what can’t my filter block." Attack and defense are the front and back of one vulnerability, and only someone who sees both can explain "why it was blocked."

The three-tier summary table is not a document that ends here. Add a row every time you meet a new vulnerability. The speed at which the table grows is the speed of your growth, and someday this table will be the textbook you use to teach someone else.


Once every box is checked, Step 147 is complete. Click the checkbox in the sidebar to save your progress.