一、PDO介紹:
? ? ? ? 1.1、原理:
????????????????PDO支持使用預處理語句(Prepared Statements),這可以有效防止SQL注入攻擊。預處理語句將SQL語句與數據分開處理,使得用戶輸入的數據始終作為參數傳遞給數據庫,而不會直接拼接進SQL語句中,從而防止了SQL注入
? ? ? ? 1.2、預編譯的區分:
? ? ? ? ? ? ? ? 1.2.1、真實預編譯:
????????????????????????把ATTR_EMULATE_PREPARES設為false:
<?php
$username = $_POST['username'];$db = new PDO("mysql:host=localhost;dbname=test", "root", "root123");
$db -> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);$stmt = $db->prepare("SELECT password FROM test where username= :username");$stmt->bindParam(':username', $username);$stmt->execute();$result = $stmt->fetchAll(PDO::FETCH_ASSOC);var_dump($result);$db = null;?>
#首先定義模版,以'?’作為占位符:
prepare SELECT password FROM users where username= ?#然后插入用戶輸入的值,轉譯其中的特殊字符再將其放在''中執行:
execute SELECT password FROM users where username=
'admin\' union select database()#'#可以看到,我們的payload中的字符被轉譯了,而且還被放在引號中整個被當做字符串處理了,
不同于設置waf,這種情況下任何奇技淫巧都是徒勞的,我們無法逃逸出來
????????????????1.2.2、模擬預編譯:
? ? ? ? ? ? ? ? ? ? ? ? 沒有取消ATTR_EMULATE_PREPARES,默認為模擬預編譯:
<?php
$username = $_POST['username'];$db = new PDO("mysql:host=localhost;dbname=test", "root", "root123");$stmt = $db->prepare("SELECT password FROM test where username= :username");$stmt->bindParam(':username', $username);$stmt->execute();$result = $stmt->fetchAll(PDO::FETCH_ASSOC);var_dump($result);$db = null;?>
#沒有定義模版,后端只是對我們的輸入做了一個字符轉譯:
query SELECT password FROM users where username=
admin\' union select database()#'#可以看到這個模擬預編譯的防御就不如真實預編譯密不透風了
二、基于模擬預編譯的繞過:
? ? ? ? 2.1、寬字節注入:
#寬字節注入出現的本質就是因為數據庫的編碼與代碼的編碼不同,導致用戶可以通過
輸入精心構造的數據通過編碼轉換吞掉轉義字符。在對于我們輸入的payload的處理上,模擬預編譯只是使用了\來進行轉義,如果我們
能有什么辦法吞掉這個\,那是不是我們就可以執行惡意的sql語句了呢?#接下來就到寬字注入登場了:
#寬字節注入的原理:編碼方式:
--UTF-8:
可變長度編碼:每個字符占用 1 到 4 個字節。
ASCII 字符(如英文字母)占 1 字節。
中文字符(如漢字)占 3 字節。
特殊字符或罕見字符可能占 4 字節。
向下兼容 ASCII。--GBK:
可變長度編碼:每個字符占用 1 或 2 個字節。
ASCII 字符占 1 字節。
中文字符占 2 字節。#可以看到這些編碼方式英文字母都只占1個字節以less-32為例第三十二關使用preg_replace函數將 斜杠,單引號和雙引號過濾了,
如果輸入id=1'會變成id=1\',使得引號不起作用。
但是可以注意到數據庫使用了gbk編碼。
這里我們可以采用寬字節注入。當某字符的大小為一個字節時,
稱其字符為窄字節當某字符的大小為兩個字節時,稱其字符為寬字節。
所有英文默認占一個字節,漢字占兩個字節。#假如我們輸入:
?id=1'
#那被模擬預編譯處理后就是:
?id=1\'
hex: 315c27
#那當我們插入:
?id=1%df'
#被處理后的就是:
?id=1%df\'
hex: 31df5c27 (發生了報錯,說明我們的'起作用了)#原因如下:
1 %df \ '
31 df 5c 27在GBK編碼中,%df和\組成了一個中文字符,占2個字節,
也就是這個中文字符的十六進制編碼為:df5c,所以我們輸入的payload被處理后變成了 1�',
這樣我們的單引號就逃逸出來了,后面的問題也就迎刃而解了。#payload
?id=-1%df' union select 1,2,database()--+
????????2.2、堆疊注入:
PDO默認支持多行查詢,但是其只能顯示第一行查詢的結果
#沒有參數綁定的預編譯等于沒有預編譯,無論是真編譯還是模擬預編譯,
沒有參數綁定等于沒編譯,并且由于pdo默認支持堆疊注入,
我們可以通過堆疊注入先插入值然后查詢插入的值獲取輸出結果。--我們可以post一個id=1;
insert into test(id,username,password) values(520,database(),user())--然后傳遞id=520--我們就能得到database()和user()的輸出值了
三、基于真實預編譯的繞過:
? ? ? ? 3.1、思考:
? ? ? ? ? ? ? ? 按正常思路來說,我們是無法在注入語句上下功夫去繞過真實預編譯的,因為我們的payload無論怎樣寫都只會被當做字符串處理;那有沒有什么語句接收的參數不能被加引號呢?
實際上在mysql中order by、group by、limit、表名、列名、join這些都是不能在其后面接收的參數左右加引號的,因為一旦在其后面的參數上加了引號就會被當做字符串處理,查詢時就會造成語法錯誤,并且pdo的設計目的也不是為了防止SQL注入的,是用來在大批量查詢時減少語法樹構造的,因此官方自然也不會讓pdo在這些方法后加引號
并且在這些方法中只有order by和group by能夠加以利用,就算pdo不在表名、列名上加引號也沒有利用的價值
? ? ? ? 3.2、order by繞過:
#假如我們通過下面兩條語句查表:select username from users order by rand(true)admin
lili
xiaomingselect username from users order by rand(false)lili
xiaoming
admin#可以看到如果頁面有查詢后的信息回顯,布爾值的不同回顯的數據也不同,
那直接用布爾盲注的手段結合腳本就能遍歷出我們想要的信息#示例payload:rand(ascii(substr((select database()),1,1))>115)
#那如果頁面回顯數據是相同的或者頁面沒有回顯呢?#我們就可以使用時間盲注的手段了#示例payload:rand(if(ascii(substr(select user(),1,1)),sleep(3),0))
????????3.3、group by繞過:
? ? ? ? ? ? ? ? 與order by原理相似,如法炮制即可,如果網頁有group by的功能使用相同的方式也是有相同效果的。