nginx lua针对post上传请求,解析上传请求内容,这里做了畸形报文检测。
在针对http上传请求流量时,可以采取这种方法进行解析和过滤识别上传内容。
根据解析后的内容,可以手动写规则进行拦截过滤等。
进一步的还可以获取上传的文件内容,文件名,文件大小等信息。
针对文件进行实时的或者离线的文件内容分析,判断是否是恶意的文件上传webshell等。
这里只贴出解析部分代码,后续扩展可自行实现。
代码语言:javascript复制function _M.parse_request_body(waf, request_headers, collections)
local content_type_header = request_headers["content-type"]
-- multiple content-type headers are likely an evasion tactic
-- or result from misconfigured proxies. may consider relaxing
-- this or adding an option to disable this checking in the future
if type(content_type_header) == "table" then
Util.crsDebug(waf.transaction_id .. "Request contained multiple content-type headers, bailing!")
ngx.exit(400)
end
-- ignore the request body if no Content-Type header is sent
-- this does technically violate the RFC
-- but its necessary for us to properly handle the request
-- and its likely a sign of nogoodnickery anyway
--[[
if not content_type_header then
--if waf._debug == true then ngx.log(waf._debug_log_level, '[', waf.transaction_id, '] ', "Request has no content type, ignoring the body") end
--if waf._debug == true then Util.crsDebug(waf.transaction_id .. "Request has no content type, ignoring the body") end
return nil
end
--]]
--按照上面的说法,可能会产生构造空content-type绕过waf的情况
if not content_type_header then
ngx.req.read_body()
if ngx.req.get_body_file() == nil then
return ngx.req.get_post_args()
else
return nil
end
end
-- handle the request body based on the Content-Type header
-- multipart/form-data requests will be streamed in via lua-resty-upload,
-- which provides some basic sanity checking as far as form and protocol goes
-- (but its much less strict that ModSecurity's strict checking)
if ngx.re.find(content_type_header, [=[^multipart/form-data; boundary=]=], waf._pcre_flags) then
if not waf._process_multipart_body then
return
end
if ngx.ctx.files == nil and ngx.ctx.form_file_name == nil and ngx.ctx.file_sizes == nil and ngx.ctx.files_tmp_content == nil and ngx.ctx.files_combined_size == nil then
local form, err = upload:new()
if not form then
logger.warn(waf, "failed to parse multipart request: ", err)
ngx.exit(400) -- may move this into a ruleset along with other strict checking
end
local FILES = {}
local FILES_NAMES = {}
local FILES_SIZES = {}
local FILES_TMP_CONTENT = {}
ngx.req.init_body()
form:set_timeout(1000)
ngx.req.append_body("--" .. form.boundary)
-- this is gonna need some tlc, but it seems to work for now
local lasttype, chunk, file, body, body_size, files_size
files_size = 0
body_size = 0
body = ''
while true do
local typ, res, err = form:read()
if not typ then
logger.fatal_fail("failed to stream request body: " .. err)
end
if typ == "header" then
if res[1]:lower() == 'content-disposition' then
local header = res[2]
local s, f = header:find(' name="([^"] ")')
file = header:sub(s 7, f - 1)
table.insert(FILES_NAMES, file)
s, f = header:find('filename="([^"] ")')
if s then table.insert(FILES, header:sub(s 10, f - 1)) end
end
chunk = res[3] -- form:read() returns { key, value, line } here
ngx.req.append_body("rn" .. chunk)
elseif typ == "body" then
chunk = res
if lasttype == "header" then
ngx.req.append_body("rnrn")
end
local chunk_size = #chunk
body = body .. chunk
body_size = body_size #chunk
if waf._debug == true then Util.crsDebug(waf.transaction_id .. "chunk_size: " .. chunk_size .. ", body_size: " .. body_size) end
ngx.req.append_body(chunk)
elseif typ == "part_end" then
table.insert(FILES_SIZES, body_size)
files_size = files_size body_size
body_size = 0
FILES_TMP_CONTENT[file] = body
body = ''
ngx.req.append_body("rn--" .. form.boundary)
elseif typ == "eof" then
ngx.req.append_body("--rn")
break
end
lasttype = typ
end
-- lua-resty-upload docs use one final read, i think it's needed to get
-- the last part of the data off the socket
form:read()
ngx.req.finish_body()
collections.FILES = FILES
collections.FILES_NAMES = FILES_NAMES
collections.FILES_SIZES = FILES_SIZES
collections.FILES_TMP_CONTENT = FILES_TMP_CONTENT
collections.FILES_COMBINED_SIZE = files_size
else
collections.FILES = ngx.ctx.files
collections.FILES_NAMES = ngx.ctx.form_file_name
collections.FILES_SIZES = ngx.ctx.file_sizes
collections.FILES_TMP_CONTENT = ngx.ctx.files_tmp_content
collections.FILES_COMBINED_SIZE = ngx.ctx.files_combined_size
end
return nil
elseif ngx.re.find(content_type_header, [=[^application/x-www-form-urlencoded]=], waf._pcre_flags) then
-- use the underlying ngx API to read the request body
-- ignore processing the request body if the content length is larger than client_body_buffer_size
-- to avoid wasting resources on ruleset matching of very large data sets
ngx.req.read_body()
--如果上传文件大小小于设定的buffer,则临时文件get_body_file不会产生
if ngx.req.get_body_file() == nil then
return ngx.req.get_post_args()
else
if waf._debug == true then Util.crsDebug(waf.transaction_id .. "Request body size larger than client_body_buffer_size, ignoring request body") end
return nil
end
--[[elseif util.table_has_key(content_type_header, waf._allowed_content_types) then
-- if the content type has been whitelisted by the user, set REQUEST_BODY as a string
ngx.req.read_body()
if ngx.req.get_body_file() == nil then
return ngx.req.get_body_data()
else
if waf._debug == true then ngx.log(waf._debug_log_level, '[', waf.transaction_id, '] ', "Request body size larger than client_body_buffer_size, ignoring request body") end
return nil
end]]--
else
--[[if waf._allow_unknown_content_types then
if waf._debug == true then ngx.log(waf._debug_log_level, '[', waf.transaction_id, '] ', "Allowing request with content type " .. tostring(content_type_header)) end
return nil
else
if waf._debug == true then ngx.log(waf._debug_log_level, '[', waf.transaction_id, '] ', tostring(content_type_header) .. " not a valid content type!") end
ngx.exit(ngx.HTTP_FORBIDDEN)
end]]--
end
end