sqlmap is an open source penetration testing tool that automates the detection and exploitation of SQL injection vulnerabilities. It is widely used by security professionals to test whether a web application is vulnerable before an attacker finds the weakness first.
Understanding how sqlmap works also makes you a better developer: when you see what it can extract from a vulnerable application, the importance of parameterized queries becomes very concrete.
SQL injection occurs when user-supplied input is concatenated directly into a SQL query instead of being passed as a parameter. The attacker can then manipulate the query structure to bypass authentication, read arbitrary data, or modify the database.
A classic vulnerable Node.js/Express example:
// VULNERABLE — never do this
app.get('/user', async (req, res) => {
const id = req.query.id;
const sql = "SELECT * FROM users WHERE id = " + id;
const result = await pool.query(sql);
res.json(result.rows);
});
If the URL is /user?id=1 the query is normal. But if an attacker sends /user?id=1 OR 1=1, the query becomes:
SELECT * FROM users WHERE id = 1 OR 1=1
This returns all rows. With more advanced payloads, an attacker can read any table, extract password hashes, or determine the entire database schema.
Never test against a live application. Use a deliberately vulnerable practice application instead. Two popular options:
Both run locally. DVWA is PHP/MySQL-based so it fits naturally alongside the rest of this course. After installation, set the security level to Low in DVWA settings to make the SQL injection exercises exploitable.
sqlmap requires Python 3. Download it from https://sqlmap.org or clone the repository:
git clone https://github.com/sqlmapproject/sqlmap.git
Run it with:
python sqlmap.py --help
On Linux/macOS sqlmap is often available directly via the package manager:
sudo apt install sqlmap # Debian/Ubuntu
brew install sqlmap # macOS
The most common starting point is testing a URL parameter. The -u flag specifies the target URL. sqlmap will try various payloads against each parameter and report which ones are injectable.
python sqlmap.py -u "http://localhost/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie="PHPSESSID=abc123;security=low"
If sqlmap finds a vulnerability it will show the injection type and the database type. You can then ask it to enumerate further:
| Flag | What it does |
|---|---|
--dbs | List all databases on the server |
-D dbname --tables | List all tables in a database |
-D dbname -T tablename --columns | List columns in a table |
-D dbname -T tablename --dump | Dump all rows from a table |
--current-user | Show the database user sqlmap is running as |
--current-db | Show the current database |
--level=3 | Increase test depth (1–5, default 1) |
--risk=2 | Allow riskier payloads (1–3, default 1) |
--batch | Run non-interactively, accept all defaults |
Example: list all databases after confirming injection:
python sqlmap.py -u "http://localhost/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie="PHPSESSID=abc123;security=low" --dbs --batch
For login forms and other POST requests, capture the request with a browser's developer tools or Burp Suite, save it to a file, and pass it with -r:
python sqlmap.py -r request.txt --batch --dbs
The request file looks like a raw HTTP request:
POST /login HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
username=admin&password=test
sqlmap will test each POST parameter for injection.
When sqlmap successfully exploits an injection point, it can typically retrieve:
Against a typical poorly secured application, a full dump of the users table takes seconds. This is why even a single unparameterized query is a critical vulnerability.
The fix is always the same: never concatenate user input into SQL. Use parameterized queries (prepared statements) so the database driver always treats user input as data, never as SQL syntax.
Vulnerable (Node.js/Express):
app.get('/user', async (req, res) => {
const result = await pool.query(
"SELECT * FROM users WHERE id = " + req.query.id
);
res.json(result.rows);
});
Safe (Node.js/Express + PostgreSQL):
app.get('/user', async (req, res) => {
const result = await pool.query(
'SELECT * FROM users WHERE id = $1',
[req.query.id]
);
res.json(result.rows);
});
Safe (Node.js/Express + SQL Server):
app.get('/user', async (req, res) => {
const result = await pool.request()
.input('id', sql.Int, req.query.id)
.query('SELECT * FROM users WHERE id = @id');
res.json(result.recordset);
});
In all cases the database driver sends the query and the parameters separately. No matter what the user supplies, it cannot change the query structure.