SQL 注入
原理
SQL 注入的產生本質上是基於字串的拼接原理從而產生的,通常資料庫作為一箇中間產品,需要我們將他和我們的開發程式碼結合起來使用,比如 Java
和 PHP
等語言。以 Java
為例,通常我們需要用 JDBC
來連線 DBMS, 也就是類似於 MySQL
的資料庫管理系統,即,透過 Java
來執行 SQL 語句,這就意味著,我們通常需要將資料庫的查詢或者插入刪除等等指令預先儲存在程式碼中,在最簡單的情況下,這些指令就是由字串的格式被實現放在程式碼中。例如以下的Java
程式碼:
public boolean findAdmin(Admin admin){
String sql = "select count(*) from admin where usrname='"+admin.getUsername()+"'and password ='"+admin.getPassword()+"'"; // SQL 查詢語句
try {
ResultSet res = this.conn.createStatement().executQuery(sql);
if(res.next()){
int i = res.getInt(1); // 取第一列的值
if (i>0){
return true;
}
}
} catch (Exception e){
e.printStackTrace();
}
return false;
}
這段程式碼就是一個管理員登入時的後端程式碼的判斷語句操作,如果查詢的值大於0, 則表示使用者存在,查詢成功,密碼正確,最終判定登入成功,反之代表登入失敗。儘管整個程式碼的邏輯沒有任何問題,甚至看起來這個程式碼也不是僅僅一兩句話,但是如果你仔細看就會發現,代表 SQL 程式碼指令的語句是以 String
型別儲存的。就在程式碼段的第一句話中。
你在使用者名稱和密碼的輸入框中輸入的字元被儲存到 Admin
類中,透過 admin.getUsername()
等方法獲取,最終因為字串的拼接作用被拼接成一個 String
,如果你填寫的 user 為 user
, 密碼為:pwd
,在第一個語句執行後你的變數 sql
就會變成如下的模樣:
select count(*) from admin where username='user' and password='pwd'
那麼,如果我們換一個方向呢?我們在 user 中填寫的欄位為'or 1=1-- '
,密碼為空,也就是不做填寫,那麼這個變數 sql
就會長這樣:
select count(*) from admin where username='' or 1=1-- and password='pwd'
注意在sql
中,單行註釋的指令為--
;
也就是說,對 SQL 直譯器來說,它看到的你的語句執行條件就是一個 or
關係的判斷語句,且後面那一個恆定為真,那麼這個查詢語句一定能找到結果,最終在程式碼裡一定會顯示登入成功。我們登入成功,並不是因為這個我們的管理員的名字叫做 'or1=1--'
,而是輸入的這個欄位篡改了我們原先想要它執行的 SQL 語句,這種情況就稱作 SQL 注入。
那麼用維基百科的定義就是: 發生於應用程式與資料庫層的安全漏洞。簡而言之,是在輸入的字串之中注入SQL指令,在設計不良的程式當中忽略了字元檢查,那麼這些注入進去的惡意指令就會被資料庫資料伺服器誤認為是正常的SQL指令而執行,因此遭到破壞或是入侵 。
用途
SQL 注入可能存在於任何地方,在這種登入介面進行 SQL 注入的可能性事實上並不大,又或者說,注入後我們進入的某個賬戶裡的資訊並不足以讓我們控制整個網站,注入可能會存在於任何地方,那麼這個時候,也許我們可能可以嘗試檢查一下搜尋框,如果存在 SQL 注入,則可以利用這個輸入框獲取資訊。
利用 Union 來實現SQL 注入
SQL 的查詢語法 Union 經常會被應用於獲取資料庫的其他資訊,同時我們也會利用 Union 來猜測資料表各個列的資料型別,或是探測欄位數。
Union
Union 通常被用來合併兩個查詢結果集的結果。這就意味著,前後兩個查詢的結果的列數必須相同,同時,查詢的每個列的資料型別必須相容。Union的使用可以參考:https://www.runoob.com/sql/sql-union.html
Union 探測欄位數
試想一下一個存在 SQL 注入的搜尋框,他對應的正常的 SQL 查詢語句是:
select id, username, password, sex from user where id=1
雖然一個搜尋框不可能是這樣的查詢語句,好吧,就姑且當作是這個把。以這個為例,我們可以得到他的查詢語句的結果; 但是我們要知道,作為攻擊者的我們,是不知道 select
的查詢列數,就是說,我們不知道 select
後面跟著幾個欄位。Union 的作用就是試探出 select
後面跟著幾個列,可以嘗試輸入 union select null
,那麼完整的注入語句應該是:
select id, username, password, sex from user where id=1 union select null
如果列數不同,那麼會收到一個報錯提醒你的聯合查詢的結果列數並不相同,意味著我們知道這個表的列數不是1,那麼可以接著試探:
select id, username, password, sex from user where id=1 union select null, null, null,... ,null
直到不再收到報錯為止。
Union 查詢敏感資訊
例如在知道列數為4時,就可以嘗試構造語句:
select id, ..., sex from user where id=1 union select 'x', null, null, null from sysobjects where xtype ='U'
sysobjects
是一個自帶的系統表, xtype
是其中的一個列,'x'
在這裡和 xtype
沒有任何關聯,他只是一個字串,在這裡他所起到的作用就是在第一列的查詢結果的後面加一個 'x'
,這條語句實際上是在查詢 xtype
的型別為 U
(代表使用者表)的sysobjects
的資訊,並在第一列的結果後面新增一個 'x'
,表示這一列的資料型別是一個字串型。 可以以此調換 x 的位置,以此試探列的型別。
在知道某一列的型別同 'x' 相同時,就可以將字元 x 調換,例如替換成 version()
, 來顯示版本號等等。
DBMS 自帶函式的應用
DBMS 本身自帶很多的系統函式足夠可以便捷的檢視資訊,例如在 SQLServer
中可以使用語句:
select suser_name(); -- 返回使用者的登入標識名;
select user_name(); -- 基於指定的標識號返回資料庫使用者名稱等等。
...
同樣 MySQL
和 Oracal
同樣有著其對應的系統函式,需要時進行查詢即可。
延時注入
延時注入是盲注技術的一種,主要是基於頁面返回給瀏覽器的時間不同來判斷網頁是否存在 SQL 注入;這種技術多數是運用在網頁的容錯技術做的很好的時候,無論你輸入什麼額外的引數,頁面都將返回同樣的內容,這時候就要透過頁面返回的時間來判斷,比如 sleep
函式。例如當你要滲透的網站使用的 DBMS 是 MySQL 時,你可以靠如下的方式來獲取你的資料:
-- 以獲取使用者的登入名為例, 程式碼只顯示注入的關鍵語句
and if(length(user()=0), sleep(3), 1) -- 獲取使用者名稱的長度,如果判斷成功則3秒後返回
and if(hex(mid(user(), L, 1))=N,sleep(3),1) --迴圈比較,依次取出使用者名稱字裡的字元和ACSII碼比較,如果相等則三秒後返回注入
SQL 注入的工具
在使用語句對單個 URL 進行測試時是比較容易的,但是如果要測試的 URL 非常多的時候,就需要藉助工具進行使用了,常見的 SQL 注入工具有 SQLMap, Pangolin, Havij ,下面給出相應的可以參考學習的連結。
SQLMap: https://github.com/kvko/sqlmap-wiki-zhcn
Pangolin: https://developer.aliyun.com/article/393100
Havij:https://www.hackercoolmagazine.com/sql-injection-using-havij-step-by-step-guide/
防範
SQL 注入實際上完全是可以防範的,即使完全不知道所謂的 SQL隱碼攻擊,但是一個開發團隊只要定義好自己的程式設計模板,就可以很大程度上避免這類問題的產生。例如在碰到 SQL 語句時完全採用 "PreparedStatement"類,且必須用引數繫結,這樣就可以將安全問題轉移到程式碼規範的問題上。
SQL隱碼攻擊包含著很多的內容,本篇筆記只是選取了一部分內容進行介紹和說明,但是還有一大部分的內容需要大家自行去查閱和研究,也可以透過靶場進一步的提升自己的實戰能力。
Reference
https://zh.wikipedia.org/zh-cn/SQL隱碼攻擊
《Web 安全深度剖析》