前言
在 OWASP Top 10 - 2017 之中,Injection 還是在排在第一位,可見 Injection 所帶來的影響很大。
本文將跟大家介紹 Explicit / Blind SQL Injection 以及一些 SQL 常見的手法。
首先,先來了解一下 SQL Injection
當 URL 為 http://www.mysite.com/product?id=1 時,
會對應到 SQL 為 SELECT * FROM Product WHERE Id = 1,
當中不信任的資料為 id 的參數值 1。
所以當我們把 URL 改成 http://www.mysite.com/product?id=1 or 1=1
程式如果沒驗證的話,SQL 就會變成 SELECT * FROM Product WHERE Id = 1 or 1=1,
SQL 常見手法
產生錯誤
要讓 SQL 發生錯誤,最簡單的就是將字串轉成 int ,自然就會發生錯誤,例如,
1 | Select Convert(int, 'a'); |
依序取得 一筆資料
要取得一筆資料可以使用 Top 1 ,而要依序則需要使用 Order ,如下,
1 | Select Top 1 UserID, Email |
那如果要取得第2筆資料呢? 改成 Top 2 嗎?
1 | Select Top 2 UserID, Email |
但是 第一筆還是 admin@gss.com.tw 而不是 cindy@gss.com.tw ,
那要如何第一筆才會變成 cindy@gss.com.tw 呢?
…
就是再把資料反過來排一次。
把上面的 SQL 當成是 Sub-Query 再 Order By Desc 取 Top 1 就可以。
1 | Select Top1 UserID, Email |
SQL Injection 測試
有了上面的 SQL 小技巧,就可以加入 SQL Injection 的測試之中,
而SQL Injection attack 包含 2 種類型
1.Explicit
1.1.Error-base: 透過錯誤訊息來顯示資料,例如 輸入 ‘ 後,系統直接顯示 SQL 的原生錯誤訊息
1.2.Union-base: 增加額外的資料輸出,例如 登入功能輸入 ‘ or ‘9’ = ‘9 就可以登入,或是有些查詢功能中,使用 union 加入其他的資料讓它顯示出來
1.1.Error-base
在測試有沒有 SQL Injection ,通常會輸入一個單引號,來看看它會不會出現錯誤,如下,
1.2.Union-base
從上面所發生出來的錯誤訊息可以得知,它的登入 SQL 為 SELECT * FROM Users Where Email = ‘{email}’ and Password = ‘{password}’ Order By Email Desc
會有 SQL Injection 的問題,所以我們可以調整的 SQL 如下,
1 | SELECT * FROM Users Where Email = '{email}' |
所以要如何讓不知密碼也可以通過 Query 呢?
1 | SELECT * FROM Users Where Email = '{email}' |
所以 Password 欄位可以使用 ‘ or ‘’=’ 或是 ‘ or ‘1’=’1 等.
當然,也可以串一些 insert , update & delete 的 SQL ,例如,
1 | SELECT * FROM Users Where Email = '{email}' |
1.1.Error-base (Advance)
因為程式會將原生的錯誤訊息顯示出來,所以可以利用這種方式來取出我們想要的資料,
例如要取得 Email ,則可以依上面的 SQL ,再將它轉成 int 就可以順著錯誤訊息來讓我們知道了,如下,
1 | SELECT * FROM Users |
2.Implicit(Blind)
2.1.Time-base: 依輸入資料而花費等待時間才輸出結果, 例如 輸入 WaitFor Delay ‘00:00:10’ 則會多花費 10 秒
2.2.Boolean-base: 依輸入的資料給出不同的輸出結果,例如 輸入 A 會發生錯誤,輸入 B 則正常
前面的範例是很明確讓我們知道它有 SQL Injection 的問題,有些狀況下,雖然它有 SQL Injection 的問題,但它只會跳到錯誤處理畫面去,會讓我們不知是不是 SQL Injection 的問題,這種就叫作 Blind SQL Injection 。
例如系統的 URL 為 Product/Detail?ProductID=13&orderBy=UserID ,
會正常的顯示資料,但如果將 UserID 改成 UserIX ,則會發生錯誤。
所以可以假設它的 SQL 為
1 | SELECT * FROM Votes |
當 UserID 改成 UserIX 就會發生錯誤。因為 SQL 就會變成如下,
1 | SELECT * FROM Votes |
因為排序欄位錯誤,只是我們無法看到明確的錯誤訊息,所以只能用猜的。
為了更清楚是否有 SQL Injection 的問題,可以再把 URL 改成 Product/Detail?ProductID=13&orderBy=UserID,2 ,
這時程式可以正常運作,但如果將它改成 roduct/Detail?ProductID=13&orderBy=UserID,a ,則會發生錯誤。
這表示 SQL 分別為,
1 | SELECT * FROM Votes |
1 | SELECT * FROM Votes |
2.1.Blind SQL Injection - Time-base
要測試它是否有 SQL Injection 的問題,就可以在 URL 後再加入 WaitFor Delay ,如下,
Product/Detail?ProductID=13&orderBy=UserID;WaitFor Delay ‘00:00:05’;–
系統的執行時間也跟著增加 5 秒,那它就有 SQL Injection 的問題。
知道它有 SQL Injection 的問題,所以後面就可以再組一些 SQL 去讓它執行。
2.2.Blind SQL Injection - Boolean-base
即然知道它是 Blind SQL Injection ,所以可以透過程式會不會出錯來達到用猜的方式來猜出資料,
例如假設我們要查詢資料庫中的資料表名稱,則可以透過 Case When (要猜的SQL) Then 1 Else (出錯的SQL) End 的方式來達到。
1 | SELECT * FROM Votes |
而URL則為Product/Detail?ProductID=13&orderBy=UserID, case when (SELECT Top 1 substring(A2.name, 1, 1) From ( SELECT Top 1 A1.name FROM sys.tables A1 Order By A1.name ) A2 Order By A2.name Desc) = ‘p’ then 1 else convert(int, ‘x’) end;
如果第一個資料表名字的第一個字母為 p 就不會錯誤,不對的話,就會發生錯誤。
它的比對邏輯蠻簡單的,所以通常可以透過程式來測試,如果猜對了,程式就不會出錯,猜錯了,程式就會發生錯誤,最後就可以將所需要的資料全都猜出來。
結論
以上透過簡單的範例說明,介紹了如何透過 Error 來顯示錯誤的資訊,及 Blind SQL Injection 透過用猜的方式來取得我們要的資訊。
如果再加以組合之後,更可以猜出資料庫的 Database Name, Table Name, Column Name ,然後透過 Error 的方式來將 Table 的資料顯示出來。
系統的需求很多種,所以在存取資料庫時,請注意 SQL Script 請勿使用串接字串及串接參數值的方式。