Rust語言:基於變量生命周期進行編譯時SQL注入防止

Rust語言:基於變量生命週期進行編譯時SQL注入防止

自打Web流行以後,面向用戶終端的應用程序和數據庫開始結合,成了動態網站的開發標準架構。但是,這就帶來了一個可怕的漏洞:SQL注入。

在世界最具危險的漏洞排行榜中,SQL常年佔據榜首,是最具危險的漏洞。

Rust語言:基於變量生命週期進行編譯時SQL注入防止

為了應對SQL注入的威脅,業界提供了佔位符和SQL連接類庫的prepared語句。這種方式,將可變數據從實際查詢中分離出來,確保兩者不被混合。幾乎所有現代SQL客戶端都支持這個功能,當然,還是可以通過sql語句中混合可變的數據。

為了展示編譯時程序驗證的做法,蟲蟲在此通過Rust語言實例來說明下如何通過Rust特有的生命週期功能來檢查SQL注入漏洞問題。

開始

我們首先創建一個執行SQL查詢的函數。它需要一個SQL查詢和參數值列表以用於佔位符:

fn sql_query(query: &str, params: &[&SQLParam]) -> SQLRows {

// ...

}

為了引入SQL注入漏洞,我們可以動態構建一個format!宏SQL的查詢,Rust的sprintf版本,如下所示:

fn get_user_by_name(username: &str) {

let query = format!("SELECT * FROM users WHERE username={}", username);

let _rows = sql_query(&query, &[]);

// ...

}

這個函數有問題。攻擊者可以提交利用函數中SQL查詢交互的用戶名,在其中注入額外的代碼。代碼可以正常的編譯,只有通過代碼審計才能發現漏洞。

那麼,如何讓我們在編譯就能檢測到這個問題呢?

生命週期

可能你還不熟悉Rust語言,這個語言有一個生命週期的概念。這些是編譯時必須滿足的特殊條件。用它們來防止內存在被引用時被釋放掉。生命週期通常侷限於所引用的變量的詞法作用域。

我們的sql_query的查詢參數是對實際字符串的引用,所以為了確保引用的內存還在被引用的時候不會被釋放掉,所以有了生命週期。生命週期默認由編譯器自動推斷。但是我們可以自己設置生命週期,比如sql_query函數:

fn sql_query(query: &'a str) {

// ...

}

函數的生命週期必須在可以其使用之前聲明。這就是為什麼"'a"出現兩次的原因。一次在查詢類型中,還有函數名稱之後的的聲明中。

'static生命週期

有一個特殊的"'"靜態生命週期值,它可以超過程序的生命週期。

我們可以利用這個特性。如果我們將查詢的生命週期更改為'static,那麼它將只接受與程序同樣生命週期長的字符串:

fn sql_query(query: &'static str) {

// ...

}

這會導致動態字符串不再是有效的參數,因為它們是動態構建的,因此永遠不滿足'static條件。

如果我們試圖編譯之前錯誤的示例代碼,我們會發現它會報錯:

error[E0597]: `query` does not live long enough

--> src/main.rs:6:28

|

6 | let _rows = sql_query(&query, &[]);

| ^^^^^ borrowed value does not live long enough

7 | }

| - borrowed value only lives until here

|

= note: borrowed value must be valid for the static lifetime...

這樣,我們現在無需運行程序就可以成功預防SQL注入漏洞!

要修復這個問題,我們必須提供一個靜態字符串並使用佔位符?做為用戶名。

let _rows = sql_query("SELECT * FROM users WHERE username=?", &[username]);

因為查詢字符串現在是一個字符串,所以它滿足'static生命週期,允許程序編譯。

結論

Rust是一種具有許多有趣功能的語言,它們可以幫助程序員編寫靜態正確性檢查的代碼。這也給了它學習起來有點燒腦,但是一旦你掌握了這們神奇的語言,你也就不太可能再犯SQL注入這樣的錯誤了。

我們在這裡通過一個實例證明了靜態程序分析的強大和用途,以及它如何幫助碼農們寫出更好更安全的軟件。


分享到:


相關文章: