A small staff portal. The login form takes the username and password and puts them straight into an SQL query. There is no protection at all, and the page shows you the query the server ran so you can see the effect of what you type.
This is the Ravens staff portal, a small two-page app: a login page and a crew board behind it. The login page posts the username and password to /login, and the server prints the exact query it built, so you see the effect of every character you type. An admin account exists, but its password is random and changes each time.
Log in as the admin account without knowing its password.
The server answers that you are logged in with an account whose role is admin.
Why the solution worked
The server built the query by pasting what you typed into SQL text. The quote you typed ended the string value, so the database read the rest of your input as code, not data. With the -- comment you removed the password condition, leaving a query that only asks for the user admin. The database did exactly what it was given; the mistake is in the code that mixed data with commands.
The root cause
The line that builds the query by string concatenation, pasting the username value into the SQL text between quotes. To the database there is no boundary between what the developer wrote and what the user typed; it is one string parsed as commands.
How the code is fixed
The fix is parameterized queries: the query text is fixed and the values are sent separately, so no character in the input can turn into code. Filtering quotes is not a fix. And passwords are stored as hashes, not compared inside the query.
Vulnerable code
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Input is pasted into the SQL text: a quote in the input changes the query itself.
const sql = "SELECT id, username, role FROM users " +
"WHERE username = '" + username + "' AND password = '" + password + "'";
const user = db.get(sql);
if (user) startSession(res, user);
});
Safe code
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// Placeholders: the query text is fixed, and the values travel separately as data.
const user = db.get('SELECT id, username, role, password_hash FROM users WHERE username = ?', [username]);
// Passwords are stored as hashes and compared with a password-hashing function.
if (user && (await verifyPassword(password, user.password_hash))) startSession(res, user);
});
Impact in the real world
Login bypass is the simplest face of SQL injection. In the real world injection has dumped entire databases and leaked millions of users; it has sat at or near the top of the OWASP Top Ten for years.
How defenders detect it
Defenders watch server logs for quotes, -- comments and or 1=1 in login fields, and for spikes in SQL errors. WAFs and detection rules flag these patterns, but relying on them does not replace the real fix.