Q3 of 24 · Security
What is SQL injection and how does a QA tester verify that inputs are protected?
Short answer
Short answer: SQL injection occurs when user input is interpolated into a SQL query, allowing an attacker to alter the query's logic. A QA tester validates protection by submitting injection payloads in text fields and URL parameters, then asserting the response is a normal error or result — not leaked data, not an unhandled exception, not an HTTP 500.
Detail
The vulnerability occurs when code builds a SQL query with string concatenation rather than parameterised queries (prepared statements). A payload like ' OR '1'='1 injected into a login field can turn WHERE username = 'input' into WHERE username = '' OR '1'='1', which evaluates to true for every row — bypassing authentication entirely.
What a QA tester tests:
- Submit common injection payloads in every user-controlled text field:
',' OR '1'='1' --,'; DROP TABLE users; --,1 UNION SELECT null-- - Submit payloads in URL query parameters, form fields, headers (User-Agent, Referer), and JSON body fields in API requests
- Assert the expected response: a validation error ("invalid input") or a normal business-logic error. A correct response is not a 500, not a database error message, not a stack trace.
- Assert the application does NOT return data from tables other than intended (a sign a UNION-based injection worked)
What the fix looks like: parameterised queries (prepared statements) treat all user input as data, not SQL syntax. ORM query builders do this by default. Input validation (allow-list) is a defence-in-depth measure but not a substitute for parameterised queries — a determined attacker can bypass many input validation schemes.
// EXAMPLE
sql-injection.test.ts
const injectionPayloads = [
"' OR '1'='1",
"'; DROP TABLE users; --",
"1 UNION SELECT null, null --",
"admin'--",
];
for (const payload of injectionPayloads) {
const response = await request.post('/api/login', {
data: { email: payload, password: 'anything' },
});
// Must not be 200 (bypass), must not be 500 (unhandled DB error)
expect(response.status()).toBe(401);
expect(await response.text()).not.toContain('SQL');
expect(await response.text()).not.toContain('syntax error');
}