SQL injection is a database-query construction bug. The input can arrive in a URL, JSON body, cookie or header; its location does not make it trustworthy. The useful review is to follow each value from the request to every query that consumes it, including analytics and authentication lookups.
This guide combines the request-vector map with the seven field-specific reviews. For the underlying vulnerability classes, use the SQL injection guide and the SQL injection learning path.
The map
A vector identifies the input location. A technique describes how a vulnerable query behaves. Neither tells you whether a particular application is vulnerable without examining the data flow.
| Request input | Typical database use | Review boundary |
|---|---|---|
| Query string and path | Product, account or document lookup | Bind values; authorize access to the selected record |
| Form or JSON body | Search, filtering, inserts and updates | Validate shape, then bind each value |
| XML elements | Import and integration data | Harden the XML parser separately from SQL construction |
| Multipart filename | Upload metadata or audit entry | Generate storage names; bind the original display name |
| User-Agent and Referer | Analytics and attribution | Logging queries need the same protections as application queries |
| Forwarded IP headers | Audit, geolocation and blocking | Establish proxy trust before choosing an IP |
| Host and forwarded host | Tenant lookup | Validate the allowed hostname and bind the lookup |
| Cookies | Session or application preferences | Cookie parsing is not SQL parameterization |
| Authorization or API key | Token lookup, identity and permissions | Validate credentials; bind any downstream query |
| Other headers and HTTP method | Locale, correlation or audit data | Trace only inputs the application actually consumes |
URL: query string parameters
A query containing WHERE id = $1 is safe from value injection when the database driver binds the value to that placeholder. By contrast, building SQL by joining a request value into the statement lets input become SQL syntax. A placeholder shown in a string is not enough: check the actual execution call and its parameter list.
Validate an identifier's expected form and apply record-level authorization as well. Parameterization prevents a value from rewriting SQL; it does not decide whether the requesting user should see that record.
URL: path segments
A router extracting an ID from /products/123/reviews does not change the query rule. Use the extracted value as a bound parameter. A routing regex can reject malformed IDs but should not be the only protection at the database boundary.
Body: form-urlencoded fields
Treat form inputs as values, including hidden fields. The browser's input type and client-side validation do not constrain a request made by another HTTP client. Review login, search and write paths, plus the error or audit queries they trigger.
Body: JSON fields
JSON decoding verifies a serialization format. It does not make q, nested filter values or array elements safe to interpolate into SQL.
The risky boundary is often an ORM's raw-SQL escape hatch. Django's raw() supports a separate parameter list; supplying that list correctly differs from formatting values into the query string. Follow the API's documented binding syntax and do not quote placeholders yourself. Django's raw SQL documentation gives both the supported pattern and its limits.
A PHP/PDO search handler, assuming $pdo is an existing connection and authentication has already run, can keep the value outside the statement:
$body = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
$q = is_array($body) ? ($body['q'] ?? null) : null;
if (!is_string($q) || strlen($q) > 200) {
throw new InvalidArgumentException('Expected a search string of at most 200 bytes.');
}
$stmt = $pdo->prepare('SELECT id, title FROM articles WHERE title = :title');
$stmt->execute(['title' => $q]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);Handle malformed JSON and validation exceptions as controlled client errors in your framework. The 200-byte limit here is a product constraint for this example, not a threshold below which SQL injection becomes impossible.
Placeholders bind values, not arbitrary table names, column names or sort direction. Map a requested sort key to a fixed server-owned column list. Do not concatenate a JSON key merely because the corresponding values are bound.
Body: XML elements and attributes
After an XML parser extracts text, the same parameter-binding rule applies. External entity resolution is a separate concern: a parameterized SQL query does not fix an unsafe XML parser. Keep both boundaries in the review.
Body: multipart filename
The upload's original filename is request data. Bind it when saving metadata, escape it when displaying it, and choose the on-disk filename on the server. Upload validation and storage controls are covered in the file upload guide.
Header: User-Agent
The analytics flow is often request → middleware → page_views insert. A rejected application request may still reach that insert. Review the logging path even if the main handler never queries a database.
For a PDO-backed log table, the important change is binding the header rather than interpolating it:
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$stmt = $pdo->prepare('INSERT INTO page_views (user_agent) VALUES (:user_agent)');
$stmt->execute(['user_agent' => $userAgent]);Apply a documented storage limit before this call, or reject oversized data according to your logging policy. A long header is not proof of an attack, and a short header is not proof of safety. Review asynchronous ingestion jobs too: a value stored safely in a queue can become unsafe when a later job constructs SQL from it.
Header: Referer
Referer is optional and can be supplied by an arbitrary client. It is useful attribution data, not proof that the visitor came from the named site. Parse it as a URL only when the feature needs URL fields, handle absent or malformed values, and bind any stored value.
The preservation detail for this vector is the second use: reporting jobs may group by domain, search the raw URL, or construct a dynamic report. Keep values bound there as well. Escape stored URLs when rendering an analytics dashboard; SQL safety and HTML safety are separate.
Header: X-Forwarded-For (and X-Real-IP, X-Client-IP)
The socket peer may be a reverse proxy. A forwarded header can describe the earlier client, but only within a known proxy arrangement. Do not blindly choose the first comma-separated value or trust every request carrying the header.
Configure the framework with the trusted proxy addresses or network ranges. Make the last trusted proxy remove or overwrite client-supplied forwarding headers, and account for alternate paths to the application. Express documents how its trust proxy setting changes address selection and the risks of a mismatched configuration. Express proxy guidance.
After selecting the address, validate it as IPv4 or IPv6 and bind it in geolocation, audit and block-list queries. Proxy trust establishes where a value came from; parameterization protects how a database uses it. Keep the original forwarding chain only if needed for diagnostics and subject it to logging limits.
Header: Host and X-Forwarded-Host
For a tenant lookup, use a normalized hostname from a trusted routing boundary, check it against the application's allowed domains, and bind it in the lookup. Resolve only registered tenant domains; a syntactically plausible hostname is not proof that a tenant owns it.
TLS does not make an HTTP Host value safe to concatenate. Certificate validation and SNI are not replacements for request-authority validation. A cache is a performance choice, not a SQL injection defense: the cache's initial database lookup must still be safe, and tenant authorization must remain correct on cache hits.
If the application uses X-Forwarded-Host, establish which proxy supplies it and ensure direct-origin requests cannot smuggle a different value through the same trust path.
Header: Cookie (non-session cookies)
Preference cookies might choose a theme, language or experiment bucket. Validate against the values the feature supports, and bind the selected value if it reaches a database.
Session cookies deserve the same review. Framework parsing separates cookie names from values; it does not sanitize values for SQL. A database session adapter, custom session lookup or audit insert must still use safe queries. Signing a cookie detects modification when verified correctly, but it does not change the database API's contract.
Preserve the difference between storage and later use. A cookie-derived value safely inserted today can become a second-order problem if a reporting or profile job later interpolates it into another statement.
Header: Authorization
An opaque API key can be implemented safely with a bound lookup. A signed JWT can be implemented unsafely if a claim is later concatenated into SQL. Authentication format alone does not settle this question.
Validate the credential using the chosen scheme, then validate the claims or identifier expected by the application. JWT's sub claim is a string; signature verification does not automatically turn it into a database-safe numeric identifier. RFC 7519.
If tokens are stored as digests, bind the digest lookup too. Do not log raw Authorization headers or bearer credentials while investigating this flow. Use an internal request ID and a non-secret account identifier for diagnostics. Migration to a different token format is a separate architecture decision, not the required fix for an unsafe query.
Header: Accept-Language
Map supported locales to a fixed list, then bind the selected locale when querying translations or updating preferences. A locale token can be perfectly valid for HTTP while unsupported by your application.
Header: custom (X-Api-Key, X-Tenant-Id, X-Request-Id)
Inventory the headers your application reads, including middleware and background ingestion. API keys follow the token-lookup rules; tenant IDs follow the lookup and authorization rules; correlation IDs follow the logging rules. A custom name does not create a special trust boundary.
Other: HTTP method
An HTTP parser constrains method syntax, but an audit logger should still bind the method as a value. Do not rely on a list of common methods to justify string-built SQL.
How do you actually test all of these vectors?
Start with source review and a local test database. Use ordinary values containing punctuation and Unicode to verify they are stored or compared literally. Exercise malformed types, oversized values, absent optional headers and rejected authentication paths. Assert that a request never changes the intended query structure or reads another user's rows.
Scanner coverage has limits. sqlmap documents cookies at level 2, User-Agent and Referer at level 3, and Host at level 5. This does not mean level 5 automatically tests every application-specific header. See the official coverage documentation and the separate sqlmap reference.
A response difference, timeout or database error is a lead, not confirmation. Correlate it with the application path, query behavior and a controlled repeat. Network jitter, validation and authorization can all change a response without SQL injection.
Defender's checklist
- Bind values in application, authentication, analytics and background-job queries.
- Map dynamic SQL identifiers to fixed choices owned by the server.
- Validate request shapes and enforce limits for the feature's requirements.
- Configure forwarding-header trust explicitly; validate tenant domains separately.
- Give each database account only the operations and data its job needs.
- Keep credentials out of logs and return controlled errors to clients.
- Add regression cases for each repaired data flow, including rejected requests.
The OWASP prevention guide explains why parameterization, allowlisted identifiers and least privilege complement each other.
A framework can also have a query-construction bug
Drupal's May 2026 advisory describes SQL injection in its database abstraction API affecting PostgreSQL installations, including anonymous exploitation. That is a reason to maintain framework updates alongside application code review. The advisory does not establish that every JSON endpoint or request-header path is affected. Drupal SA-CORE-2026-004.
Where to go next
Use the SQL injection guide for the underlying mechanics, the sqlmap reference for tool behavior, and the web application security taxonomy to keep related parser and authorization issues separate.
Sources
Authoritative references this article was fact-checked against.
- OWASP — SQL Injection Preventioncheatsheetseries.owasp.org
- PHP — PDO prepared statementsphp.net
- Django — performing raw SQL queriesdocs.djangoproject.com
- RFC 7519 — JWT subject claimdatatracker.ietf.org
- Express — trusted proxy configurationexpressjs.com
- sqlmap — documented request coveragegithub.com
- Drupal — PostgreSQL database abstraction advisorydrupal.org





