1. 程式人生 > 其它 >django orm關聯查詢_防止SQL注入:來自Django作者的觀點

django orm關聯查詢_防止SQL注入:來自Django作者的觀點

本文由Django作者之一的Jacob Kaplan-Moss以及Grayson Hardaway共同創作。

什麼是SQL注入?

SQL注入(SQLi)是最危險的WEB漏洞之一。所幸這種情況漸少發生,主要歸功於越來越多的開發者開始使用資料庫抽象層(如Django的ORM)。然而,一旦經受此類攻擊,後果將不堪設想。

當代碼裡錯誤的構造了包含使用者輸入的SQL查詢時,就會發生SQLi。例如,假設在不瞭解SQLi的情況下,實現一個查詢方法:


發現問題了嗎?注意查詢請求來自瀏覽器:request.GET['q']。思考一下如果請求包含單引號,在構建SQL時會發生什麼呢?

假如攻擊者查詢' OR 'a'='a,這時SQL語句將變成:


如此就糟了,此查詢會返回資料庫表中的全部內容,導致資料洩露甚至資料庫服務癱瘓。

還有更糟的,假如攻擊者查詢';DELETE FROM some_table。此時SQL語句如下:


哇嗚,刺激!

防止SQLi的基本概念

我們將很快介紹Django的詳細內容,不過在此之前,真正理解防止SQL注入的基本規則很重要:

永遠不要信任使用者提交的任何資料;

直接構造SQL查詢時,請始終使用“引數化查詢”。

來自使用者的任意內容都有可能被惡意構建。即使看似安全的內容,像瀏覽器headers(例如Django中使用者代理request.META['HTTP_USER_AGENT']),也很容易直接在瀏覽器或Burp,Charles這類工具中篡改。

實際上,在Django中,HttpRequest object中的內容作為傳遞給檢視函式的第一個引數,都需要注意。儘管存在一些例外情況,但最好將request中的任何內容都視為不可信。

然而,對於那些不經過request傳遞的資料,並不意味著值得信任。例如下面這個例子:


試想,如果image.caption是由使用者輸入的......那可能就大事不妙了。因此,需要關注第二條原則:始終使用引數化查詢。

引數化查詢是指在SQL查詢時,將所需數值以動態引數形式傳入的一種機制。這些引數要麼由資料庫直接解釋,要麼經過安全轉義後再新增到查詢中。幾乎所有的資料庫客戶端都支援引數化查詢,如果您的不支援,還請換一個吧。

用引數化查詢實現上面的查詢方法如下所示:


注意SQL語句中的?,以及execute中的第二個引數,它是一個引數列表,其中的元素會注入查詢語句以代替問號。

根據PEP-249,Python資料庫API規範要求引數化查詢,儘管不同的庫可能對佔位符使用不同的語法(如%樣式引數、:named引數、數字引數等)。

您可以使用程式碼分析工具來檢查SQL注入,Bento就是其中的一種,它可以捕獲常見的SQL注入問題。不過,通過引數化查詢來完全阻止此類攻擊才是最佳方案。

Django如何防止SQLi

引數化查詢在Django的ORM中無處不在,因此對於SQLi具有很強的防禦能力。如果您的APP使用此框架進行資料庫查詢,那就相當安全了。

但是,在一些情況下,您還是得注意注入攻擊,極少數的API並非100%安全,這些是進行自動化程式碼分析時應當重點關注的物件。

Raw Queries

有時ORM不足以滿足需求,需要使用原生SQL。不過在此之前,還請考慮是否有避免這種情況的方法。例如,在資料庫檢視之上構建Django model,或者呼叫儲存過程(stored procedure)都是行之有效的方案。

當然某些情況下,無法避免原生SQL,可以通過一些API來實現,不過不夠安全。如下是Django提供的API:

1.Raw queries,例如:


2.RawSQL註解,例如:


3.直接使用資料庫遊標,例如:


4.避免:Queryset.extra(沒有示例:此方法不安全,出於完整性考慮才將其包括在內)。

如何安全地使用這些API:

閱讀本文第一部分,並確保瞭解引數化查詢;

不要使用extra。此方法很難以100%安全的方式使用(即使不是不可能),因此視為棄用;

