Web attacks
SQL injection: three requests from probe to database version
Updated
- File
- http_dvwa_sqlinjection.pcapng
- Format
- pcapng, ether
- Size
- 9.86 KB
- Packets
- 35
- Duration
- 45 s
- Captured (UTC)
- 2017-04-05 20:15:44
- SHA-256
- 09f674a52703aaa23fa73fa47fd2e23030a5649815395dc6c11242e1e01f1102
This SQL injection PCAP holds three GET requests to one DVWA endpoint over 45 seconds, and three
200 OK answers that mean three different things. The first response lists every user record, the second
is a 139-byte MySQL syntax error, and the third carries the database version grafted into the page. SQL injection is easy to see
in a packet capture, because the attack and its result both travel in the HTTP request and response. A-Packets
rebuilds each request with the response that followed, so you can read the escalation the way the attacker worked
it, one step at a time.
The target is the SQL injection exercise in DVWA, the Damn Vulnerable Web Application, and the traffic is plain HTTP, so nothing is hidden: the crafted parameter and the data it pulled back are both in the capture. That makes this a clean teaching example of what a successful injection looks like on the wire, and of the one response detail, a change in what the page returns, that separates a real finding from a request that was merely attempted.
Three requests that escalate from probe to data
The requests all hit one endpoint, /dvwa/vulnerabilities/sqli/, and all tamper with one parameter,
id. A-Packets shows each crafted value in full in the HTTP sessions view; decoded from URL encoding,
they read as a textbook manual-testing sequence.
| Host | Request | Status | Type |
|---|---|---|---|
| 10.2.2.104 | GET /dvwa/vulnerabilities/sqli/?id=%25%27+or+%270%27%3D%270&Submit=SubmitUA: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 | 200 OK | text/html 1.46 KB |
| 10.2.2.104 | GET /dvwa/vulnerabilities/sqli/?id=%25%27+or+0%3D0+union+select+null%2C+version%28%29&Submit=SubmitUA: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 | 200 OK | text/html 139 Bytes |
| 10.2.2.104 | GET /dvwa/vulnerabilities/sqli/?id=%25%27+or+0%3D0+union+select+null%2C+version%28%29+%23&Submit=SubmitUA: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 | 200 OK | text/html 1.51 KB |
The first value is %' or '0'='0. The stray single quote breaks out of the string the application wraps
the input in, and the or '0'='0 makes the WHERE clause always true, so the query returns
every row instead of one. Seeing the full list of names come back where a single record should appear is the
confirmation that the parameter is injectable: the input changed the meaning of the query, not just its value.
The next two values show a failed attempt followed by a correction. %' or 0=0 union select null,
version() appends a second query whose results are grafted onto the application's own output; the attacker
chooses what it selects, here version(), the database's version string. Its answer, frame 18 on TCP
stream 1, is 139 bytes of MySQL syntax error near the leftover quote, under an HTTP status of 200, and the error
itself tells the attacker which database engine is listening. The third request, frame 27, adds a trailing
# to comment out the remainder of the original query so the injected UNION parses
cleanly, and its 1,543-byte answer in frame 30 carries the database version back inside the page: the attacker
reading data of their choosing straight out of the application.
The # travels as %23 and becomes a SQL comment only after the server decodes the
parameter. Typed literally into a browser's address bar it would start a URL fragment and never reach the server,
which is why the request target as sent, not the URL as displayed, belongs in the notes.
Why UNION SELECT is the workhorse of manual injection
A UNION combines the rows of two SELECT statements into one result set, provided both
return the same number of columns with compatible types. That is why injection testers probe column counts first
(an automated tool does it with ORDER BY 1, ORDER BY 2 and so on until the query errors)
and why this sample selects two values, null and version(), to match the two columns the
original query returns: null fills one position, and version() lands in a column the page
displays. Once the shapes match, the attacker can replace version() with any expression the database can
evaluate: the current user, the list of tables, or the contents of a table. The
password dump sample is the same technique pointed at the users
table.
The reason UNION-based injection is so visible in a capture is that the stolen data comes back in the
normal response body, in place of or alongside the application's own output. There is no separate channel and no
encoding to undo when the site is plain HTTP: the request names what to steal and the response contains it. That is
the opposite of a blind injection, where the attacker infers data one bit at a time from the application's behaviour
and the responses look almost identical, a pattern the sqlmap scan shows when a
tool automates it.
Reading the response, not just the request
The request tells you what was attempted; the response tells you whether it worked, and that distinction is the whole job when you triage an injection alert. A payload in a log or a capture proves someone tried; a response that returned extra rows, a version string, or a column of data that the endpoint has no business returning proves the flaw is real. In this capture the proof is plain: the first probe returns multiple names, the un-commented UNION returns an error, and the corrected UNION returns the version. A-Packets carves the response bodies so you can confirm the data came back rather than guessing from the request alone.
In Wireshark you can isolate the same evidence with http.request.uri contains "union" for the UNION
attempts, or more broadly http.request.uri contains "select" and the single-quote and comment sequences
that mark injection, then follow each TCP stream to read the response. On a production web server the equivalent is
the access log, which records the injected parameter with the status and the response size. This capture shows why
the size matters more than the status: all three answers are 200, but the failed one is 139 bytes against roughly
1.5 KB for the two that worked.
When the data does not come back in the page
This capture is the easy case, where the injected query's results appear directly in the response. Real targets are often less generous, and the techniques an attacker falls back to change what the capture shows. In blind injection, the application returns no data and no error, so the attacker infers it one true/false question at a time from a difference in the page (boolean-blind) or from a deliberate delay (time-based blind, using a database sleep function). In those cases the capture holds many similar requests and near-identical responses, and the signal is the pattern and the timing rather than a visible data leak, the shape the automated sqlmap scan produces when it has to work blind.
There is also out-of-band injection, where the attacker makes the database itself open a connection, a DNS lookup or an HTTP request, to a server they control, carrying the stolen data in that request. That leaves no trace in the web response at all; the evidence is an unexpected outbound connection from the database server, which is why monitoring egress from database hosts matters as much as watching the web tier. The UNION-based sample here is the most visible point on this spectrum; knowing the quieter variants exist is what keeps a defender from assuming that a clean web-response capture means no injection occurred.
Fixing the flaw and detecting the attempt
The defence is in the application, not the network. Parameterised queries, also called prepared statements, send the SQL structure and the user input to the database separately, so bound values are treated as data rather than SQL syntax; an injected quote becomes part of the data, not part of the query. Constraining what the database account can read, so the web application's user cannot reach other tables, limits the damage if a flaw slips through, and an object-relational mapper used correctly gives parameterisation by default. These remove the vulnerability; input filtering and web application firewalls only raise the bar.
Prepared parameters cover values, not SQL identifiers or sort directions. If a route lets the user choose a column or a table, map the choice through a fixed server-side set instead of interpolating it, and review stored procedures and ORM escape hatches for dynamic SQL; the OWASP prevention guidance covers each case. Retest the repaired lookup with ordinary IDs, missing IDs and strings full of punctuation: every one must keep the same query structure and return only authorized records, with no database error text, and a WAF that blocks this exact spelling proves nothing about the query underneath.
On the detection side, the injected parameters are what to alert on and to search logs and captures for: quotes,
UNION SELECT, SQL comment markers and boolean tautologies like or 1=1 in query strings.
A web application firewall will flag and slow these, and rate limiting blunts an automated scan, but neither is a
substitute for parameterised queries. In MITRE ATT&CK terms a web-facing injection is T1190, Exploit
Public-Facing Application, and the data it returns maps to collection techniques depending on what the attacker
pulls.
This sample is the manual cousin of the automated sqlmap scan and the lighter end of the password-dump case. For the broader set of web attacks visible in traffic, see top threats found in PCAP analysis, or upload a capture of your own to read its HTTP the same way.