【philippica】sql injection--忘掉我就吃掉你

由於ctf的原因最近重拾了sql injection,最早學的時候就想吐槽這貨易懂難精,1=1 1=2 的教程滿天飛,可中間過渡的文章寥寥,結果,艱深的文章又看不懂,寫這個東西的受眾應該是入門級的,不過需要有一點資料庫基礎,順便你如果不想惹麻煩請不要在任何網站擁有者未授權的情況下對網站進行任何測試!!!你如果不想惹麻煩請不要在任何網站擁有者未授權的情況下對網站進行任何測試!!!你如果不想惹麻煩請不要在任何網站擁有者未授權的情況下對網站進行任何測試!!!重要的話說三遍!!!

sql injection能幹嘛?簡單來說能拿到資料庫里的敏感數據,比如賬戶密碼一般都是存在資料庫里的,如果能找到admin的密碼就能進後台,一般更大的許可權出現漏洞的概率也就更大,更甚至程序員的變數命名風格之類的,你就可以憑藉這些信息為進一步入侵做準備

sql injection和xss,緩衝區溢出這些經典漏洞本質上都是相同的,都是對於用戶輸入沒有過濾完整,導致本是數據的部分被當作代碼執行

於是人們發明了很多辦法避免,比如黑名單,白名單,其中最徹底的辦法應該就是prepare statement,但在目前,sql注入仍是web安全的重中之重

sql語句是大小寫不敏感的,但為了區別語句和數據,我們通常把SELECT,UNION,FROM之類的關鍵字大寫而把表名等數據小寫

即使只有and or之類的邏輯運算也能搞出大新聞,比如世紀初大肆其道的萬能密碼 or 1 = 1原理就是1=1永遠是true,任何邏輯不嚴密,過濾不完全的網站都有可能中招,即使現在依然能在wooyun上見到萬能密碼成功的例子

如果你學過php也許見過這些代碼:

$cid = isset($_GET[cid]) ? $_GET[cid] : NULL;

$sql = "SELECT article FROM foo_table WHERE id = $cid";

$result = $db->query($sql);

$row = $db->fetch_array($result);

echo $row[0];

比如id是傳進來的第幾篇文章,那麼根據這個get參數,返回不同的文章

基本上php剛學幾天這麼寫是沒問題的,但如果將這個代碼發布到網上,估計這個程序員就要滾蛋回家了,

/*

好一點的寫法是:

$st = $db -> prepare("SELECT article FROM foo_table WHERE id = ?");

利用問號佔位,這段sql語句預先編譯了,然後利用$db -> excute();把這個佔位符號補上,這是sqllite支持的寫法

*/

我們來看最簡單的$sql = "SELECT article FROM foo_table WHERE id = $cid";這種情況會出現什麼問題

$cid是從超全局變數$_GET中得到的,比如127.0.0.1/index.php?

我們把後面1換成 id=1+and+1=1 加號會被url轉譯成空格,那麼$cid就是一個字元串"1 and 1=1"代進去,構成sql語句SELECT article FROM foo_table WHERE id = 1 and 1=1

後面1=1永遠為真所以這句sql會正常執行;?id=1+and+1=2 由於1=2為false,所以返回empty set,這就是著名的1=1,1=2測試

目前我們是開了上帝視角來進行攻擊的,實際攻擊中我們不可能知道具體的php代碼以及構造的sql語句,例如「SELECT article FROM foo_table WHERE id = $cid」,我們需要通過網站返回各種行為進行黑盒測試(說得不那麼裝逼一點就是猜咯),例如SELECT後面跟了多少查詢的列,這裡用到了order by statement

order by是用來排列的,例如表中有個id列,那麼SELECT * FROM foo_table ORDER BY id;便是按照id排序 order by後面同樣可以跟數字,表示對<b>select語句查詢結果<b>的第幾列進行排序,例如SELECT username, id FROM foo_table ORDER BY 1;便是對username排序,ORDER BY 2便是對id進行排序

如果select的結果只有n列,你卻order by n + 1,這時候mysql就會報錯,體現在網頁上就是原本該輸出的東西消失或者異常

