TechEarl

SQL Injection in HTTP Requests: Headers, Cookies and JSON

Trace SQL injection through JSON, cookies, authorization, analytics headers and tenant lookups. Prepared-query examples, proxy boundaries and review checks.

Ishan Karunaratne⏱️ 11 min readUpdated
Share thisCopied
Every HTTP request field that carries SQL injection: URL, body, User-Agent, Referer, Cookie, X-Forwarded-For, Host, Authorization, custom headers.

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 inputTypical database useReview boundary
Query string and pathProduct, account or document lookupBind values; authorize access to the selected record
Form or JSON bodySearch, filtering, inserts and updatesValidate shape, then bind each value
XML elementsImport and integration dataHarden the XML parser separately from SQL construction
Multipart filenameUpload metadata or audit entryGenerate storage names; bind the original display name
User-Agent and RefererAnalytics and attributionLogging queries need the same protections as application queries
Forwarded IP headersAudit, geolocation and blockingEstablish proxy trust before choosing an IP
Host and forwarded hostTenant lookupValidate the allowed hostname and bind the lookup
CookiesSession or application preferencesCookie parsing is not SQL parameterization
Authorization or API keyToken lookup, identity and permissionsValidate credentials; bind any downstream query
Other headers and HTTP methodLocale, correlation or audit dataTrace 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:

php
$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:

php
$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.

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.

TagsSQL InjectionHTTP HeadersWeb SecurityPenetration TestingOWASPApplication Security

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Systems and Network Architect · Chief Technology Officer

Systems and network architect and Chief Technology Officer with more than two decades designing, building, and running production software, cloud and network architecture, Linux systems, and the bare metal underneath them, and lately working AI into the stack. A US Army veteran who served in Operation Iraqi Freedom. What I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

Complete SQL injection guide: union, error-based, boolean blind, time blind, out-of-band, second-order, NoSQL. Exploits and defence.

SQL Injection: Variants, Exploitation, and Defence

How SQL injection actually works, what every major variant looks like (union-based, error-based, boolean blind, time blind, out-of-band, second-order, NoSQL), how to exploit each one against a vulnerable app, and how to defend against them at the code, query, and infrastructure layers.