A wave of injection attempts hits at 2am. The firewall holds the line while you triage, contain, and chase down why the query was ever exploitable.
A web application firewall (WAF) sits in front of your app and reads every incoming request before the app does. Most traffic is ordinary — logins, searches, page views. But sometimes a spike of SQLi payloads arrives: strings like ' OR '1'='1 or UNION SELECT aimed at tricking a database query into doing something it shouldn't.
The WAF matches each request against its ruleset. Legit traffic passes through to the app; payloads that match an injection signature are blocked at the gate, never reaching the database. Treat the spike as an on-call incident with three phases — triage (detect and confirm), contain (lean on the WAF), root-cause (fix the query that made it exploitable).
The WAF is a signature matcher: it scans the request for patterns that look like injection and blocks on a hit. That stops the wave at the edge — but it does not make the query safe. The durable fix lives in the app: stop building SQL by gluing strings together, and let the driver bind user input as a parameter so it can never change the shape of the query.
# --- WAF (defense in depth: matches the attack, not the bug) ---
if matches_any(request.body, RULES.sqli): # ' OR '1'='1 , UNION SELECT, ...
block(request) # 403 - never reaches the app
# --- App: the ROOT CAUSE - string-concatenated query ---
# vulnerable: the input becomes part of the SQL text
q = "SELECT * FROM users WHERE name = '" + name + "'"
db.execute(q) # name = "' OR '1'='1" -> returns every row
# --- App: the DURABLE FIX - parameterized / prepared statement ---
q = "SELECT * FROM users WHERE name = ?" # input is data, not code
db.execute(q, [name]) # the driver binds ?, so payloads stay inert
Note the order of trust: the parameterized query is the real defense — the input can never break out of the value slot. The WAF is a second layer that buys you time and blocks known patterns, but a clever payload may slip past a signature. Fix the query; keep the WAF.
| Choice | Buys you | Costs you |
|---|---|---|
| WAF rule (defense in depth) | Blocks the wave at the edge, fast to deploy as a virtual patch | Not the fix — a novel or encoded payload can bypass the signature |
| Parameterized query (the real fix) | Input can never change the query shape; closes the hole for good | Needs a code change and deploy; slower than a rule |
| Signature vs anomaly / rate | Signatures are fast and precise on known patterns | Anomaly and rate catch more but raise more false positives |
| Block mode vs monitor mode | Block stops the attack now; monitor lets you tune without breaking users | Block can hurt legit traffic if over-tuned; monitor leaves you exposed |
Triage. At 02:14 an alert fires: blocked requests on /search jumped from a handful per minute to hundreds. You confirm it's an injection wave — the payloads carry UNION SELECT and ' OR '1'='1 — not a deploy glitch, and the app's error rate is flat, so nothing is getting through yet. Contain. You tighten the WAF SQLi rule and rate-limit the source range; blocked-per-window climbs while legit pass-through stays healthy, so real users keep searching. Root-cause. You open the handler and find "... WHERE name = '" + name + "'" — a string-concatenated query. You ship the parameterized version (WHERE name = ? with a bound value), and now the endpoint is safe even if a payload ever slips the WAF. The firewall stays on as defense in depth.
The WAF is blocking the SQLi wave cleanly and legit users are unaffected. Is the incident resolved?
Coach note: the WAF is defense in depth, not the fix. A novel or encoded payload can bypass a signature — only the parameterized query closes the hole for good. Take another pass if that ordering feels slippery.