Broken Authentication

Broken Authentication in Node.js (Express) guide [May 2026] [CVE-2023-46453]

[Updated May 2026] Updated CVE-2023-46453

Overview

CVE-2023-46453 describes an authentication bypass on certain GL.iNet devices where a login username could be crafted to be both a valid SQL statement and a valid regular expression, effectively bypassing authentication and granting administrative control on the device. While this CVE is specific to GL.iNet firmware, it illustrates a broader class of Broken Authentication flaws where untrusted input influences both data access queries and authorization logic. In Node.js (Express) apps, similar risks arise when user input is directly interpolated into SQL queries (SQL injection) and used to influence runtime checks such as regex-based authorization, leading to bypasses or privilege escalation. If an app uses a single input source for both data access decisions and access control logic, attackers can craft inputs that satisfy multiple constraints (e.g., bypassing a login check while matching privileged roles), mirroring the real-world impact described by CVE-2023-46453. This guide focuses on recognizing these patterns in Node.js/Express and implementing robust defenses.

Code Fix Example

Node.js (Express) API Security Remediation
Vulnerable:
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const app = express();
app.use(express.json());

const db = new sqlite3.Database(':memory:');
db.serialize(() => {
  db.run('CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT, role TEXT)');
  db.run("INSERT INTO users (username, password, role) VALUES ('admin','adminpass','admin')");
});

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // Vulnerable: SQL string interpolation and using user input to build a regex for authorization
  const sql = `SELECT id, role FROM users WHERE username = '${username}' AND password = '${password}'`;
  db.get(sql, (err, row) => {
    if (err) return res.status(500).send('Server error');
    if (!row) return res.status(401).send('Invalid credentials');
    const re = new RegExp(username); // attacker could craft username to bypass checks via regex
    if (re.test(row.role)) {
      return res.json({ token: 'fake-token', role: row.role });
    }
    res.status(403).send('Forbidden');
  });
});

app.listen(3000, () => console.log('Server running'));

Fixed:
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const app = express();
app.use(express.json());

const db = new sqlite3.Database(':memory:');
db.serialize(() => {
  db.run('CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT, role TEXT)');
  // Passwords should be hashed; this is just a demonstration
  db.run("INSERT INTO users (username, password, role) VALUES ('admin','adminpass','admin')");
});

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // Safe: parameterized query prevents SQL injection
  const sql = 'SELECT id, role, password FROM users WHERE username = ?';
  db.get(sql, [username], (err, row) => {
    if (err) return res.status(500).send('Server error');
    if (!row) return res.status(401).send('Invalid credentials');
    // Compare hashed password (example uses plain text for brevity; replace with bcrypt.compare)
    if (password !== row.password) return res.status(401).send('Invalid credentials');
    // Do not use user input to construct regex for authorization
    if (row.role === 'admin') {
      return res.json({ token: 'fixed-token', role: row.role });
    }
    res.status(403).send('Forbidden');
  });
});

app.listen(3000, () => console.log('Server running'));

CVE References

Choose which optional cookies to allow. You can change this any time.