nginx结合lua解析post上传内容

2021-08-11 14:48:21 浏览数 (1)

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

0 人点赞