記得高中時還沒接觸資料庫的時候被某些文章誤導過,這裡order by語句測試的用途並非是測試這個table有多少列,而是select返回的查詢結果有多少列

知道select返回的結果有多少列有什麼好處?這樣就可以用union了呀

sql語句里可以進行集合操作,UNION並集,INTERSECT交集和EXCEPT差集,後兩個對於注入意義不大,因為沒有增加我們需要的信息,但是union確實實實在在增加我們信息的。使用union時各查詢結果的列數必須相同,對應項的數據類型也必須相同,資料庫書上如是說道

由於列數必須相同,因此order by得到列數才如此重要,然後我們就可以干一些猥瑣的事了,比如我們知道了列數是3,我們把$cid變成: "-1 UNION SELECT table_name,2,3 FROM information_schema.tables" 2,3就是為了將union 後查詢的列數和前面的列數相同,由於不存在id是-1的recorder,因此前面的select語句得到empty set,並上union後面select的結果,返回的就是所有的表名!!!!如果能利用好便能echo到你的網頁上

網上有提到字元型,數值型注入的,其實就是多了一對引號嘛,那我在前面和後面分別加引號閉合它行不行,一定要造一個新名詞,只要我開心,where id = 1 and 1=1都行,只要引號是匹配的就行,

/*

tips:

information_schema是mysql默認存在的資料庫,其中有一張表tables存放著所有的表,其中table_name欄位記錄著表的名字,因此才能確定表名

有些資料庫並沒有類似的默認資料庫,因此為了得到表名需要暴力猜解,許多人都有備好常用表名的字典

由於暴力猜解沒有太大手工注入的價值,通常python寫腳本或者直接用現成的sqlmap,所以這裡不表

*/

資料庫的名字被存在information_schema.schemata這張表裡,這裡我們用嵌套查詢獲得資料庫的名字

?id=-1 UNION ALL SELECT (SELECT schema_name FROM information_schema.schemata LIMIT 0, 1)-- -

這裡好像有點複雜,其實還是換湯不換藥,我們看內層查詢:SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1;唯一見的少的應該就是limit statement了,它的意思是取出第一個欄位,limit後面兩個參數分別代表取出第幾個元素和取出的數量,和vb 中mid函數的後兩個參數很像對吧,所以這句查詢的結果就是取出information_schema.schemata這個表中schema_name列中第一個元素,要查第二個或者第三個元素只要LIMIT 1, 1 或者LIMIT 2, 1

後面一個-- -是sql風格的注釋,萬一原本的sql語句不是"SELECT article FROM foo_table WHERE id = $cid";這樣,而是"SELECT article FROM foo_table WHERE id = $cid and 1 = 1"; 嗯就會出錯,所以為了避免這種錯誤,把後面的sql語句注釋掉就可以了,順便說一下,注釋除了-- -還有# ` ;0之類的,例如#,如果要用這個注釋那必須了解url編碼規則,#本身會被瀏覽器解釋成anchor,也就是在網頁中定位用的而不會被發送到伺服器,正確的方式是將它url編碼

這樣我們就能得到所有的表名和所有的資料庫名了!!但是這樣查太麻煩了,如何找指定資料庫的某一個表呢?

?id = -7423 UNION ALL SELECT (SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN foo_databasename LIMIT 0,1)-- -

union在手天下我有!

嗯,上面這些是基礎過關的本科生,學過資料庫都會的東西

能不能更給力一點啊老濕!

很多時候並沒有把sql查詢的結果返回給你,例如用戶名和密碼,查詢後如果正確將跳轉正確頁面,錯誤則返回錯誤信息,又或者網站的某種樣式,根據資料庫查詢結果不同,表型出網站不同的樣式

例如:

$cid = isset($_GET[cid]) ? $_GET[cid] : NULL;

$sql = "SELECT article FROM foo_table WHERE id = $cid";

$result = $db->query($sql);

$row = $db->fetch_array($result);

if(isset($row[0]))echo "1"; else echo "0";//這裡只是為了簡化虛擬的場景

這時候由於並不能返回有用的結果,union函數貌似失去了它的意義,但是echo 1或者0無論如何給了我們1bit的信息啊!!利用這1bit的信息我還是可以得到和上面相同的注入方法

