05.28 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注入这样的错误了。

我们在这里通过一个实例证明了静态程序分析的强大和用途,以及它如何帮助码农们写出更好更安全的软件。


分享到:


相關文章: