從WAF攻防角度重看sql注入
攻防都是在對抗中逐步提升的,所以如果想攻,且攻得明白,就必須對防有深刻的瞭解
sql注入的大體流程
- Fuzz測試找到注入點
- 對注入點進行過濾檢測,及WAF繞過
- 構建payload,對資料庫進行脫褲,甚至getshell
sql注入流程分析
- sql注入,最初沒有防護的時候,就只要能成功拼接sql語句,就可以進行sql注入
- 先對注入點進行判斷,是字元型還是整型。
對於整型一般是直接拼接sql,而對於字元型的一般是先要閉合掉單引號,或者雙引號,還有一種存在利用邏輯漏洞閉合sql語句的,等下一起分析了
所以這裡先通過加 " ' 進行判斷,得出是字元型注入。
引號閉合方式:1. 利用註釋符 mysql中一般用的是--+和#,不過如果是GET傳參,需要將#先進行URL編碼,不然會被當作url中的hash結構,--+中的+是隨意的,只要--後跟一個字元就可以,如果利用的是GET傳參,也需要將空格進行url編碼,不然會被瀏覽器自動剔除最末 尾空白符的操作給疑惑到,一般用-- -更方便,反正最後一個-也會被當作sql註釋後的內容
2. 利用邏輯,例如 id=' or {payload} or '1'='1 這樣的話最後拼接到資料庫中就是select * from user where id='$id'>select * from user where id='' or {payload} or '1'='1';從而完成引號閉合 3 其他:利用邏輯將'轉義 select * from user where id='$username' and pass='$password';>select * from user where id='asdasd' and pass=' or 1=1#';在這裡,由於第二個引號被轉義,所以第一個引號和第三個引號和中間字元組成一個字串,也就是id最終是{ asdasd' and pass= },最終得以繞過
防護1:1. 對於整形資料,php中可以用intval()將輸入流先進行轉換 2.對於字元型引數,可以用合理利用addslashes()對其中的單引號和雙引號進行轉義,合理是要求對任何進入程式的輸入流都進行先轉義,包括資料庫,防止二次注入 3.合理利用編碼,防止編碼注入
- 確認注入點後,確定欄位數
確定欄位數為3
- 開始構造payload
- id=0' union select 1,2,3--+
確認回顯位置為2,3引數的位置 - id=0' union select 1,database(),3--+
獲取資料庫名 - id=0' union select 1,database(),(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database())--+
利用group_concat一下獲取該資料庫中的所有的表,當然也可以利用limit一個個來,這裡最好限定下庫名,防止其他庫中也有同名的表 - id=0' union select 1,(select group_concat(username) from users),(select group_concat(password) from users)--+
最終成功獲取同表中的所有資料
- id=0' union select 1,2,3--+
如果想獲取其他表或者其他庫中的檔案,修改下引數即可,其他庫中的表 {庫名.表名},而使用者都放在mysql資料庫中,如果有許可權可以任意篡改,甚至還可以寫shell,操作多多。
防護2:過濾關鍵字,如select|information|table|column|from|union等,但黑名單總是存在被繞過的可能
猜想:可不可以利用mysql支援hex編碼,先將sql寫入到檔案,再執行?
sql注入防護-採用預編譯
從上面的流程可以看出,最基本的防護就是採用黑名單模式過濾關鍵字元或字串,但黑名單往往隨著sql語法的支援增加,黑名單的效果會下降。
預編譯其實是先通過了編譯,建立了AST語法樹,所以後續的操作只是像空格中填充東西而不影響句子的執行結構。
用了預編譯就一定安全了嘛?你真的會用預編譯嘛?
- 錯誤順序導致無效的預編譯
$query = "select balabala from table1 where 1=$_GET[‘id’]";
$row = $db->prepare($query);
$row->execute();
可以看到在這裡,先把變數拼接到sql語句,再進行預編譯是無效的,這樣對應的惡意語法樹還是解析了
- 模擬預編譯導致的寬位元組注入
PDO中有一個PDO::ATTR_EMULATE_PREPARES配置,用來控制是模擬預編譯,其實就是對sql的特殊字元進行了轉義
如果該資料的編碼是GBK的話,就有可能造成sql的寬位元組注入
其實這裡我也沒明白為什麼會搞一個模擬預編譯,為了效能?
- 支援多行程式碼執行的預編譯導致的sql注入(沒有過濾分號)
Set @x=0x31
Prepare a from “select balabala from table1 where 1=?”
Execute a using @x
由於sql中預設支援hex編碼,所以將目的碼進行hex編碼繞過檢測,然後就可以在預編譯時期造成sql執行。
對於動態程式碼,或支援動態程式碼的操作或者函式一定要小心。動態程式碼字串就可以利用奇奇怪怪的編碼造成千人千面的效果,在終點再轉換為爭取的程式碼執行,以此來繞過防護
sql注入繞WAF
其實上面說的那麼多,很多就是軟體WAF的工作原理。
所以這裡繞過WAF,也就相當於是對上面的總結。
-
WAF檢測關鍵字串和關鍵字元
對於關鍵字串,一般採用替代的方法,sql中有很多不為人知的用法,例如limit 1,1 如果對逗號進行了繞過還可以採用 limit 1 offset 1,如果對limit進行了限制,還可以用from to來,前提是對sql語法足夠熟悉。而對於空格,一般直接用Fuzz測試即可 -
過濾了關鍵的庫名
例如上面用的information庫,其實sys中的某些表或檢視也儲存了相關的表名和欄位名,也就可以用來做替換 -
利用WAF檢測點來繞過
例如某WAF只對GET請求的引數進行了sql檢測,而同時後端用的是$_REQUEST來獲取引數,這個時候就可以用POST進行繞過,應該$_REQUEST可以也可以獲取POST引數,但WAF不會對POST引數進行檢測 -
利用WAF的多重解碼,進行編碼注入
有些WAF為了防止編碼注入,會先檢測一部分,然後對輸入流進行解碼,再檢測另一部分,利用解碼,可以繞過第一部分的惡意字元。除非是對解碼前後都進行了相同的完全的過濾。但這樣處理編碼,無異於對效能是個巨大的浪費。 -
利用WAF只檢測部分資料
有些WAF為了效能,不可能對全部的資料進行檢測,例如上傳一個大的檔案,不可能對檔案進行全面檢測,所以往往只檢測前幾十個位元組,而我們可以把惡意資料放在檢測區間的後面,來繞過檢測。 -
HTTP協議相容性:HTTP Body多樣性
-
HPP引數汙染
HPP是HTTP Parameter Pollution的縮寫,意為HTTP引數汙染。
在ASPX中,有一個比較特殊的HPP特性,當GET/POST/COOKIE同時提交的引數id,服務端接收引數id的順序GET,POST,COOKIE,中間通過逗號連結,於是就有了這個idea。
這些都是應對於軟體WAF的,還有云WAF和硬體WAF,回頭再說