If you've spent any time configuring user authentication on... Full Story
By Manny Fernandez
October 13, 2026
Anatomy of a SQL Injection Attack “SQLi”
Anatomy of a SQL Injection Attack

Executive Summary
Objective: Walk a SQL injection (SQLi) attack from first probe to full data exfiltration, stage by stage, then map a concrete defensive control to each stage. This is a defender-first breakdown. You will see what the attacker does and, more importantly, where you break the chain.
Why this still matters in 2026: SQL injection refuses to die. CWE-89 sits at rank 2 on the 2025 CWE Top 25 Most Dangerous Software Weaknesses, up one spot from the prior year. It also anchors the A05:2025 Injection category in the OWASP Top 10, which, while it slipped from #3 to #5, still accounts for well over 14,000 SQL-injection CVEs. Frameworks and ORMs have blunted the classic string-concatenation bug, but legacy apps, raw queries, and sloppy ORM escape hatches keep the attack surface wide open.
Target audience: Network and security engineers, FortiGate administrators, and anyone who owns a web application or the firewall in front of it. You do not need to be a database administrator to follow along, but a working knowledge of HTTP and basic SQL helps.
Prerequisites and Architecture
Assumed knowledge:
- Basic SQL (SELECT, UNION, WHERE)
- HTTP request structure (GET/POST, query strings, headers, cookies)
- FortiGate policy and UTM profile basics (helpful for the defense section)
The target environment: A classic three-tier web application. The pattern never changes, only the technology stack does.
| Tier | Component | Role in the attack |
|---|---|---|
| Presentation | Browser / API client | Where the attacker crafts input |
| Application | Web/app server (PHP, Java, .NET, Node) | Builds the SQL string from user input |
| Data | SQL database (MySQL, MSSQL, PostgreSQL, Oracle) | Executes whatever query it is handed |
The entire vulnerability lives in one place: the application tier builds a query by gluing untrusted input directly into SQL text. The database has no way to tell the difference between the developer’s intended query and the attacker’s injected logic. To the database, it is all just SQL.
The root cause, in one line of bad code:
$query = "SELECT * FROM users WHERE username = '" . $_GET['username'] . "' AND password = '" . $_GET['password'] . "'";
That concatenation is the whole ballgame. Supply a normal username and the database obeys. Supply ' OR '1'='1 and the database still obeys, because it was never told which parts were data and which were commands.
198.18.0.0/15 for public/transit addresses and 10.0.0.0/16 for internal LAN ranges, per house convention.Step-by-Step: The Attack Chain
A SQLi attack moves through six stages. Each stage has a goal, an action, and a defensive control that stops it cold. Defenders should read the control column first.
Stage 1: Reconnaissance and Discovery
Goal: Find an input that reaches the database unsanitized.
Action: The attacker enumerates every input that could feed a query: URL parameters, form fields, HTTP headers, cookies, JSON body values, even the User-Agent string. They map the application’s inputs and flag anything that looks database-backed (search boxes, login forms, id= parameters, filters, sort orders).
https://shop.example.com/product?id=42 https://shop.example.com/search?q=widgets https://shop.example.com/login (POST username, password)
Any of these is a candidate. The id=42 parameter is the textbook starting point because integer IDs almost always land in a WHERE id = ... clause.
Defender verification: On the FortiGate, reconnaissance shows up as scanning behavior and tool signatures. Automated SQLi tooling (sqlmap being the usual suspect) sends hundreds of requests in a burst with a recognizable User-Agent and payload cadence.
Stage 2: Fingerprinting
Goal: Confirm the injection point and identify the backend database.
Action: The attacker sends a single character that breaks SQL syntax, usually a single quote, and watches the response.
https://shop.example.com/product?id=42'
If the app returns a database error, a 500, or a blank page where product 42 should be, the input is reaching the SQL parser. The next step is to confirm it is controllable rather than just broken:
id=42 AND 1=1 -> normal page (true condition) id=42 AND 1=2 -> different/empty page (false condition)
When 1=1 and 1=2 produce different responses, the attacker controls the query logic. Error messages often leak the database engine outright (You have an error in your SQL syntax is MySQL; Unclosed quotation mark is MSSQL; ORA-01756 is Oracle), which tells the attacker exactly which dialect and functions to use next.
Defender verification: Verbose database errors returned to the client are the tell. If your app is echoing raw SQL errors to users, you are handing attackers a fingerprinting oracle for free.
Stage 3: Exploitation (Logic Manipulation)
Goal: Alter the query to do something it was never meant to do, such as bypass authentication.
Action: The classic authentication bypass. Recall the vulnerable login query. The attacker submits this as the username:
' OR '1'='1' --
The query the database actually executes becomes:
SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = '...'
'1'='1' is always true, the -- comments out the password check, and the query returns the first user in the table, frequently the admin. The attacker is logged in without a valid credential.
Defender verification: Successful logic manipulation often leaves no error at all, which is exactly why prevention beats detection here. On a FortiGate with WAF in proxy mode, this is the payload class that trips the SQL Injection signature main-class.
Stage 4: Data Extraction
Goal: Pull data out of the database, the actual objective in most SQLi attacks.
Action: Two dominant techniques, chosen based on what the app reveals.
UNION-based (when results render on the page): the attacker appends a UNION SELECT to graft their own query onto the original result set.
id=42 UNION SELECT username, password, email FROM users
First they determine the column count (ORDER BY 1, ORDER BY 2, … until it errors) and which columns display, then they pivot to dumping credentials, tokens, or PII.
Blind SQLi (when nothing renders but behavior differs): the attacker extracts data one bit at a time through true/false responses (boolean-blind) or response delays (time-based).
id=42 AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a' id=42; IF (1=1) WAITFOR DELAY '0:0:5' --
Slow, but fully automatable. sqlmap does exactly this at machine speed.
Defender verification: Data extraction generates abnormal query volume and often large or unusual response sizes. Time-based blind injection produces a signature pattern of requests that each take precisely N seconds, visible in web server and WAF logs.
Stage 5: Escalation
Goal: Go beyond the database into the host or wider network.
Action: Depending on database privileges and configuration, the attacker may:
- Read local files (
LOAD_FILE()in MySQL,OPENROWSETin MSSQL) - Write a webshell to the web root (
INTO OUTFILE) - Execute OS commands (
xp_cmdshellon misconfigured MSSQL) - Harvest stored credentials and pivot to other systems
This is why least privilege on the database account matters so much. An app that connects as a DBA hands the attacker the keys to the host. An app that connects as a scoped, read-only user with no file privileges stops the chain right here, even after a successful injection.
Defender verification: Escalation attempts show up as outbound connections from the database or web server to attacker infrastructure, new files appearing in the web root, and database service accounts spawning shell processes. This is where EDR (FortiEDR) and egress filtering earn their keep.
Stage 6: Exfiltration and Persistence
Goal: Get the stolen data out and keep access.
Action: The attacker moves the dumped data to infrastructure they control, often over DNS or HTTP to blend with normal traffic, and may plant persistence: a new admin row in the users table, a webshell, or backdoored application logic.
Defender verification: Large or anomalous outbound transfers, DNS queries with encoded data in the subdomain, and connections to newly registered or low-reputation domains. Egress default-deny and outbound DNS inspection are what catch this stage.
Verification and Validation: Mapping Controls to Stages
Here is the defender’s cheat sheet. Each attack stage, and the control that breaks it.
| Stage | Attacker goal | Primary control (breaks the chain) |
|---|---|---|
| 1. Reconnaissance | Find inputs | Rate limiting, bot mitigation, remove verbose banners |
| 2. Fingerprinting | Confirm injection + DB type | Suppress database errors to the client; WAF detection |
| 3. Exploitation | Bypass logic | Parameterized queries (the real fix); WAF signatures |
| 4. Data extraction | Dump data | Parameterized queries; least-privilege DB account; WAF |
| 5. Escalation | Reach the host | Least-privilege DB account; disable xp_cmdshell/file privs |
| 6. Exfiltration | Steal data, persist | Egress default-deny; DNS inspection; file-integrity monitoring |
The single most important control is parameterized queries (prepared statements). They fix the root cause by separating code from data at the driver level, so ' OR '1'='1 is treated as a literal string to search for, not as SQL logic. Everything else, WAF included, is defense in depth around that fix.
// The fix. Input can never change the query structure.
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
FortiGate WAF: Defense in Depth at the Edge
When you cannot fix the application immediately (legacy code, third-party app, vendor stack), the FortiGate WAF profile buys you time and catches what slips through. WAF requires the policy to be in proxy mode, because the WAD daemon handles inspection.
FortiGate groups WAF signatures into main-classes. The two that matter for SQLi:
- main-class 30000000 = SQL Injection
- main-class 40000000 = SQL Injection (Extended)
A practical gotcha worth knowing: when you create a WAF profile from the GUI, logging is disabled by default on every main-class, and the GUI does not show it. You can block an attack and see nothing in your logs. Fix it in the CLI:
config waf profile
edit "default"
config signature
config main-class 30000000
set status enable
set action block
set log enable
set severity high
end
config main-class 40000000
set status enable
set action block
set log enable
end
end
next
end
Verify a block. Send a benign test injection against the protected virtual server:
https://shop.example.com/index.php?username=1' or '1' = '1
Then confirm the match on the FortiGate. Each signature logs its main-class, action, and severity:
diagnose wad debug enable level verbose diagnose wad debug enable category all diagnose debug enable
A matched SQL Injection signature from main-class 30000000 with action=1 (block) is your success criterion. If you see action=0, the signature is in monitor mode, not blocking.
Troubleshooting and Gotchas
1. “The WAF isn’t blocking, but the client got blocked.”
You will see a different Event ID on the FortiGate than on the client, sometimes with action passthrough, because multiple signatures match one request across different main-classes. The one that actually blocked (for example 30000040 under main-class 30000000) may have logging disabled while a monitored Extended signature (main-class 40000000) is the one showing up. Check which main-class is set to block versus monitor, and enable logging on all of them.
diagnose waf dump | grep 30000040
2. “WAF profile does nothing at all.”
The policy is almost certainly in flow mode. WAF inspection runs in the WAD proxy daemon, so the firewall policy using the WAF profile must be in proxy-based inspection mode. Flow mode silently ignores the WAF profile.
3. “We parameterized everything and sqlmap still finds injection.”
Two usual causes. First, dynamic identifiers (table or column names, ORDER BY clauses) cannot be parameterized and are often concatenated as a blind spot; whitelist them against a fixed allow-list instead. Second, a stored procedure or ORM raw() call downstream re-introduces string building. Parameterize the whole path, not just the obvious endpoints.
Recent posts
-
-
DNS is one of those technologies that quietly underpins... Full Story
-
BGP issues on FortiGate firewalls usually trace back to... Full Story
-
Every time your laptop talks to your router, a... Full Story
-
If you've spent any time configuring NAT on a... Full Story
-
If you have spent any time configuring firewall policies... Full Story
-
High availability on FortiGate is one of those features... Full Story
-
If you've configured SD-WAN on a FortiGate, you've almost... Full Story
-
FortiLink is the management protocol that turns a FortiSwitch... Full Story
-
FortiSwitches are pretty rock solid from Mean Time Between... Full Story
-
This is a quicky tip. Have you ever gone... Full Story
-
DNS is one of those quiet pieces of internet... Full Story
-
This article is an updated version of the previous... Full Story
-
You will add ns2 as a secondary (slave) BIND9... Full Story
-
In the process of deploying my lab, I needed... Full Story
-
RFC 8805, used to be known as Self-Correcting IP... Full Story
-
Years back, I wrote an article about certificate pinning. ... Full Story
-
FortiGates have the ability to send alerts to Microsoft... Full Story
-
In this post, I am going to walk through... Full Story
-
Troubleshooting VoIP on a FortiGate can feel like trying... Full Story
-
Prior to FortiOS 7.0, there were three commands to... Full Story
-
In this post, I am going to go over... Full Story
-
What we are going to do: We are going... Full Story
-
Choosing between FGCP (FortiGate Clustering Protocol) and FGSP (FortiGate... Full Story
-
Creating a VLAN on macOS (The "Pro" Move) A... Full Story
-
This blog post explores the logic behind how macOS... Full Story
-
Pretty Fly for a Wi-Fi Tell My Wi-Fi Love... Full Story
-
Part of my daily gig is creating BoMs (Bill-of-Materials)... Full Story
-
ICMP introduces several security risks, but careful filtering, rate... Full Story
-
The command diag debug application dhcps -1 enables full... Full Story
-
In the world of FortiOS, execute tac report is... Full Story
-
LLDP; What is it The Link Layer Discovery Protocol... Full Story
-
What it actually does When you run diagnose fdsm... Full Story
-
Monkey Bites are bite-sized, high-impact security insights designed for... Full Story
-
I have run macOS in macOS with Parallels but... Full Story
-
Don't be confused with my other FortiNAC posts where... Full Story
-
This is the third session in a multi-part article... Full Story
-
Today I was configuring key-based authentication on a FortiGate... Full Story
-
Netcat, often called the "Swiss Army knife" of networking,... Full Story
-
At its core, IEEE 802.1X is a network layer... Full Story
-
In case you did not see the previous FortiNAC... Full Story
-
This is our 5th session where we are going... Full Story
-
Now that we have Wireshark installed and somewhat configured,... Full Story
-
The Philosophy of Packet Analysis Troubleshooting isn't about looking... Full Story
-
Anatomy of a SQL Injection Attack Executive Summary... Full Story
-
1. Title and Executive Summary Objective: Configure a FortiGate... Full Story
-
TL;DR Every public CA is moving to the Let's... Full Story