始終傳遞引數化的語句,即使引數列表為空,如下所示:


這是為了提醒您以後將引數新增到此列表,並讓Bento之類的自動化工具更易發現潛在的API錯誤使用情況。

查詢本身應始終是一個靜態字串,而不是由其他字串處理而成。同樣,也是為了讓自動化工具更易發現API錯誤使用的情況。

自動預防

一種推薦的方案是通過程式碼分析工具來捕捉可預防的錯誤。俗話說,犯錯者為人。Bento將自動檢查Django程式碼的SQL注入模式。以下內容將檢查程式碼庫是否因為請求物件而導致SQL注入:


比起檢查當前程式碼,更好的做法是檢查未來的程式碼。Bento旨在作為一個預提交hook或在連續整合(CI)環境中執行。它可以識別差異,並且僅檢查提交,從而既保證程式碼安全又確保快速的工作流程。如果您在專案中初始化Bento,將自動設定為檢查提交。

這種基於提交的工作流在確保某些模式永遠不會進入程式碼庫方面特別強大。為了消除SQL注入,您應該自動檢測程式碼:

始終使用引數化查詢;

永遠不用.extra。

Bento可以通過其他註冊器來檢測這些模式:


即使沒有漏洞,這組規則也會高亮檢測結果。如果一次性檢測全部程式碼,會更嚴格。您可以使用Bento來歸檔,此時結果將被隱藏,直到準備好要處理它們。這樣就可以繼續檢查程式碼中的問題,不會因為之前的結果而應接不暇。

Bento是基於Semgrep執行的。這是一種檢測程式碼庫bug和反模式的工具,它結合了grep在語法和語義搜尋上的正確性。與普通grep相比,Semgrep的優勢在於不受邊界限制。

假設要檢測如下SQL注入:


在Semgrep中可以這樣表示:


在基於提交的工作流程中進行程式碼檢測非常重要,因為可以從程式碼庫中消除此類SQL注入。詳情請參考https://sgrep.live/0X5。

其他ORMs

最後,如果您覺得Django的ORM還不能滿足需求,可以採用SQLAlchemy來代替。這樣您將失去許多Django的便捷功能,例如管理、模型表單以及基於模型的通用檢視,但會獲得更強大、表達性更好且足夠安全的API。

自定義ORM

最後,即使您不使用原生SQL,也潛在一些風險。Django允許建立自定義聚合和自定義表示式,例如第三方庫提供Document.objects.filter(title__similar_to=other_title)這樣的API也能執行。

Django的核心ORM——核心的表示式、註解以及聚合——都已經相當成熟,出現SQLi的概率非常低。但是一些自定義的ORM,尤其是您自己實現的內容,還是存在一定的風險。

為了減輕這些高階功能帶來的SQL注入風險,我建議以下幾點:

首先,請謹慎引入第三方應用中自定義的表示式/聚合。在使用前,您應該仔細稽核這些應用。該應用是否成熟、穩定且有人維護?是否有信心任何安全問題都能及時得到解決?當然,請防止在沒有確認的情況下安裝更新或是安全性較低的版本。

同樣,在編寫自己的自定義聚合時也要謹慎。請仔細閱讀本文開頭,以及有關避免在自定義表示式中進行SQL注入的Django文件。如文件所示,如果可以,應儘量避免在自定義表示式中進行字串插入。如果不能,則需要自己轉義所有表示式引數。這很難做到,且依賴資料庫引擎和Python API的具體情況。謹慎入坑!

Bento的django.security.audit註冊器可以檢查程式碼庫中是否有自定義ORM,還能以此快速稽核第三方應用。這個開發條件有細微的差別,如果您在專案中發現此問題,請務必諮詢這方面的專家!

總結

Django旨在防禦SQL注入(和其他常見網路漏洞),其中最常用的內容都被自動保護,因此Django應用很少出現SQLi漏洞。

然而一旦發生,SQLi漏洞將是毀滅性的打擊,因此值得花些時間來檢查程式碼庫以確保安全。Bento可以標記一些常見漏洞,既如此,就可以更好地編寫安全的程式碼了。

英文原文:https://blog.r2c.dev/2020/preventing-sql-injection-a-django-authors-perspective/