Injection Testing
Injection is a security defect where untrusted input crosses into a place that interprets it — a SQL query, an operating-system command, an LDAP filter, an XPath expression or a template — and part of that input runs as code instead of being treated as data. The classic example is a login field whose value is pasted straight into a SQL query: a value such as ' OR '1'='1 stops being an email address and becomes part of the query's logic.
Injection testing is the practice of checking, on a system you own or have written permission to test, whether inputs that reach an interpreter are kept separate from the code around them. You send carefully chosen, non-destructive inputs and watch how the system responds. This lesson covers the server-side interpreters; injection into the browser (cross-site scripting) is the next lesson.
Why it matters
Injection has sat among the OWASP Top 10 web application security risks for years, because the mistake is easy to make and the consequences are severe: one unparameterised query can expose every row in a table, let someone bypass a login, or corrupt data. The defect is invisible in normal use — the feature works perfectly for ordinary input — so it survives until someone sends input the developer did not expect.
For a tester, injection is high-value work: the probes are cheap, they follow a repeatable pattern across every input that reaches an interpreter, and a confirmed finding is usually fixed quickly because the remedy is well understood. Finding one injection point before release is far cheaper than a breach in production.
Where injection happens
Every injection has the same shape: data from the user is combined with a command for some interpreter, and the interpreter cannot tell which part was meant to be data. Wherever the application builds a command out of text, there is a candidate — and the interpreter does not have to be a database.
- SQL injection: input reaches a database query. A lone single quote
'is a metacharacter — a character with a special meaning to the interpreter, here the end of a text value — and it often provokes a database error, the first sign the input is being parsed as SQL. - OS command injection: input reaches a shell command, for example a file name the application inserts into a command it runs. Shell metacharacters such as
;,|and&can change what runs. - LDAP, XPath and NoSQL injection: input reaches a directory filter, an XML query or a document-database query, each with its own syntax and special characters.
- Server-side template injection: input reaches a template engine that evaluates expressions, which can turn a profile name into executed code.
- Reach matters more than the field name: a value from a URL, a header, a cookie or an uploaded file can travel to an interpreter just as a form field can.
Probing safely
Test only systems you own or have written permission to test, and prefer the mildest input that answers the question. The goal is to learn whether input is parsed as code, not to cause damage, so you never use payloads that delete, change or read out data.
The safest first probe is a single metacharacter on its own. If a ' produces a database error, a stack trace or a changed result while a normal value does not, the input is reaching the interpreter unprotected — enough to raise a defect. You confirm with one harmless comparison and go no further: two quotes '', which SQL reads as one literal quote inside a text value, should make the error disappear; or a normal value with an always-true condition joined by AND should return the usual result, while the same value with an always-false condition returns nothing.
- Use metacharacters as signals:
',",;,--for SQL;;,|,&for shells;*,(,)for LDAP. - Watch the differential: a database error, a changed response, a longer delay or a different row count between a benign and a crafted input is the evidence.
- Test every channel, including inputs hidden by the UI, because validation on the screen does not protect the server.
- Never run destructive or data-reading exploit chains; stop at the proof that input is interpreted.
Try it: Special Character Dataset Generator Try it: Negative Test Generator
Worked example: the fix is parameterised queries
Consider a customer lookup that finds users by email. The vulnerable version builds the query by joining strings, so the input lands inside the SQL text. The fix is a parameterised query (also called a prepared statement): the query text is fixed and the input is sent to the database separately, as a value that can never change the query's structure.
Input validation and escaping help, but they are not the fix for injection: the fix is the SQL row of the previous lesson's table of output contexts, a parameterised query. Valid data legitimately contains metacharacters: the surname O'Brien has an apostrophe, so validation cannot reject them. Escaping those characters is only a last resort for the server-side interpreters this lesson covers: each interpreter has its own special characters, and one that is missed reopens the hole. Keeping data and code separate with parameterisation is what actually closes it. A part of the query that cannot be bound, such as a column name chosen for sorting, must be checked against a fixed allow-list of names, and least-privilege database accounts limit the damage if an injection point is missed.
String-built queryVulnerable
The input is joined into the query text
sql = "... WHERE email = '" + input + "'"
- How the input is used:
- Pasted into the query text, so it can become code
- A single quote
'in the input: - Breaks the SQL — a database error or changed resultsFail
- What the test shows:
- A defect to report: the input is parsed as SQL
Parameterised querySafe
The query text is fixed; the input is bound
sql = "... WHERE email = ?"; params = [ input ]
- How the input is used:
- Sent to the database as a separate value, never as code
- A single quote
'in the input: - Compared as an ordinary character; no errorPass
- What the test shows:
- Input treated as data; the injection is closed
Text version
-- Vulnerable: input is pasted into the SQL text, so it can act as code
sql = "SELECT * FROM users WHERE email = '" + input + "'"
input = ben@shop.test -> ... WHERE email = 'ben@shop.test'
input = ' OR '1'='1 -> ... WHERE email = '' OR '1'='1' (logic changed)
-- Safe: the query text is fixed; input is bound as a separate value
sql = "SELECT * FROM users WHERE email = ?"
params = [ input ]
input = ' OR '1'='1 -> compared as text; matches no rowWhat to report
A good injection report lets a developer reproduce and fix the defect without guessing. Name the exact endpoint and parameter, the input you sent, the response that proves the input was interpreted, and the response for a benign input as a control. State the impact in terms of what the interpreter governs, and recommend the fix.
- Location: the URL or screen, the HTTP method, and the exact parameter, field, header or cookie.
- Input and evidence: the precise characters you sent and the observed result — the error text, the differential behaviour or the status code — with a benign input alongside for comparison.
- Impact and severity: what the affected interpreter controls (authentication, a data table, a shell), rated with your team's severity scale.
- Fix: parameterised queries or the interpreter's safe API, with validation and least privilege as defence in depth.
- Scope: note that you tested only the authorised system and stopped at proof, so the reader knows no data was touched.
Common mistakes
- Testing only the fields the UI shows, or stopping at the first vulnerable one, instead of checking every input that reaches an interpreter — headers, cookies, URL parameters and API-only inputs included.
- Treating input validation or escaping as the fix; they help, but parameterisation is what stops SQL injection, and escaping is only a last resort.
- Running destructive payloads. A tester never needs to delete or read data to prove an injection; the mildest probe is enough.
- Testing a system you do not own and have no written permission to test.
- Reporting "SQL injection found" with no endpoint, no input and no evidence, so the developer cannot reproduce it.
- Assuming only SQL matters and missing OS command, LDAP, XPath and template injection.
The QA perspective
On a team, injection testing starts at requirements and design review: every place the application builds a query or a command from input is a risk to flag before it is written, and the question "is this parameterised?" belongs in code review. During testing you work from a checklist of inputs that reach interpreters, run the same safe probes against each, and keep a benign control for every crafted input so your evidence is clear.
You coordinate with developers and, on larger systems, with a security team: agree in writing what you may test and when, use a non-production environment with least-privilege accounts, and hand a confirmed finding over with reproduction steps and a fix recommendation. Record what you tested and what you deliberately did not, so the next tester and the auditor can see the scope.
Practice exercise
Turn a vulnerable search into a safe one and report it
A product-search endpoint GET /api/products?q=<term> builds its query by joining strings in the application code: "SELECT * FROM products WHERE name LIKE '%" + q + "%'". On a test system you own, you send q with a single quote and get a database error; a normal term returns results. Identify the injection point, choose a safe confirming probe, describe the behaviour you expect before and after the fix, and draft the defect report.
- Name the interpreter and the exact input channel.
- Write the mildest probe that confirms the input is parsed as SQL, and a benign control.
- Write the parameterised version of the query and the behaviour you expect after the fix.
- Draft the report: location, input, evidence, impact, severity and fix.
Show the model answer
Interpreter: the SQL database. Channel: the q query-string parameter of GET /api/products.
Probe: a single quote ' as the value of q gives the database error; control: a normal term such as pen returns the usual list. Confirming probe: two quotes '', which SQL reads as one literal quote inside the text value, make the error disappear. An error that comes and goes with the number of quotes proves that q is parsed as SQL; no destructive or data-reading payload is needed.
Parameterised: SELECT * FROM products WHERE name LIKE ? with the bound value %pen% (the application adds the % signs to the value, not to the SQL text), so the term is a value, never part of the query text. After the fix, q=pen still returns the pens, and ' returns an ordinary result (products whose name contains an apostrophe, usually none) with no database error.
Report: Location GET /api/products, parameter q. Input: a single ', confirmed with ''. Evidence: a database error for ' (quote the message), no error for '', and the normal list for q=pen. Impact: the search query is injectable, so the data in products and possibly other tables the database account can read is exposed; rate it on the team's severity scale (typically critical or blocker). Fix: use a parameterised query; add least-privilege database accounts and keep input validation as defence in depth. Scope: tested only on the owned test system and stopped at proof; no data was modified or extracted.
Frequently asked questions
Is injection testing the same as penetration testing?
No. Penetration testing is a broad, usually time-boxed authorised attack on a whole system by a specialist. Injection testing is a focused security check any tester can run on specific inputs. Both require permission; injection testing fits naturally into normal test design.
Do I need to know SQL to test for SQL injection?
A little helps you read queries and understand why a probe works, but you can confirm many injection points by behaviour alone: a metacharacter that causes an error or changes the result where a normal value does not. The SQL for Testers guide goes deeper.
Why is validation not enough to stop injection?
Validation narrows what input is accepted, but valid data can still contain metacharacters — the name O'Brien has an apostrophe — and rules can be bypassed or forgotten. Escaping those characters is only a last resort; keeping data separate from code with parameterised queries removes the vulnerability at its source.
What severity should an injection defect get?
It depends on what the interpreter controls and whether authentication or sensitive data is reachable, but a confirmed injection into a production-bound query is usually critical or blocker. Record the reasoning on your team's severity scale rather than guessing.
References
- OWASP Top 10:2025The ten most critical web application security risks, with how to test for and prevent each.
- OWASP Web Security Testing Guide (WSTG)The reference methodology for testing web applications and APIs for security weaknesses, test by test.
- OWASP Cheat Sheet Series: SQL Injection PreventionParameterised queries, stored procedures used safely, allow-list validation and least privilege — the defences a database test should confirm.
- OWASP Cheat Sheet Series: Injection PreventionPreventing SQL, OS-command and other injection through parameterisation, validation and escaping.
Practise with Mutqan tools
Generate inputs full of special characters: quotes, SQL/HTML/JSON metacharacters, control chars, emoji and path tricks.
Generate negative test cases for form fields and APIs: empty, null, wrong type, too long, injection, invalid format.
Check SQL statements for syntax problems, unbalanced parentheses, unterminated strings and risky patterns.
Generate high-level test scenarios covering functional, negative, security, usability and compatibility angles for a feature.
Compose and send HTTP requests (method, URL, headers, auth, body) and inspect the response — a lightweight Postman in your browser.
Go deeper
- Performance & Security Resilience TestingSecurity testing: the OWASP baseline
- Database Testing for QA EngineersSecurity: injection, privileges and personal data
- SQL for TestersPractical SQL for QA: count and sum reconciliation, orphan and duplicate detection, NULL and date checks, dialect differences and safe practice.
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.