SQL注入漏洞-SQL注入原理与实践
什么是SQL注入?:所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。具體來說,它是利用現有應用程序,將(惡意的)SQL命令注入到后臺數據庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的數據庫,而不是按照設計者意圖去執行SQL語句。
????????MySQL基礎語法:MySQL 教程_w3cschool???? ?
?瀏覽器打開sqli.com/sqli-1.php,其源碼如下:
代碼已經寫了注釋,如果沒有給這個頁面傳參數,則默認傳一個id參數,它的值為1。可以看到,在13行,獲取到用戶傳過來的參數后,直接進入了SQL語句,帶進了查詢語句,沒有任何其他操作,很明顯的存在SQL注入。在sql語句中,new被反引號包起來了,關于反引號的作用,請參考預備知識中的“mysql創建表時反引號的作用”。
先在URL后面加一個單引號,可以看到頁面報錯了,這個警告的意思是:mysql_fetch_array()這個函數第一個參數期望的參數類型是一個資源集,但是給的是一個布爾值,出現該警告的位置在代碼的第15行。
?查看代碼,發現它的第一個參數$result這個參數的值來自第14行,它是mysql_query執行后的返回值。
前面的語句在MySQL里執行出錯了,于是返回了false,這說明我們傳進去的參數1’破壞了SQL語句,我們可以稍微修改下代碼,在第14行后面輸出一下當前執行的sql語句。修改后的代碼如下:
我們可以直接復制該語句到MySQL中去執行。由于數據庫編碼是utf8的,直接在mysql客戶端中進行查詢,中文會顯示亂碼,所以可以到phpmyadmin中來執行這個sql語句。
訪問http://localhost/phpmyadmin/,如果需要輸入密碼,賬號輸入root,密碼為空,然后點擊執行登錄。
復制剛才輸出的語句:select * from `new` where id = 1',然后點擊執行。
這說明我們輸入的參數被mysql服務器當作SQL命令執行了!再來看下通過and 1=1和and 1=2來判斷網頁是否存在SQL注入的原理。首先傳個1 and 1=1,頁面顯示正常,而且最終執行的SQL語句是: select * from `new` where id = 1 and 1=1
再次嘗試1 and 1=2,頁面除了輸出最終執行的SQL頁面,沒有其他任何輸出。
其實這里SQL語句執行并沒有出錯,問題出在and 1=2上,再來一下這條語句,它從new這個表里面查詢數據,但是有限制條件,where指查詢滿足后面的數據,也就是說需要查找id = 1的數據,并且需要 1=2,但是1=2這是用于不可能滿足的。所以,這個語句被數據庫準確無誤地執行了,但是它并沒有找到符合條件的數據,所以沒有任何數據輸出。而前面加 1 and 1=1能正常返回數據,是因為1=1這個永遠為真,所以這個條件可以說是跟沒加一樣。
通過and 1=1 、 and 1=2來判斷頁面是否存在注入,是因為我們修改了原本的SQL語句邏輯,當添加and 1=1的時候,這個條件永遠成立,所以頁面一定返回正常(需要該頁面存在注入),但是當添加and 1=2的時候,這個條件永遠不成立,所以如果該頁面存在注入,必然不會返回任何數據。所以就會說,如果添加and 1=1頁面返回正常,添加and 1=2頁面返回不正常或者錯誤,就說明頁面存在SQL注入。當然這只能大概判斷,而不是說明該頁面一定存在注入,其他情況在后面的實驗里說。
那萬能密碼又是怎么回事呢? 網上找到的萬能密碼都是類似’ or 1=1這種,為什么輸入這個字符串就可以繞過登錄了呢? 我們也用or 1=1試試,看在這個頁面提交這個值是什么效果。
訪問http://sqli.com/sqli-1.php?id=1 or 1=1
發現居然輸出了多條記錄,這里其實輸出了new表中所有的數據,所有的建表語句都在sql.sql文件中,可以打開看下sqli數據庫中有哪些表,有什么內容。
它這里只要滿足id =1 或者1=1任意一個條件,而1=1永遠為真,所以返回了所有的數據,其效果與select * from `new`一樣。
但是如果僅僅只是讀取new這個表里面的數據,對我們來說,其實意義不大,因為即使不存在注入,我們也可以增加ID的值來查看其中所有的信息。比如訪問http://sqli.com/sqli-1.php?id=2 可以查看id=2的內容。
我們希望的是能夠通過注入,獲取一些網站管理員不希望我們知道的數據,比如管理員的賬號密碼。
在本頁面的源碼中,注入點在where后面,也就說,從哪個表中查詢什么數據是寫死的,那么怎么才能通過注入獲取其他表中的內容呢? 比如獲取user表中的數據。
通過union操作符就可以達到我們的目的,union可以合并兩條或者多條select語句的查詢結果,它的語法如下:
Select column-1,column-2,…,column-N from table-1 union select column-1,column-2,…,column-N from table-2
它的返回值是兩個select語句查詢結果組成的表。我們可以通過在第一個查詢后面注入一個union運算符,并添加另外一個任意查詢,就可以讀取到數據庫中用戶可以訪問的任何表。但是,使用union有一些限制:
1.兩個查詢返回的列數必須型相同。
2.兩個select語句對應列所返回的數據類型必須是相同或者是兼容的。
當前的兩個select 他們查詢的列的個數必須一致,如果第一個select查詢了5列,則第二個select也必須查詢5列。
如:select title, content from new union select username, password from user;
像上面的查詢,列數相同,會返回這2個查詢的結果集。
來到phpmyadmin中驗證一下,如圖:
重點關注怎么知道查詢中列的數量,代碼中,寫的是select * from `new`,直接在代碼中看不出來new這個表中有多少列,而需要去查看建表語句,而在實際應用中,除非是開源的CMS,否則你不可能準確知道它查詢了多少列,這就需要我們想個辦法來獲取當前查詢的列數。
一個簡單的辦法是通過order by 子句來確定。Order by 是根據指定的列名進行排序。
Order by 子句可以接受一個列名作為參數,也可以接受一個簡單的、能表示特定列的數字,所以可以通過增大order by 子句中代表列的數字來識別查詢中的列數。
來到phpmyadmin測試。執行show create table new來查看建表語句
?可以看到這個表有3列,然后來測試用order by子句來測試列數量,從1開始,每次查詢加1,加到報錯為止,如:
1的時候正常,繼續增加該值。到4的時候,發現執行出錯。
因為這里一共只有3列,但是你要根據第4列來進行排序,當然就會報錯了。
?再測試一個語句,把* 改成title, content。還是從1開始測試。結果如下:
select title, content from new order by 1 ,結果正常。
select title, content from new order by 3 ,報錯。因為我們這里只查詢了2列。
現在就來注入頁面進行測試。
訪問http://sqli.com/sqli-1.php?id=1 order by 1,頁面正常。
依次增加order by 子句后面的值,直到4的時候,頁面報錯。
所以我們可以得出結論,在這個查詢中,一共查詢了3列,因為4的時候報錯了。
然后就可以通過union 來進行查詢user表中的數據啦。
根據union的語法,構造注入語句,通過前面的測試已經知道這個查詢一共查詢了3列,在符合union操作符的前提下,可以構造如下語句:
http://sqli.com/sqli-1.php?id=1 union select null, username, password from user
?
這樣就獲取了user表中所有的用戶名和密碼。
如果你寫的網站存在注入,別人很容易就能通過SQL注入拿到管理員賬號密碼,那么網站就很容易被人篡改,并且導致信息泄露。
這樣就獲取了user表中所有的用戶名和密碼。
如果你寫的網站存在注入,別人很容易就能通過SQL注入拿到管理員賬號密碼,那么網站就很容易被人篡改,并且導致信息泄露。
這就需要了解mysql數據庫的information_schema 這個數據庫了。
構造出獲取所有數據庫的語句,為:
http://sqli.com/sqli-1.php?id=1 union select 1,schema_name,3 from information_schema.schemata
可以看到返回了所有的數據庫名:
?
其中schema_name 和 數字3的位置可以互換,沒有什么影響,因為這里只需要有一個位置輸出了就行,其中的數字1和3 可以被其他數字或者字符或者NULL代替,用字符代替的時候,記得用雙引號或者單引號引上。雖然1和3可以被代替,但是不能缺少,因為必須要保持3列union才能正確執行。
這里后面的from 為什么是information_schema.schemata而不是schemata呢?
因為在當前執行SQL語句的環境中,它的默認是庫是sqli,如果我們要跨庫查詢,則需要在表名前加上庫名,如果不加上庫名,則表示在sqli這個數據庫的schemata表中查詢,由于sqli這個數據庫不存在schemata表,所以會報錯,如下圖:
如果我們輸出mysql 的報錯信息,就可以看到很明顯的提示。
修改sqli-1.php源文件,在執行sql語句的下面加入如下代碼:
??? if(!$result){?????
? ?? ?? die(mysql_error());
??? }
再次訪問上述鏈接:
提示表sqli.schemata 不存在。
通過前面的注入語句,我們已經獲取了所有的數據庫庫名
在實驗步驟二中,我們已經獲取了sqli數據庫user表中的數據,這次換一個,改成mysql。mysql這個數據庫保存了mysql的賬號密碼信息,在注入的時候,可以讀取該表中的數據來讀取mysql賬號密碼。
現在就通過注入來獲取mysql的賬號和密碼。
首先查看mysql這個數據庫中有哪些表。構造如下語句:
1 union select 1, table_name,3 from information_schema.tables where table_schema='mysql'
?
這個表中保存了mysql服務器的賬號和密碼的hash值。然后構造語句獲取該表中的所有列名。構造語句為:
1 union select 1, column_name, 3 from information_schema.columns where table_name ='user' and table_schema='mysql'
訪問后返回該表中所有的列名。
存在User字段和Password字段,User字段保存了MySQL服務器的用戶名,Password字段保存的是用戶的Hash值。
由于在這里有2個位置可以輸出,所以我們可以一次查詢用戶的賬號和密碼。
構造語句如下:
1 union select 1, User, Password from mysql.user
?發現頁面只有輸出root。進入phpmyadmin確認一下。
?
?
總結
以上是生活随笔為你收集整理的SQL注入漏洞-SQL注入原理与实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: re学习笔记(37)BUUCTF-re-
- 下一篇: mysql日期函数之DATEDIFF()