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
getOne: function(un, callback) {
const sql = "SELECT * FROM users WHERE username = '" + un + "'";
return db.query(sql, callback);
},
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 run sqlmap against a production application. If you want to test a real application, clone it to a local or separate test environment and run sqlmap against that instead. This way testing does not affect real users or data.
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
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 |
For login forms and other POST requests, you can specify the method and POST data directly on the command line with --method POST and --data:
python sqlmap.py -u "http://localhost/myapp/login" --method POST --data="username=test&password=test" --batch
sqlmap will test each POST parameter (username and password) for injection vulnerabilities.
To test a GET endpoint where the user identifier is part of the URL path, use the -u flag with the target URL:
python sqlmap.py -u "http://localhost/myapp/user/1" --batch
If SQL injection is detected, sqlmap reports the injection point, technique, and database type:
[INFO] testing connection to the target URL
[INFO] testing if the target URL content is stable
[INFO] parameter 'id' appears to be 'AND boolean-based blind' injectable
[INFO] GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s):
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1 AND 1=1
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1 AND SLEEP(5)
Type: UNION query
Title: Generic UNION query (NULL) - 1 to 20 columns
Payload: id=1 UNION ALL SELECT NULL,NULL,CONCAT(username,0x3a,password) FROM users--
---
[INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.12
sqlmap will probe the URL for injectable parameters. If the endpoint also accepts query string parameters you can include them in the URL:
python sqlmap.py -u "http://localhost/myapp/user/1?id=1" --batch
Instead of testing each endpoint separately, sqlmap can crawl the application and find endpoints automatically:
python sqlmap.py -u "http://localhost/myapp" --crawl=3 --batch
--crawl=3 means sqlmap follows links up to three levels deep and tests each discovered 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 + MySQL):
app.get('/user', async (req, res) => {
const [rows] = await pool.execute(
'SELECT * FROM users WHERE id = ?',
[req.query.id]
);
res.json(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.