什么是 SQL 注入?

作者:szSun · 2024-07-31 · 閱讀時間:12分鐘

SQL 注入是攻擊者最常用的攻擊媒介之一。事實上,就在 2019 年,SQL 注入攻擊(也稱為 SQLi 攻擊)占所有 Web 應用程序攻擊的近三分之二。這些數字是驚人的,放在實際情況中就更是如此。盡管自 2003 年以來,SQLi 一直是十大 CVE 漏洞之一,但它只是在過去幾年才開始加速發展和普及;就在 2019 年之前的兩年里,SQLi 僅占Web 應用程序攻擊的 44%。這種增長表明在 DevOps 流程中添加自動化安全測試是多么重要,這樣在這些錯誤投入生產之前就能發現它們。?

讓我們分析一下這個錯誤類別以及如何避免它。

什么是 SQL 注入?

SQL 注入是一種 Web 應用程序攻擊,攻擊者“注入”SQL 語句來操縱或訪問應用程序數據,無論這些數據是敏感數據還是公開數據。這些攻擊利用 Web 應用程序中要求用戶輸入的區域。如果應用程序中的用戶輸入未得到適當清理,攻擊者可以使用 SQL 注入來訪問相關的應用程序數據存儲區。 

SQL 注入示例

攻擊者通常使用 SQL 注入通過用戶輸入滲透 Web 應用程序。這包括用戶名、用戶 ID、名字和姓氏等表單填寫。如果您在接受這些輸入之前沒有對其進行清理,或者大量使用參數化 SQL 語句,攻擊者可以通過該輸入傳遞 SQL 語句,并在不知情的情況下在您的數據庫上運行。

例如,假設您正在從用戶那里獲取用戶 ID 的輸入。當您的應用程序獲取有關用戶的信息時,URL 可能如下所示:

合法的 SQL 查詢如下所示: SQL

SELECT * FROM users WHERE id = '42'

他們輸入他們的用戶 ID,您接受他們的輸入,使用它在您的數據庫中查找他們的信息,并為他們顯示他們的數據。 

但是,請考慮一下:他們沒有輸入用戶 ID,而是輸入了可以解釋為 SQL 查詢的內容。例如:SQL

'42' OR '1'='1'

如果你按原樣獲取他們的輸入而不進行清理,這將導致類似如下的 SQL 查詢:SQL

SELECT * FROM users WHERE id = '42' OR '1'='1';

由于 1=1 始終為真,因此這將返回所有用戶的每個數據字段。這是 SQL 注入的典型示例。

需要注意的是,這是數據庫針對此類查詢而設計的輸出。在這種情況下,攻擊者并不想破壞您開發的應用程序……只是利用現有資源來訪問他們不應該訪問的內容。在開發應用程序時,請嘗試考慮哪些內容可以訪問但不應該訪問,然后實施方法來阻止這種訪問。 

SQL 注入有哪些類型?

SQL 注入主要有三種類型:帶內 SQLi、推理 SQLi 和帶外 SQLi。

In-band SQLi

當攻擊者從用于收集注入輸出的同一位置發起 SQL 注入時,這被稱為帶內 SQLi。這是最常見的 SQLi 攻擊類型之一,通常分為基于錯誤的 SQLi 和基于聯合的 SQLi。

Error-based SQLi

基于錯誤的 SQLi 是一種輸出數據庫拋出的錯誤消息的 SQL 注入。這種類型的注入可用于向攻擊者提供有關數據庫的寶貴信息,例如其大小和元素。很多時候,攻擊者會使用基于錯誤的 SQLi 對數據庫進行偵察,然后最終執行 SQL 注入,以執行更復雜的任務,例如輸出數據。 

Error-based SQLi示例 

假設您已在生產環境中使用 Web 應用程序登錄時遺留了錯誤。為了收集有關數據庫的信息,攻擊者會使用他們知道會返回錯誤的內容來修改用戶輸入。 

這會產生以下 SQL 查詢:SQL

SELECT * FROM users WHERE id = '42''

