【摘要】 在安全領域,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”。