Sql Injection in Express with Basic Auth
Sql Injection in Express with Basic Auth — how this specific combination creates or exposes the vulnerability
SQL Injection in an Express API that uses HTTP Basic Authentication can occur when user-controlled credentials are not strictly validated and are instead concatenated into SQL strings. Even though Basic Auth credentials are transmitted in the Authorization header, developers sometimes misuse them by directly embedding the username or password into SQL queries, for example to fetch a user record or to dynamically select a database/schema. If the input is not parameterized, an attacker can supply crafted credentials such as ' OR '1'='1 to alter query logic and bypass intended access controls.
Consider an Express route that retrieves a user by username and password received from Basic Auth:
const db = require('./db'); // hypothetical database client
app.get('/profile', (req, res) => {
const auth = req.headers.authorization; // 'Basic base64(credentials)'
if (!auth || !auth.startsWith('Basic ')) {
return res.status(401).send('Unauthorized');
}
const decoded = Buffer.from(auth.slice(6), 'base64').toString('utf-8');
const [user, pwd] = decoded.split(':');
// Vulnerable: directly interpolating user input into SQL
const query = `SELECT * FROM users WHERE username = '${user}' AND password = '${pwd}'`;
db.query(query, (err, rows) => {
if (err) return res.status(500).send('Server error');
if (!rows.length) return res.status(401).send('Invalid credentials');
res.json({ profile: rows[0] });
});
});
In this pattern, an attacker can set the Authorization header to Basic Oid9IG9yICcxJz0xJw== (which decodes to ' OR '1'='1:anything). The resulting SQL becomes SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything', potentially returning all rows and allowing unauthorized access. This is a classic SQL Injection that leverages authentication input.
Another scenario involves dynamic query building where the username is used to select a schema or table name, which cannot be parameterized using prepared statement placeholders. For example:
app.get('/tenant/:username/data', (req, res) => {
const username = req.params.username;
// Dangerous: using user input as an identifier
const query = `SELECT * FROM ${username}.records WHERE active = 1`;
db.query(query, (err, rows) => {
// ...
});
});
If the API also requires Basic Auth, the attacker could combine credential injection with identifier injection to escalate impact. Even when authentication is enforced, SQL Injection remains a risk because the same username may be reused elsewhere in the application with weaker validation.
Key takeaways: Basic Auth does not prevent SQL Injection; it only transports credentials. The vulnerability arises when those credentials are used unsafely in SQL. Always treat all external inputs—including Authorization header contents—as untrusted.
Basic Auth-Specific Remediation in Express — concrete code fixes
Remediation centers on strict input validation and the use of parameterized queries. Do not construct SQL by concatenating strings, even for authentication purposes. Use placeholders for values and avoid interpolating identifiers derived from user input.
1) Use parameterized queries for credential checks:
app.get('/profile', (req, res) => {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Basic ')) {
return res.status(401).send('Unauthorized');
}
const decoded = Buffer.from(auth.slice(6), 'base64').toString('utf-8');
const [user, pwd] = decoded.split(':');
// Safe: parameterized query
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [user, pwd], (err, rows) => {
if (err) return res.status(500).send('Server error');
if (!rows.length) return res.status(401).send('Invalid credentials');
res.json({ profile: rows[0] });
});
});
2) If you must use identifiers (e.g., schema or table names), validate them against a strict allowlist rather than trusting user input:
const allowedTenants = ['tenant_a', 'tenant_b', 'tenant_c'];
app.get('/tenant/:username/data', (req, res) => {
const username = req.params.username;
if (!allowedTenants.includes(username)) {
return res.status(400).send('Invalid tenant');
}
// Safe: identifier is from allowlist, not raw user input
const query = `SELECT * FROM ${username}.records WHERE active = 1`;
db.query(query, (err, rows) => {
// ...
});
});
3) Prefer a robust authentication layer instead of manual Basic Auth handling where possible, and ensure passwords are stored as hashes, not plaintext. For storage verification, use constant-time comparison to avoid timing attacks.
4) Add strict size and format checks on credentials to reduce noise in logs and prevent certain classes of injection:
const isValidUser = (u) => /^[A-Za-z0-9_-]{3,32}$/.test(u);
const isValidPass = (p) => p && p.length >= 8 && p.length <= 128;
if (!isValidUser(user) || !isValidPass(pwd)) {
return res.status(400).send('Invalid credentials format');
}
These practices reduce the risk of SQL Injection when combined with regular security testing. Tools such as middleBrick can help detect SQL Injection and authentication-related misconfigurations in your Express endpoints by scanning the unauthenticated attack surface and providing prioritized findings with severity and remediation guidance.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |