What Is a Database Query? SQL and Query Execution Explained
A foundational guide to database queries: how SQL works, how queries are compiled and executed, and why separating code from data prevents injection.
Short answer
A database query is a structured instruction that tells a database what data to retrieve, insert, update, or delete. When application code builds queries by concatenating user input with SQL text, the user's data becomes part of the instruction — that is SQL injection.
The idea in one minute
Think of a database as a filing room with a very literal clerk. You write your request on a slip of paper: "Give me the file for account 4471." The clerk reads it and retrieves the file. Everything works.
Now imagine the clerk reads the request as: "Give me the file for account 4471; also, list all the other account files in the room." If someone writes that second instruction on the same slip of paper — after the intended request — the clerk treats it all as one instruction and follows both parts.
A database query works the same way. SQL (Structured Query Language) is a language that the database server speaks natively. When an application builds a query like "SELECT * FROM users WHERE id = '" + userInput + "'", the user's input is pasted directly into the query text. If the input contains SQL keywords, quotes, or operators, those become part of the query — not part of the data. The database has no way to know which parts of the query text were intended as code and which came from user input.
How SQL queries actually work
SQL is a declarative language: you describe what you want, not how to get it. A typical query has clauses that each serve a specific purpose:
SELECT first_name, last_name, email -- what columns to return
FROM users -- which table to search
WHERE account_id = 4471 -- which rows to include
ORDER BY last_name; -- how to sort the result
The database server processes this through a pipeline:
The query execution pipeline
SQL text
│
▼
Parser: tokenize SQL text, check syntax, build parse tree
│
▼
Resolver: map table/column names to actual database objects
│
▼
Optimizer: consider indexes, join strategies, statistics → choose a plan
│
▼
Executor: carry out the plan → fetch rows from storage
│
▼
Result set: rows returned to the caller
The parser has no concept of "this part came from user input." It sees a single string of SQL text and applies the SQL grammar to the whole thing. If user input contains a single quote, it closes the string literal the application opened. If it contains a --, it comments out the rest of the query. If it contains UNION SELECT, it appends an entirely new query to the result set.
A minimal example
A login endpoint that builds a query by concatenation:
username = request.form["username"]
password = request.form["password"]
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
Intended usage — the user submits alice and mypassword:
SELECT * FROM users WHERE username = 'alice' AND password = 'mypassword'
The database looks for a row matching both conditions. If found, login succeeds.
Attacker usage — the user submits admin' -- as the username and anything as the password:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
The ' in the input closes the username string. The -- turns the rest of the query into a comment. The database sees only:
SELECT * FROM users WHERE username = 'admin'
The password check is gone. If an admin user exists, login succeeds without knowing the password.
Why concatenation creates vulnerability
The root cause is always the same: user input is treated as SQL text rather than as data. This happens whenever applications build query strings using:
- String interpolation or concatenation:
f"SELECT ... WHERE id = {input}","SELECT ... WHERE id = '" + input + "'" - Incomplete escaping: applying
escape_string()oraddslashes()but missing edge cases (character set mismatches, alternative encodings, second-order injection where escaped data is re-interpreted) - Dynamic table or column names: these cannot be parameterized in most SQL APIs and must be validated against an allowlist instead
The alternative — parameterized queries (also called prepared statements) — separates the SQL structure from the data at the protocol level:
cursor.execute(
"SELECT * FROM users WHERE username = ? AND password = ?",
(username, password)
)
The SQL text is sent to the database first. The database parses it, compiles a query plan, and then receives the parameters as pure data values. The parameters can never become SQL syntax because the parsing phase is already complete. A value of admin' -- in a parameterized query is treated as a literal string that happens to contain those characters — it is not interpreted as SQL.
What attackers look for
- Every input field that feeds into a database query: login forms, search bars, filters, sort parameters, pagination offsets, product IDs in URLs
- Parameters that affect the structure of a query, not just the values:
ORDER BYcolumns, table names,LIMITvalues - API endpoints that reflect database results in the response (confirms injection is possible and readable)
- Blind injection points where results are not reflected but behavioral signals exist (timing, error messages, boolean responses)
- The characters
',",;,--,#,/*,UNION, andORin any input that reaches a database
Detection
- Code review: search for database calls that use string concatenation (
+,.,f"",%sformatting) instead of parameterized queries or an ORM. Trace user-controlled values to the query construction site. - Dynamic testing: submit a single quote (
') into every parameter and observe the response. A database error ("unclosed quotation mark", "syntax error") is a strong signal that the input is interpolated into a query. - Boolean-based testing: submit conditions that are always true (
' OR '1'='1) and always false (' AND '1'='2) and compare responses. Different behavior between the two confirms the injection point. - Time-based testing: submit
'; WAITFOR DELAY '0:0:5'--(MSSQL) or' OR SLEEP(5)--(MySQL) and measure response time. A delay confirms execution.
Verification: real vulnerability or false positive?
A database error from a single quote is suspicious but not definitive — the application may handle errors safely. Confirm by showing one of:
- A boolean-based test where
' OR '1'='1and' AND '1'='2produce measurably different responses (content, status code, or redirect behavior) - A time-based payload that produces a consistent delay of 5+ seconds compared to a baseline request
- A
UNION SELECTpayload that returns data from an unintended table in the response - An out-of-band exfiltration payload (
'; EXEC xp_cmdshell('curl ...')orCOPY ... TO PROGRAM ...) that reaches infrastructure you control
The evidence must show that the database executed your injected SQL, not just that it attempted to parse a malformed query.
Real-world impact
- Authentication bypass: logging in as any user without knowing their password — the most common and most damaging outcome
- Data breach: extracting all rows from any table — user credentials, personal data, payment information, proprietary data
- Data modification: inserting, updating, or deleting rows — defacing content, changing prices, creating unauthorized accounts
- Privilege escalation: in some database configurations, SQL injection can escalate to reading/writing files on the server's filesystem (
LOAD_FILE,INTO OUTFILE,xp_cmdshell) - Full server compromise: stacked queries (
; DROP TABLE users --) or stored procedure execution can turn a database bug into operating-system-level access
Prevention
- Use parameterized queries (prepared statements) for all query values. This is the single most effective control — it eliminates SQL injection as a class of vulnerability when applied consistently.
- Use an ORM (Object-Relational Mapper) that handles parameterization for you. Most ORMs — SQLAlchemy, Prisma, Hibernate, Entity Framework — use parameterized queries internally. But be aware that raw query APIs within ORMs still require parameterization.
- Validate against an allowlist for dynamic table names, column names, and SQL keywords (
ORDER BY,LIMIT). Never interpolate these from user input without checking against a predefined set of acceptable values. - Apply least privilege: the database user the application connects as should have only the permissions it needs — no
DROP, noCREATE, no file access. This limits what an attacker can do even if injection succeeds. - Escape identifiers (column/table names in dynamic SQL) using the database's own quoting function if an allowlist is not feasible — but this is a fallback, not a primary defense.
Related vulnerabilities
- SQL injection — the direct consequence of unsanitized input in a query string
- NoSQL injection — the same principle applied to NoSQL databases (MongoDB, Couchbase), where query operators ($gt, $ne, $where) can be injected through JSON or URL parameters
- Second-order SQL injection — data that is safely stored (parameterized on write) but later interpolated into a query unsafely on read
- Authentication vulnerabilities — SQL injection is one of the most common mechanisms for bypassing authentication
Testing methodology (do this safely)
- Test only against targets you own or have explicit authorization to test. SQL injection can destroy data — a destructive payload on someone else's database is not a test, it's an incident.
- Start with boolean-based or time-based payloads to confirm injection before attempting data extraction using
UNION SELECT. - If you confirm injection, extract only a minimal data sample (1–2 rows from a non-sensitive table) to prove the vulnerability exists. Do not exfiltrate real user data.
- Use time-based payloads carefully — they place load on the database. A single
SLEEP(5)is sufficient; chaining delays can degrade performance for other users. - Document the exact parameter, the injection point, the payload used, and the evidence of execution. A reproducible proof of concept is what turns a suspicion into a reportable finding.
Further reading
- W3Schools: SQL Introduction
- ISO: SQL Standard (ISO/IEC 9075)
- OWASP: SQL Injection
- PortSwigger: SQL injection
Nyxeara perspective
Nyxeara's scan engine approaches SQL injection detection through three parallel channels: static analysis of query construction patterns in source code, dynamic injection probes targeting every input parameter, and response-analysis heuristics that distinguish database errors from application-level error handling. A finding is only elevated to confirmed when parameterized-query absence is corroborated by a behavioral signal — a timing differential from a SLEEP payload, a boolean response asymmetry, or a UNION-based data reflection. The engine treats parameterized queries as the normative baseline and flags any code path that bypasses them, even before dynamic confirmation is attempted.