REST API with Express.js
sqlmap

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.

Ethical use only. Only run sqlmap against systems you own or have explicit written permission to test. Running it against any other system is illegal and unethical regardless of intent.
What is SQL Injection?

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.

Test Environment

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.

Installing sqlmap

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
Basic Usage

If sqlmap finds a vulnerability it will show the injection type and the database type. You can then ask it to enumerate further:

FlagWhat it does
--dbsList all databases on the server
-D dbname --tablesList all tables in a database
-D dbname -T tablename --columnsList columns in a table
-D dbname -T tablename --dumpDump all rows from a table
--current-userShow the database user sqlmap is running as
--current-dbShow the current database
--level=3Increase test depth (1–5, default 1)
--risk=2Allow riskier payloads (1–3, default 1)
--batchRun non-interactively, accept all defaults
Testing a Login Form

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.

Testing a GET Endpoint

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
Crawling the Whole Application

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.

What the Results Tell You

When sqlmap successfully exploits an injection point, it can typically retrieve:

  • All database names on the server
  • All table and column names
  • All data rows — including usernames, password hashes, email addresses
  • The database software version and operating system
  • In some configurations: read local files or execute OS commands

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.

How to Prevent SQL Injection

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.



Toggle Menu