OpenResty SQL 注入

2021-08-13 09:31 更新

有使用 SQL 語句操作數(shù)據(jù)庫的經(jīng)驗(yàn)朋友,應(yīng)該都知道使用 SQL 過程中有一個(gè)安全問題叫 SQL 注入。所謂 SQL 注入,就是通過把 SQL 命令插入到 Web 表單提交或輸入域名或頁面請(qǐng)求的查詢字符串,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的 SQL 命令。 為了防止 SQL 注入,在生產(chǎn)環(huán)境中使用 OpenResty 的時(shí)候就要注意添加防范代碼。

延續(xù)之前的 ngx_postgres 調(diào)用代碼的使用,

    local sql_normal = [[select id, name from user where name=']] .. ngx.var.arg_name .. [[' and password=']] .. ngx.var.arg_password .. [[' limit 1;]]

    local res = ngx.location.capture('/postgres',
        { args = {sql = sql } }
    )

    local body = json.decode(res.body)

    if (table.getn(res) > 0) {
        return res[1];
    }

    return nil;

假設(shè)我們?cè)谟脩舻卿浭褂蒙?SQL 語句查詢賬號(hào)是否賬號(hào)密碼正確,用戶可以通過 GET 方式請(qǐng)求并發(fā)送登錄信息比如:

#    http://localhost/login?name=person&password=12345

那么我們上面的代碼通過 ngx.var.arg_name 和 ngx.var.arg_password 獲取查詢參數(shù),并且與 SQL 語句格式進(jìn)行字符串拼接,最終 sql_normal 會(huì)是這個(gè)樣子的:

    local sql_normal = [[select id, name from user where name='person' and password='12345' limit 1;]]

正常情況下,如果 person 賬號(hào)存在并且 password 是 12345,那么 sql 執(zhí)行結(jié)果就應(yīng)該是能返回 id 號(hào)的。這個(gè)接口如果暴露在攻擊者面前,那么攻擊者很可能會(huì)讓參數(shù)這樣傳入:

    name="' or ''='"
    password="' or ''='"

那么這個(gè) sql_normal 就會(huì)變成一個(gè)永遠(yuǎn)都能執(zhí)行成功的語句了。

    local sql_normal = [[select id, name from user where name='' or ''='' and password='' or ''='' limit 1;]]

這就是一個(gè)簡(jiǎn)單的 sql inject(注入)的案例,那么問題來了,面對(duì)這么兇猛的攻擊者,我們有什么辦法防止這種 SQL 注入呢?

很簡(jiǎn)單,我們只要 把 傳入?yún)?shù)的變量 做一次字符轉(zhuǎn)義,把不該作為破壞 SQL 查詢語句結(jié)構(gòu)的雙引號(hào)或者單引號(hào)等做轉(zhuǎn)義,把 ' 轉(zhuǎn)義成 \',那么變量 name 和 password 的內(nèi)容還是乖乖的作為查詢條件傳入,他們?cè)僖膊荒転榉亲鞔趿恕?/p>

那么怎么做到字符轉(zhuǎn)義呢?要知道每個(gè)數(shù)據(jù)庫支持的 SQL 語句格式都不太一樣啊,尤其是雙引號(hào)和單引號(hào)的應(yīng)用上。有幾個(gè)選擇:

    ndk.set_var.set_quote_sql_str()
    ndk.set_var.set_quote_pgsql_str()
    ngx.quote_sql_str()

這三個(gè)函數(shù),前面兩個(gè)是 ndk.set_var 跳轉(zhuǎn)調(diào)用,其實(shí)是 HttpSetMiscModule 這個(gè)模塊提供的函數(shù),是一個(gè) C 模塊實(shí)現(xiàn)的函數(shù),ndk.set_var.set_quote_sql_str() 是用于 MySQL 格式的 SQL 語句字符轉(zhuǎn)義,而 set_quote_pgsql_str 是用于 PostgreSQL 格式的 SQL 語句字符轉(zhuǎn)義。最后 ngx.quote_sql_str 是一個(gè) ngx_lua 模塊中實(shí)現(xiàn)的函數(shù),也是用于 MySQL 格式的 SQL 語句字符轉(zhuǎn)義。

讓我們看看代碼怎么寫:

    local name = ngx.quote_sql_str(ngx.var.arg_name)
    local password = ngx.quote_sql_str(ngx.var.arg_password)
    local sql_normal = [[select id, name from user where name=]] .. name .. [[ and password=]] .. password .. [[ limit 1;]]

    local res = ngx.location.capture('/postgres',
        { args = {sql = sql } }
    )

    local body = json.decode(res.body)

    if (table.getn(res) > 0) {
        return res[1];
    }

    return nil;

注意上述代碼有兩個(gè)變化:

* 用 ngx.quote_sql_str 把 ngx.var.arg_name 和 ngx.var.arg_password 包了一層,把返回值作為 sql 語句拼湊起來。
* 原本在 sql 語句中添加的單引號(hào)去掉了,因?yàn)?ngx.quote_sql_str 的返回值正確的帶上引號(hào)了。

這樣已經(jīng)可以抵御 SQL 注入的攻擊手段了,但開發(fā)過程中需要不斷的產(chǎn)生新功能新代碼,這時(shí)候也一定注意不要忽視 SQL 注入的防護(hù),安全防御代碼就想織網(wǎng)一樣,只要有一處漏洞,魚兒可就游走了。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)