由于末尾有多余的勾號,因此會引發錯誤。如果錯誤日志已打開,則錯誤會呈現給攻擊者:

Error: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near….

攻擊者現在知道 Web 應用程序容易受到基于錯誤的 SQLi 的攻擊,并且可以通過調用提供有關數據庫的更多信息的錯誤消息來利用這一點。 

為了防止這種情況,應在實時 Web 應用程序中禁用錯誤??日志記錄,或將其輸出到受限文件。 

Union-based SQLi

基于 Union 的 SQLi 使用 UNION 運算符將附加數據輸出到單個結果中,通常是輸出到 Web 應用程序中已經可見的表中。為了成功執行基于 Union 的 SQLi,攻擊者必須掌握有關數據庫的信息,例如表名、查詢中的列數和數據類型。這是因為,為了使 UNION 成功,被聯合的 SELECT 查詢必須:

  1. 具有相同數量的列。
  2. 具有兼容的數據類型。

收集這些偵察數據的一種方法是啟用錯誤日志記錄后通過基于錯誤的 SQLi。從錯誤日志收集的信息可能為攻擊者提供足夠的信息來了解表的大小和使用的數據類型。 

Union-based SQLi 示例

考慮這樣一種情況:攻擊者設法收集有關表的大小、正在使用的數據類型以及第二個表的名稱names 的信息。 

http://mycoolapp.com/allusers.php?id=' UNION SELECT * FROM names --

這將導致 SQL 查詢:SQL

SELECT * FROM users WHERE id ='' UNION SELECT * FROM names -- ' and password = 'abcd'

— 是 SQL 中的注釋,因此 — 之后的所有內容都會自動被注釋掉。初始 SELECT 語句返回空集,因為users中沒有 id 為 ” 的用戶,而第二個 SELECT 語句返回names中的所有信息。 

Blind SQLi

盲 SQLi 與帶內 SQLi 非常相似,但有一個區別:來自 Web 應用程序的響應不會輸出查詢結果或數據庫錯誤。基本上,Web 應用程序開發人員會抑制來自數據庫的錯誤消息,從而使攻擊者更難使用 SQL 注入。但是,這并不能解決 SQL 注入問題。攻擊者仍然可以使用盲 SQLi,它有兩種形式:基于內容的盲 SQLi 和基于時間的盲 SQLi。 

Content-based Blind SQLi

基于內容的盲 SQLi 使用查詢來獲取條件響應,而不是數據輸出。這些 SQL 查詢會詢問數據庫真或假的問題,以便攻擊者可以評估輸出并確定 Web 應用程序是否存在漏洞。這可能非常繁瑣,因此攻擊者有時會自動執行這些攻擊。 

Content-based Blind SQLi 示例

回到我們很酷的應用程序示例。考慮這樣一種情況,您已采取了一些預防措施,并且不顯示查詢結果或數據庫錯誤的輸出。為了收集有關您的數據庫的偵察,攻擊者注入查詢,希望系統返回 false。 

這會產生以下 SQL 查詢:SQL

SELECT * FROM users WHERE id = 42 and 4=1

當然,四不等于一。如果應用程序容易受到基于內容的盲 SQLi 攻擊,則此查詢不會返回任何內容,或者頁面會在某種程度上與正常功能不同。雖然這并不一定能證實應用程序存在漏洞,但實際上對攻擊者來說可能是一個好兆頭。為了確認應用程序存在漏洞,攻擊者將注入一個應返回 true 的查詢并觀察輸出。 

這會產生以下 SQL 查詢:SQL

SELECT * FROM users WHERE id = 42 and 4=4

這將返回 true 并輸出 ID 為 42 的用戶的數據。 

如果 Web 應用程序對數據庫返回 true 和 false 的響應不同,則攻擊者知道該應用程序容易受到 SQL 注入攻擊。通過繼續對數據庫使用 true/false 測試,攻擊者可以找到有關它的其他信息,甚至可能找到數據庫本身的內容。 

Time-based Blind SQLi

