前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nginx结合lua解析post上传内容

nginx结合lua解析post上传内容

原创
作者头像
stan1ey
修改2021-08-11 14:48:21
1.4K0
修改2021-08-11 14:48:21
举报
文章被收录于专栏:安全开发记录安全开发记录

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("\r\n" .. chunk)
                elseif typ == "body" then
                    chunk = res
                    if lasttype == "header" then
                        ngx.req.append_body("\r\n\r\n")
                    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("\r\n--" .. form.boundary)
                elseif typ == "eof" then
                    ngx.req.append_body("--\r\n")
                    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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
应用安全开发
应用安全开发(Application Security Development,下文中也叫 Xcheck)为您提供优质的代码分析服务。Xcheck 凭借优秀的算法和工程实现,能在极低的误报率和漏报率前提下,以极快的速度发现代码中存在的安全漏洞。Xcheck 采用私有化部署的模式,所以产品使用的整个生命周期,源码都不会流出公司网络,杜绝源码泄露风险。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档