我們構造get參數為?id = 1 and MID((SELECT schema_name FROM information_schema.schemata LIMIT 0, 1), 0, 1) > 1

和前面一樣,當and後面的邏輯表達式為false時返回empty set,當為true時返回正常查詢的東西,這樣我們就可以知道後面的邏輯表達式的真假

MYSQL中mid函數和vb中用法一模一樣,第一個參數是字元串,第二個參數是開始的位置,第三個參數是查詢字元串的長度,返回的是取出的字元串,所以這個mid語句就是select語句取出字元串的第一個字元,mid和後面的大於1構成一個bool表達式

簡單來說,就是一個猜數遊戲,我現在腦子一個數字在0到255之間,你每次可以猜一個數字,我告訴你大了還是小了,利用二分法就可以迅速確定這個數字

(ascii的信息熵是8bit,每次bool表達式能得到的信息熵為1bit,因此利用二分法,我們一定能在8次以內確定這個字元是多少)

不就是boolean-base的盲注嘛

能不能更給力一點啊老濕!

即使是Boolean base的盲注也是有信息返回的,但是如果我們僅僅是查詢了資料庫,但沒有任何信息返回呢,你還能注入嘛,bite me!!

$cid = isset($_GET[cid]) ? $_GET[cid] : NULL;

$sql = "SELECT article FROM foo_table WHERE id = $cid";

$result = $db->query($sql);

// Do something else

sql注入重要是得到信息,但可以發現這個僅僅是查詢了一下資料庫,什麼信息都沒給我們,看似不可能注入了

(記得在bzoj刷題的時候,有一次RE了,但是程序很長,一時間想不到RE的位置,於是就在程序里加上for(;;);通過這一句放的位置不斷調整,系統不斷返回TLE或者RE,終於定位了RE,的位置)

嗯,方法和這個有點類似

先了解一些mysql的函數,mysql有個內置函數sleep(x),參數x代表延遲的秒數(不是毫秒)

例如你可以在mysql里輸入select sleep(5);等5秒以後才會有反應

所以構造參數?id = 1 and sleep(5);網站會等5秒後再載入出來,php會一直等到mysql返回值才能繼續

if函數:有三個參數當第一個參數為真時返回第二個表達式,否則返回第三個,很像c里的問號表達式對吧

那麼可以這麼構造?id=1 and if(MID((SELECT schema_name FROM information_schema.schemata LIMIT 0, 1), 0, 1) > 1,sleep(5),1)

同樣的,這個布爾表達式為真的時候,你的頁面會在5秒之後才能載入好,否則直接載入出來,接下來的技巧就是上面盲注中講過的了,此處不再贅述

當然實施這種方法的前提是網速好,如果你打開網頁本來就需要5秒乃至以上,這種判斷的時間顯然要延長

這個就是time-Base的盲注嘛,手注十分麻煩而且廢時間,通常還是要跑腳本嘛

能不能更給力一點啊老濕!

正常網站當然不會傻到直接把你輸進來的字元串直接放到資料庫里跑嘛,當然要做一些處理咯,有些站專門使用了waf等,過濾了字元串是否就安全了呢?

安全人員甲:很簡單嘛,把要過濾的危險字元串建一個AC自動機,當匹配到用戶輸入的字元串有危險字元串的時候就把危險字元串刪了不就好了

聽起來很豐滿現實很骨感,例如ac自動機里的匹配串是{"SELECT"},但是別忘了sql語句是大小寫不敏感的,我可以這樣嘛SeLeCt 充滿喜感的無視了你的過濾

安全人員甲:這個簡單,我把輸入的字元全部轉換為大寫然後在跑ac自動機就好

其實尷尬的事情是,單純的字元串過濾很難攔住sql injection,如果關注安全任何細小的地方都不能掉以輕心.
推薦閱讀:

Flash漏洞的死灰復燃4
GitHub 萬星推薦:黑客成長技術清單
網站被「黑」了,域名商這鍋你別想甩!!!
陳學理:他做了15年安全,為什麼要去做運動頭盔?
如何Unlock一輛汽車?一個可以無線解鎖百萬輛汽車的研究成果

TAG:网络安全 | SQL注入 |