基于時間的盲 SQLi 查詢系統以執行時間密集型操作??捎糜诨跁r間的盲 SQLi 的典型時間密集型操作是 sleep() 操作。攻擊者可以向數據庫發送查詢以使其休眠一段時間,如果 Web 應用程序在該時間段內延遲其響應,則它很容易受到攻擊。 

Time-based Blind SQLi 示例

再次考慮這樣一種情況:您已采取一些預防措施來防止查詢結果輸出到數據庫或數據庫錯誤。為了收集有關數據庫的偵察信息,攻擊者可以嘗試使用 SLEEP() 函數來影響數據庫。 

如果數據庫響應緩慢,則意味著查詢已成功執行,攻擊者能夠對數據庫執行 SQL 查詢。從那里,攻擊者可以使用其他盲 SQL 注入技術來收集有關數據庫的其他信息。

Out-of-Band SQLi

與帶內 SQLi 相比,帶外 SQLi 要求攻擊者使用與收集 SQL 注入輸出不同的渠道來發起 SQL 注入。例如,如果攻擊者在 Web 應用程序上使用帶外 SQL 注入,他們會操縱數據庫服務器將數據傳送到他們控制的獨立服務器。這些注入會濫用 Microsoft SQL Server 的 xp_dirtree 命令等工具向攻擊者控制的服務器發出 DNS 請求。帶外 SQLi 比其他類型的 SQL 注入少見得多,因為它們非常依賴于數據庫服務器上啟用的功能。 

如何編寫防止 SQL 注入的安全代碼

凈化用戶輸入

防止 SQL 注入的最佳方法是清理數據庫輸入。任何類型的用戶輸入都應進行評估,類似于檢查新注冊者是否向您提供了合法的電子郵件地址而不是隨機字符串。在應用程序的服務器端(而不僅僅是客戶端)驗證用戶輸入。客戶端驗證可以使攻擊者更難更改用戶輸入,但有許多工具可讓攻擊者在必要時繞過客戶端驗證。

凈化輸入的示例

在將用戶輸入添加到數據庫之前對其進行清理的最簡單方法是使用正則表達式禁止內容。

例如,假設您希望用戶輸入并且僅允許字母、數字和空格。正則表達式可能如下所示:?

此正則表達式將允許字母數字字符和空格。通過將您收到的輸入與此進行比較,您可以識別并阻止接受任何其他字符。使用正則表達式只是如何清理輸入的一個示例,除此之外,采取其他預防措施也很重要,例如下面列出的預防措施。  

通過框架防止 SQL 注入

許多框架都提供了特定的最佳實踐來防止 SQLi,通常以允許開發人員使用準備好的語句或參數化查詢從用戶輸入構建其 SQL 語句的形式出現。如果您有興趣了解如何防止您使用的特定框架發生 SQL 注入,請務必查看我們的指南之一:

其他實用建議 

利用準備好的語句和存儲過程

說到防止 SQL 注入嘗試,一種標準方法是在代碼中使用準備好的語句。除此之外,您可能還想利用可以在數據庫內部創建的存儲過程。這兩個選項都可以防止用戶輕易將惡意 SQL 注入數據庫服務器將執行的 SQL 代碼中。不要運行“原始”查詢(在字符串中創建 SQL 查詢,然后直接在數據庫中運行它),而是確保每個 SQL 命令僅通過這些機制執行。

遵循最小特權原則

最小權限原則僅根據用戶所需的權限來限制其訪問權限。例如,Web 應用程序的用戶可能不需要訪問整個數據庫。相反,授予用戶訪問其所需表的權限,以減少濫用的潛在影響。 

將敏感數據與公共數據分開

存儲敏感數據必須以不同于存儲公共數據的方式處理。敏感數據應僅在應用程序需要時才進行存儲,并且應進行加密。對敏感數據采取額外的預防措施,而不必對公共數據采取這些預防措施,以確保用戶的隱私。

在構建管道中自動測試 SQL 注入

雖然此類博客和針對工程團隊的安全培訓很有幫助,但避免應用程序中出現 SQL 注入漏洞的最佳方法是在管道中自動進行測試。

一些關于SQL的API清單

文章來源:What is SQL Injection?