【摘要】 在安全領域,lua編程語言因為其小巧在眾多工具上都作為插件開發語言,常見的有openresty,nmap等。因此筆者將會開辟一個Lua相關的系列文章,主要記錄工作過程中一些領悟或者是一些踩過的坑,希望能夠借此平台幫助到讀者們。
0x00 背景
最近在寫一段nginx+redis的代碼,主要基於openresty,其中使用到了lua-resty-redis庫。我平時寫代碼都比較小心,針對外部輸入的值一般都會進行異常判斷,大概的代碼如下:
local redis = require "redis"
local cjson = require "cjson"
--[[省略部分代碼]]
local ok, err = redis:get("key")
if not ok then
ngx.log(ngx.ERR, '[ERROR]:', err)
return
end
local data = cjson.decode(ok)
在decode這里出現了錯誤提示,但是ok並沒有為空或者nil,不然代碼是走不到這里來。
發現問題后,我們就在前面打印一下ok數據的類型吧,大概的代碼如下:
ngx.log(ngx.ERR, 'ok type: ', type(ok))
if not ok then
-- TODO
end
這個時候我們得到的結果是userdata,這個東西算是一種復雜結構體,一般都是跨語言產生的,比如ffi.C這些。當時我的思路大概也是這樣,肯定redis存放的數據是二進制的,但是呀,存放什么數據都是我自己控制的,不可能有什么畸形數據,因此這一點也排除了。最后在自己查看中發現,其實就是這個key不存在。
0x01 分析
既然原因找到了,我們就去看看為什么會這樣,主要通過閱讀lua-resty-redis的源碼:
local function _read_reply(self, sock)
local line, err = sock:receive()
if not line then
if err == "timeout" and not rawget(self, "_subscribed") then
sock:close()
end
return nil, err
end
local prefix = byte(line)
if prefix == 36 then -- char '$'
-- print("bulk reply")
local size = tonumber(sub(line, 2))
if size < 0 then
return null
end
local data, err = sock:receive(size)
if not data then
if err == "timeout" then
sock:close()
end
return nil, err
end
local dummy, err = sock:receive(2) -- ignore CRLF
if not dummy then
return nil, err
end
return data
elseif prefix == 43 then -- char '+'
-- print("status reply")
return sub(line, 2)
elseif prefix == 42 then -- char '*'
local n = tonumber(sub(line, 2))
-- print("multi-bulk reply: ", n)
if n < 0 then
return null
end
local vals = new_tab(n, 0)
local nvals = 0
for i = 1, n do
local res, err = _read_reply(self, sock)
if res then
nvals = nvals + 1
vals[nvals] = res
elseif res == nil then
return nil, err
else
-- be a valid redis error value
nvals = nvals + 1
vals[nvals] = {false, err}
end
end
return vals
elseif prefix == 58 then -- char ':'
-- print("integer reply")
return tonumber(sub(line, 2))
elseif prefix == 45 then -- char '-'
-- print("error reply: ", n)
return false, sub(line, 2)
else
-- when `line` is an empty string, `prefix` will be equal to nil.
return nil, "unknown prefix: \"" .. tostring(prefix) .. "\""
end
end
從上面的源碼可以看到,在讀取redis服務器返回數據的時候,如果某些格式不正確,比如數據長度的字節小於0這樣的異常情況,函數就會返回null,注意是null不是nil。
這個null的定義來自ngx.null,這個東西可以追溯到其官方文檔lua-nginx-module.
The ngx.null constant is a NULL light userdata usually used to represent nil values in Lua tables etc and is similar to the lua-cjson library’s cjson.null constant.
從上面描述看,ngx.null就是一個代表null的userdata結構,類似一個自定義的類,但是沒有什么具體含義,同時文檔里面也提到了類似的值還有cjson.null,以后小心被坑。
0x02 擴展
同時文檔中還提到了,使用ngx.log對幾個空值進行字符串打印的時候
-
nil會顯示成“nil”,
-
邏輯值會顯示成“true”或者“false”,
-
ngx.null會被顯示成“null”。
來源:華為雲社區 作者:HuangJacky