dream_653
作者dream_653·2020-06-29 08:43
系统应用运维·*****

Nginx防火墙自动封禁解封IP(添加redis版本)

字数 41700阅读 5433评论 0赞 1

ngx_lua_waf 是一个高性能的轻量级 web 应用防火墙,基于 lua-nginx-module
它具有以下功能:
防止sql注入,本地包含,部分溢出,fuzzing测试,xss,SSRF等web攻击
防止svn/备份之类文件泄漏
防止ApacheBench之类压力测试工具的攻击
屏蔽常见的扫描黑客工具,扫描器
屏蔽异常的网络请求
屏蔽图片附件类目录php执行权限
防止webshell上传
安装
安装起来也是相当容易,说白了就是给 nginx 增加 ngx_devel_kit、lua-nginx-module 这两个模块,然后再修改 nginx 配置来运行 ngx_lua_waf。
下载 ngx_lua_waf 防火墙的各种依赖及模块

cd /root   
wget https://github.com/openresty/luajit2/archive/v2.1-20181029.tar.gz   
tar xzvf v2.1-20181029.tar.gz   
mv luajit2-2.1-20181029 luajit-2.1   
wget https://github.com/openresty/lua-cjson/archive/2.1.0.6.tar.gz   
tar xzvf 2.1.0.6.tar.gz   
mv lua-cjson-2.1.0.6 lua-cjson   
wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1rc1.tar.gz   
tar xzvf v0.3.1rc1.tar.gz   
mv ngx_devel_kit-0.3.1rc1 ngx_devel_kit   
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.13.tar.gz   
tar xzvf v0.10.13.tar.gz   
mv lua-nginx-module-0.10.13 lua-nginx-module  

编译安装 luajit

cd luajit-2.1 && make -j2 && make install  
echo '/usr/local/lib' >> /etc/ld.so.conf.d/local.conf  
ldconfig  

编译安装 lua-cjson

cd lua-cjson && export LUA_INCLUDE_DIR=/usr/local/include/luajit-2.1  
make -j2 && make install  

设置 LUAJIT 环境变量

export LUAJIT_LIB=/usr/local/lib  
export LUAJIT_INC=/usr/local/include/luajit-2.1  

编译 nginx 的时候加上以下两个模块

--add-module=../lua-nginx-module   
--add-module=../ngx_devel_kit  
[root@elk-master-node ~]# cd nginx-1.14.2/   
[root@elk-master-node nginx-1.14.2]# ./configure --with-stream --with-http_ssl_module --prefix=/opt/nginx1.14.2 --add-module=../lua-nginx-module --add-module=../ngx_devel_kit  

下载配置 ngx_lua_waf

[root@elk-master-node nginx-1.14.2]# cd /opt/nginx1.14.2/conf/
[root@elk-master-node nginx-1.14.2]# git clone https://github.com/loveshell/ngx_lua_waf.git waf
 mkdir -p /opt/nginx1.14.2/logs/waf/
 chown www.www /opt/nginx1.14.2/logs/waf  注:www.www 用户和组参照nginx.conf中use

也可以使用宝塔linux面板 【https://www.bt.cn/bbs/thread-19376-1-1.html
此次配置文件是从宝塔linux面板安装nginx后拷贝过来进行的整改

在 nginx.conf 里http段添加 include waf.conf

include waf.conf;

waf.conf配置

[root@elk-master-node conf]# cat /opt/nginx1.14.2/conf/waf.conf 
#lua_shared_dict limit 20m;
lua_package_path "/opt/nginx1.14.2/conf/waf/?.lua";
init_by_lua_file "/opt/nginx1.14.2/conf/waf/init.lua";
access_by_lua_file "/opt/nginx1.14.2/conf/waf/waf.lua";

waf目录下所有文件

[root@elk-master-node waf]# ls -lrt /opt/nginx1.14.2/conf/waf/|grep -v bak  
total 60  
-rw-r--r--. 1 root root 4612 Apr 19 14:18 README.md  
-rw-r--r--. 1 root root 1587 Apr 19 14:18 install.sh  
-rw-r--r--. 1 root root 11932 May 3 01:10 redis.lua  
-rw-r--r--. 1 root root 2468 May 3 22:46 waf.lua  
-rw-r--r--. 1 root root 10527 May 4 00:29 init.lua  
drwxr-xr-x. 2 root root 216 May 4 00:33 wafconf  
-rw-r--r--. 1 root root 381 May 4 00:34 config.lua  
[root@elk-master-node waf]# ls -lrt /opt/nginx1.14.2/conf/waf/wafconf/|grep -v bak  
total 52  
-rw-r--r--. 1 root root 7 Jan 6 2019 white-user-agent  
-rw-r--r--. 1 root root 11 May 2 20:37 blockip  
-rw-r--r--. 1 root root 796 May 2 20:37 args  
-rw-r--r--. 1 root root 652 May 2 20:37 cookie  
-rw-r--r--. 1 root root 190 May 2 20:37 denycc  
-rw-r--r--. 1 root root 773 May 2 20:37 post  
-rw-r--r--. 1 root root 195 May 2 20:37 user-agent  
-rw-r--r--. 1 root root 1265 May 2 20:54 returnhtml  
-rw-r--r--. 1 root root 1256 May 2 23:09 returnhtml1  
-rw-r--r--. 1 root root 21 May 3 23:43 whiteurl  
-rw-r--r--. 1 root root 518 May 3 23:44 url  
-rw-r--r--. 1 root root 30 May 4 00:33 whiteip
  

waf.lua配置

[root@elk-master-node conf]# cat /opt/nginx1.14.2/conf/waf/waf.lua
local content_length=tonumber(ngx.req.get_headers()['content-length'])
local method=ngx.req.get_method()
local ngxmatch=ngx.re.match
if whiteip() then
elseif blockip() then
elseif whiteurl() then
--elseif is_ban() then
elseif denycc() then
elseif ngx.var.http_Acunetix_Aspect then
    ngx.exit(444)
elseif ngx.var.http_X_Scan_Memo then
    ngx.exit(444)
elseif ua() then
elseif url() then
elseif args() then
elseif cookie() then
elseif PostCheck then
    if method=="POST" then   
            local boundary = get_boundary()
      if boundary then
      local len = string.len
            local sock, err = ngx.req.socket()
          if not sock then
          return
            end
      ngx.req.init_body(128 * 1024)
            sock:settimeout(0)
      local content_length = nil
          content_length=tonumber(ngx.req.get_headers()['content-length'])
          local chunk_size = 4096
            if content_length < chunk_size then
          chunk_size = content_length
      end
            local size = 0
      while size < content_length do
    local data, err, partial = sock:receive(chunk_size)
    data = data or partial
    if not data then
      return
    end
    ngx.req.append_body(data)
          if body(data) then
               return true
            end
    size = size + len(data)
    local m = ngxmatch(data,[[Content-Disposition: form-data;(.+)filename="(.+)\\\\.(.*)"]],'ijo')
          if m then
                fileExtCheck(m[3])
                filetranslate = true
          else
                if ngxmatch(data,"Content-Disposition:",'isjo') then
                    filetranslate = false
                end
                if filetranslate==false then
                  if body(data) then
                          return true
                    end
                end
          end
    local less = content_length - size
    if less < chunk_size then
      chunk_size = less
    end
   end
   ngx.req.finish_body()
    else
      ngx.req.read_body()
      local args = ngx.req.get_post_args()
      if not args then
        return
      end
      for key, val in pairs(args) do
        if type(val) == "table" then
          if type(val[1]) == "boolean" then
            return
          end
          data=table.concat(val, ", ")
        else
          data=val
        end
        if data and type(data) ~= "boolean" and body(data) then
                      body(key)
        end
      end
    end
    end
else
    return
end

init.lua配置

[root@elk-master-node conf]# cat /opt/nginx1.14.2/conf/waf/init.lua
require 'config'
local match = string.match
local ngxmatch=ngx.re.find
local unescape=ngx.unescape_uri
local get_headers = ngx.req.get_headers
local optionIsOn = function (options) return options == "on" and true or false end
logpath = logdir 
rulepath = RulePath
UrlDeny = optionIsOn(UrlDeny)
PostCheck = optionIsOn(postMatch)
CookieCheck = optionIsOn(cookieMatch)
WhiteCheck = optionIsOn(whiteModule)
PathInfoFix = optionIsOn(PathInfoFix)
attacklog = optionIsOn(attacklog)
CCDeny = optionIsOn(CCDeny)
Redirect=optionIsOn(Redirect)
function subString(str, k)    --截取字符串
    ts = string.reverse(str)
    _, i = string.find(ts, k)
    m = string.len(ts) - i + 1
    return string.sub(str, 1, m)
end
--function getClientIp()
--        IP  = ngx.var.remote_addr 
--    if ngx.var.HTTP_X_FORWARDED_FOR then
--      IP = ngx.var.HTTP_X_FORWARDED_FOR
--    end
--        if IP == nil then
--                IP  = "unknown"
--        end
--    IP = subString(IP, "[.]") .. "*"
--        return IP
--end
    function getClientIp()
        IP = ngx.req.get_headers()["X-Real-IP"]
        if IP == nil then
            IP = ngx.req.get_headers()["x_forwarded_for"]
        end
        if IP == nil then
            IP  = ngx.var.remote_addr
        end
        if IP == nil then
            IP  = "unknown"
        end
        return IP
    end
--function getRealIp()
--        IP  = ngx.var.remote_addr 
--    if ngx.var.HTTP_X_FORWARDED_FOR then    --如果用了CDN,判断真实IP
--      IP = ngx.var.HTTP_X_FORWARDED_FOR
--    end
--        if IP == nil then
--                IP  = "unknown"
--        end
--    return IP
--end
function write(logfile,msg)
    local fd = io.open(logfile,"ab")
    if fd == nil then return end
    fd:write(msg)
    fd:flush()
    fd:close()
end
function log(method,url,data,ruletag)
    if attacklog then
--        local realIp = getRealIp()
        local realIp = getClientIp()
        local ua = ngx.var.http_user_agent
        local servername=ngx.var.server_name
        local time=ngx.localtime()
        if ua  then
            line = realIp.." ["..time.."] \\""..method.." "..servername..url.."\\" \\""..data.."\\"  \\""..ua.."\\" \\""..ruletag.."\\"\\n"
        else
            line = realIp.." ["..time.."] \\""..method.." "..servername..url.."\\" \\""..data.."\\" - \\""..ruletag.."\\"\\n"
        end
        local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log"
        write(filename,line)
    end
end
------------------------------------规则读取函数-------------------------------------------------------------------
function read_rule(var)
    file = io.open(rulepath..'/'..var,"r")
    if file==nil then
        return
    end
    t = {}
    for line in file:lines() do
        table.insert(t,line)
    end
    file:close()
    return(t)
end
-----------------------------------频繁扫描封禁ip-------------------------------------------------------------------
--function ban_ip(point)
--    local token = getClientIp() .. "_WAF"
--    local limit = ngx.shared.limit
--    local req,_=limit:get(token)
--    if req then
--    limit:set(token,req+point,3600)  --发现一次,增加积分,1小时内有效
--    else
--    limit:set(token,point,3600)
--    end
--end
 
--function get_ban_times()
--  local token = getClientIp() .. "_WAF"
--  local limit = ngx.shared.limit
--        local req,_=limit:get(token)
--  if req then
--    return req
--  else return 0
--  end
--end
 
--function is_ban()
--  local ban_times = get_ban_times()
--  if ban_times >= 100 then        --超过100积分,ban
--    ngx.header.content_type = "text/html;charset=UTF-8"
--    ngx.status = ngx.HTTP_FORBIDDEN
--    ngx.exit(ngx.status)
--    return true
--  else
--    return false
--  end
--  return false
--end
 
urlrules=read_rule('url')
argsrules=read_rule('args')
uarules=read_rule('user-agent')
wturlrules=read_rule('whiteurl')
postrules=read_rule('post')
ckrules=read_rule('cookie')
ipWhitelist=read_rule('whiteip')
ipBlocklist=read_rule('blockip')
html=read_rule('returnhtml')
html1=read_rule('returnhtml1')
function say_html()
--  ban_ip(15)      --恶意攻击,罚15分
    if Redirect then
        ngx.header.content_type = "text/html;charset=UTF-8"
    ngx.status = ngx.HTTP_FORBIDDEN
        ngx.say(html)
        ngx.exit(ngx.status)
    end
end
function say_html1()
--  ban_ip(15)      --恶意攻击,罚15分
    if Redirect then
        ngx.header.content_type = "text/html;charset=UTF-8"
    ngx.status = ngx.HTTP_FORBIDDEN
        ngx.say(html1)
        ngx.exit(ngx.status)
    end
end
function whiteurl()
    if WhiteCheck then
        if wturlrules ~=nil then
            for _,rule in pairs(wturlrules) do
                if ngxmatch(ngx.var.uri,rule,"isjo") then
                    return true 
                 end
            end
        end
    end
    return false
end
function fileExtCheck(ext)
    local items = Set(black_fileExt)
    ext=string.lower(ext)
    if ext then
        for rule in pairs(items) do
            if ngx.re.match(ext,rule,"isjo") then
          log('POST',ngx.var.request_uri,"-","file attack with ext "..ext)
            say_html()
            end
        end
    end
    return false
end
function Set (list)
  local set = {}
  for _, l in ipairs(list) do set[l] = true end
  return set
end
function args()
    for _,rule in pairs(argsrules) do
        local args = ngx.req.get_uri_args()
        for key, val in pairs(args) do
            if type(val)=='table' then
                 local t={}
                 for k,v in pairs(val) do
                    if v == true then
                        v=""
                    end
                    table.insert(t,v)
                end
                data=table.concat(t, " ")
            else
                data=val
            end
            if data and type(data) ~= "boolean" and rule ~="" and ngxmatch(unescape(data),rule,"isjo") then
                log('GET',ngx.var.request_uri,"-",rule)
                say_html()
                return true
            end
        end
    end
    return false
end
 
 
function url()
    if UrlDeny then
        for _,rule in pairs(urlrules) do
            if rule ~="" and ngxmatch(ngx.var.request_uri,rule,"isjo") then
                log('GET',ngx.var.request_uri,"-",rule)
                say_html()
                return true
            end
        end
    end
    return false
end
 
function ua()
    local ua = ngx.var.http_user_agent
    if ua ~= nil then
        for _,rule in pairs(uarules) do
            if rule ~="" and ngxmatch(ua,rule,"isjo") then
                log('UA',ngx.var.request_uri,"-",rule)
                say_html()
              return true
            end
        end
    end
    return false
end
function body(data)
    for _,rule in pairs(postrules) do
        if rule ~="" and data~="" and ngxmatch(unescape(data),rule,"isjo") then
            log('POST',ngx.var.request_uri,data,rule)
            say_html()
            return true
        end
    end
    return false
end
function cookie()
    local ck = ngx.var.http_cookie
    if CookieCheck and ck then
        for _,rule in pairs(ckrules) do
            if rule ~="" and ngxmatch(ck,rule,"isjo") then
                log('Cookie',ngx.var.request_uri,"-",rule)
                say_html()
            return true
            end
        end
    end
    return false
end
 
function denycc()
--    if CCDeny then
--        CCcount=tonumber(string.match(CCrate,'(.*)/'))
--        CCseconds=tonumber(string.match(CCrate,'/(.*)'))
--        local token = getRealIp()
--        local limit = ngx.shared.limit
--        local req,_=limit:get(token)
--        if req then
--            if req > CCcount then
--         limit:incr(token,1)
--         ban_ip(req - CCcount)  --CC攻击,罚分
--         ngx.header.content_type = "text/html;charset=UTF-8"
--         ngx.status = ngx.HTTP_FORBIDDEN
--                 --ngx.say("老哥你手速也忒快了吧,要不休息"..CCcount.."秒?")
--                 ngx.say("操作过于频繁,请稍后再试 " ..CCcount.."s")
--                 ngx.exit(ngx.status)
--                 return true
--            else
--                 limit:incr(token,1)
--            end
--        else
--            limit:set(token,1,CCseconds)
--        end
--    end
--    return false
--end
--local get_headers = ngx.req.get_headers
local ua = ngx.var.http_user_agent
local uri = ngx.var.request_uri
local url = ngx.var.host .. uri
local redis = require 'redis'
local red = redis.new()
local CCcount = 20
local CCseconds = 60
local RedisIP = '127.0.0.1'
local RedisPORT = 6379
local blackseconds = 7200

if ua == nil then
    ua = "unknown"
end

if (uri == "/wp-admin.php") then
    CCcount=20
    CCseconds=60
end

red:set_timeout(100)
local ok, err = red.connect(red, RedisIP, RedisPORT)

if ok then
    red.connect(red, RedisIP, RedisPORT)

    local token = getClientIp() .. "." .. ngx.md5(url .. ua)
    local req = red:exists(token)
    if req == 0 then
        red:incr(token)
        red:expire(token,CCseconds)
    else
        local times = tonumber(red:get(token))
        if times >= CCcount then
            local blackReq = red:exists("black." .. token)
            if (blackReq == 0) then
                red:set("black." .. token,1)
                red:expire("black." .. token,blackseconds)
                red:expire(token,blackseconds)
                --ngx.exit(503)
        say_html1()
            else
                --ngx.exit(503)
        say_html1()
            end
            return
        else
            red:incr(token)
        end
    end
    return
  end
end
function get_boundary()
    local header = get_headers()["content-type"]
    if not header then
        return nil
    end
 
    if type(header) == "table" then
        header = header[1]
    end
 
    local m = match(header, ";%s*boundary=\\"([^\\"]+)\\"")
    if m then
        return m
    end
 
    return match(header, ";%s*boundary=([^\\",;]+)")
end
 
function whiteip()
    --if next(ipWhitelist) ~= nil then
    if ipWhitelist ~= nil then
        for _,ip in pairs(ipWhitelist) do
         if getClientIp()==ip then
                return true
            end
        end
    end
        return false
end
 
function blockip()
     --if next(ipBlocklist) ~= nil then
     if ipBlocklist ~= nil then
         for _,ip in pairs(ipBlocklist) do
             if getClientIp()==ip then
                 ngx.exit(444)
                 return true
             end
         end
     end
         return false
end

config.lua配置

[root@elk-master-node waf]# cat /opt/nginx1.14.2/conf/waf/config.lua
RulePath = "/opt/nginx1.14.2/conf/waf/wafconf/"    --规则文件夹
attacklog="on"
logdir = "/opt/nginx1.14.2/logs/waf/"    --日志文件夹
UrlDeny="on"
Redirect="on"
CookieMatch="on"
postMatch="on"
whiteModule="on" 
black_fileExt={"php"}
--ipWhitelist={"8.0.0.0"}
--ipBlocklist={"8.8.8.8"}
--CCDeny="off"
--CCrate="500/100"    --这个是CC攻击的几秒钟允许请求几次

redis.lua配置(参考配置https://github.com/openresty/lua-resty-redis/blob/master/lib/resty/redis.lua)

[root@elk-master-node waf]# cat /opt/nginx1.14.2/conf/waf/redis.lua 
-- Copyright (C) Yichun Zhang (agentzh)

local sub = string.sub
local byte = string.byte
local tab_insert = table.insert
local tab_remove = table.remove
local tcp = ngx.socket.tcp
local null = ngx.null
local type = type
local pairs = pairs
local unpack = unpack
local setmetatable = setmetatable
local tonumber = tonumber
local tostring = tostring
local rawget = rawget
local select = select
--local error = error


local ok, new_tab = pcall(require, "table.new")
if not ok or type(new_tab) ~= "function" then
    new_tab = function (narr, nrec) return {} end
end


local _M = new_tab(0, 55)

_M._VERSION = '0.28'


local common_cmds = {
    "get",      "set",          "mget",     "mset",
    "del",      "incr",         "decr",                 -- Strings
    "llen",     "lindex",       "lpop",     "lpush",
    "lrange",   "linsert",                              -- Lists
    "hexists",  "hget",         "hset",     "hmget",
    --[[ "hmset", ]]            "hdel",                 -- Hashes
    "smembers", "sismember",    "sadd",     "srem",
    "sdiff",    "sinter",       "sunion",               -- Sets
    "zrange",   "zrangebyscore", "zrank",   "zadd",
    "zrem",     "zincrby",                              -- Sorted Sets
    "auth",     "eval",         "expire",   "script",
    "sort"                                              -- Others
}


local sub_commands = {
    "subscribe", "psubscribe"
}


local unsub_commands = {
    "unsubscribe", "punsubscribe"
}


local mt = { __index = _M }


function _M.new(self)
    local sock, err = tcp()
    if not sock then
        return nil, err
    end
    return setmetatable({ _sock = sock, _subscribed = false }, mt)
end


function _M.set_timeout(self, timeout)
    local sock = rawget(self, "_sock")
    if not sock then
        error("not initialized", 2)
        return
    end

    sock:settimeout(timeout)
end


function _M.set_timeouts(self, connect_timeout, send_timeout, read_timeout)
    local sock = rawget(self, "_sock")
    if not sock then
        error("not initialized", 2)
        return
    end

    sock:settimeouts(connect_timeout, send_timeout, read_timeout)
end


function _M.connect(self, host, port_or_opts, opts)
    local sock = rawget(self, "_sock")
    if not sock then
        return nil, "not initialized"
    end

    local unix

    do
        local typ = type(host)
        if typ ~= "string" then
            error("bad argument #1 host: string expected, got " .. typ, 2)
        end

        if sub(host, 1, 5) == "unix:" then
            unix = true
        end

        if unix then
            typ = type(port_or_opts)
            if port_or_opts ~= nil and typ ~= "table" then
                error("bad argument #2 opts: nil or table expected, got " ..
                      typ, 2)
            end

        else
            typ = type(port_or_opts)
            if typ ~= "number" then
                port_or_opts = tonumber(port_or_opts)
                if port_or_opts == nil then
                    error("bad argument #2 port: number expected, got " ..
                          typ, 2)
                end
            end

            if opts ~= nil then
                typ = type(opts)
                if typ ~= "table" then
                    error("bad argument #3 opts: nil or table expected, got " ..
                          typ, 2)
                end
            end
        end

    end

    self._subscribed = false

    local ok, err

    if unix then
        ok, err = sock:connect(host, port_or_opts)
        opts = port_or_opts

    else
        ok, err = sock:connect(host, port_or_opts, opts)
    end

    if not ok then
        return ok, err
    end

    if opts and opts.ssl then
        ok, err = sock:sslhandshake(false, opts.server_name, opts.ssl_verify)
        if not ok then
            return ok, "failed to do ssl handshake: " .. err
        end
    end

    return ok, err
end


function _M.set_keepalive(self, ...)
    local sock = rawget(self, "_sock")
    if not sock then
        return nil, "not initialized"
    end

    if rawget(self, "_subscribed") then
        return nil, "subscribed state"
    end

    return sock:setkeepalive(...)
end


function _M.get_reused_times(self)
    local sock = rawget(self, "_sock")
    if not sock then
        return nil, "not initialized"
    end

    return sock:getreusedtimes()
end


local function close(self)
    local sock = rawget(self, "_sock")
    if not sock then
        return nil, "not initialized"
    end

    return sock:close()
end
_M.close = close


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


local function _gen_req(args)
    local nargs = #args

    local req = new_tab(nargs * 5 + 1, 0)
    req[1] = "*" .. nargs .. "\\r\\n"
    local nbits = 2

    for i = 1, nargs do
        local arg = args[i]
        if type(arg) ~= "string" then
            arg = tostring(arg)
        end

        req[nbits] = "$"
        req[nbits + 1] = #arg
        req[nbits + 2] = "\\r\\n"
        req[nbits + 3] = arg
        req[nbits + 4] = "\\r\\n"

        nbits = nbits + 5
    end

    -- it is much faster to do string concatenation on the C land
    -- in real world (large number of strings in the Lua VM)
    return req
end


local function _check_msg(self, res)
    return rawget(self, "_subscribed") and
        type(res) == "table" and res[1] == "message"
end


local function _do_cmd(self, ...)
    local args = {...}

    local sock = rawget(self, "_sock")
    if not sock then
        return nil, "not initialized"
    end

    local req = _gen_req(args)

    local reqs = rawget(self, "_reqs")
    if reqs then
        reqs[#reqs + 1] = req
        return
    end

    -- print("request: ", table.concat(req))

    local bytes, err = sock:send(req)
    if not bytes then
        return nil, err
    end

    local res, err = _read_reply(self, sock)
    while _check_msg(self, res) do
        if rawget(self, "_buffered_msg") == nil then
            self._buffered_msg = new_tab(1, 0)
        end

        tab_insert(self._buffered_msg, res)
        res, err = _read_reply(self, sock)
    end

    return res, err
end


local function _check_subscribed(self, res)
    if type(res) == "table"
       and (res[1] == "unsubscribe" or res[1] == "punsubscribe")
       and res[3] == 0
   then
        self._subscribed = false
        -- FIXME: support multiple subscriptions in the next PR
        self._buffered_msg = nil
    end
end


function _M.read_reply(self)
    local sock = rawget(self, "_sock")
    if not sock then
        return nil, "not initialized"
    end

    if not rawget(self, "_subscribed") then
        return nil, "not subscribed"
    end

    local buffered_msg = rawget(self, "_buffered_msg")
    if buffered_msg then
        local msg = buffered_msg[1]
        tab_remove(buffered_msg, 1)

        if #buffered_msg == 0 then
            self._buffered_msg = nil
        end

        return msg
    end

    local res, err = _read_reply(self, sock)
    _check_subscribed(self, res)

    return res, err
end


for i = 1, #common_cmds do
    local cmd = common_cmds[i]

    _M[cmd] =
        function (self, ...)
            return _do_cmd(self, cmd, ...)
        end
end


for i = 1, #sub_commands do
    local cmd = sub_commands[i]

    _M[cmd] =
        function (self, ...)
            self._subscribed = true
            return _do_cmd(self, cmd, ...)
        end
end


for i = 1, #unsub_commands do
    local cmd = unsub_commands[i]

    _M[cmd] =
        function (self, ...)
            local res, err = _do_cmd(self, cmd, ...)
            _check_subscribed(self, res)
            return res, err
        end
end


function _M.hmset(self, hashname, ...)
    if select('#', ...) == 1 then
        local t = select(1, ...)

        local n = 0
        for k, v in pairs(t) do
            n = n + 2
        end

        local array = new_tab(n, 0)

        local i = 0
        for k, v in pairs(t) do
            array[i + 1] = k
            array[i + 2] = v
            i = i + 2
        end
        -- print("key", hashname)
        return _do_cmd(self, "hmset", hashname, unpack(array))
    end

    -- backwards compatibility
    return _do_cmd(self, "hmset", hashname, ...)
end


function _M.init_pipeline(self, n)
    self._reqs = new_tab(n or 4, 0)
end


function _M.cancel_pipeline(self)
    self._reqs = nil
end


function _M.commit_pipeline(self)
    local reqs = rawget(self, "_reqs")
    if not reqs then
        return nil, "no pipeline"
    end

    self._reqs = nil

    local sock = rawget(self, "_sock")
    if not sock then
        return nil, "not initialized"
    end

    local bytes, err = sock:send(reqs)
    if not bytes then
        return nil, err
    end

    local nvals = 0
    local nreqs = #reqs
    local vals = new_tab(nreqs, 0)
    for i = 1, nreqs do
        local res, err = _read_reply(self, sock)
        if res then
            nvals = nvals + 1
            vals[nvals] = res

        elseif res == nil then
            if err == "timeout" then
                close(self)
            end
            return nil, err

        else
            -- be a valid redis error value
            nvals = nvals + 1
            vals[nvals] = {false, err}
        end
    end

    return vals
end


function _M.array_to_hash(self, t)
    local n = #t
    -- print("n = ", n)
    local h = new_tab(0, n / 2)
    for i = 1, n, 2 do
        h[t[i]] = t[i + 1]
    end
    return h
end


-- this method is deperate since we already do lazy method generation.
function _M.add_commands(...)
    local cmds = {...}
    for i = 1, #cmds do
        local cmd = cmds[i]
        _M[cmd] =
            function (self, ...)
                return _do_cmd(self, cmd, ...)
            end
    end
end


setmetatable(_M, {__index = function(self, cmd)
    local method =
        function (self, ...)
            return _do_cmd(self, cmd, ...)
        end

    -- cache the lazily generated method in our
    -- module table
    _M[cmd] = method
    return method
end})


return _M

install.sh内容(未使用此脚本安装可忽略)

[root@elk-master-node waf]# cat /opt/nginx1.14.2/conf/waf/install.sh   
mkdir -p /data/src  
cd /data/src  
if [ ! -x "LuaJIT-2.0.0.tar.gz" ]; then   
wget http://luajit.org/download/LuaJIT-2.0.0.tar.gz  
fi  
tar zxvf LuaJIT-2.0.0.tar.gz  
cd LuaJIT-2.0.0  
make  
make install PREFIX=/usr/local/lj2  
ln -s /usr/local/lj2/lib/libluajit-5.1.so.2 /lib64/  
cd /data/src  
if [ ! -x "v0.2.17rc2.zip" ]; then   
wget https://github.com/simpl/ngx_devel_kit/archive/v0.2.17rc2.zip  
fi  
unzip v0.2.17rc2  
if [ ! -x "v0.7.4.zip" ]; then   
wget https://github.com/chaoslawful/lua-nginx-module/archive/v0.7.4.zip  
fi  
unzip v0.7.4  
cd /data/src  
if [ ! -x "pcre-8.10.tar.gz" ]; then  
wget http://blog.s135.com/soft/linux/nginx_php/pcre/pcre-8.10.tar.gz  
fi  
tar zxvf pcre-8.10.tar.gz  
cd pcre-8.10/  
./configure  
make && make install  
cd ..  
if [ ! -x "nginx-1.2.4.tar.gz" ]; then  
wget 'http://nginx.org/download/nginx-1.2.4.tar.gz'  
fi  
tar -xzvf nginx-1.2.4.tar.gz  
cd nginx-1.2.4/  
export LUAJIT_LIB=/usr/local/lj2/lib/  
export LUAJIT_INC=/usr/local/lj2/include/luajit-2.0/  
./configure --user=daemon --group=daemon --prefix=/usr/local/nginx/ --with-http_stub_status_module --with-http_sub_module --with-http_gzip_static_module --without-mail_pop3_module --without-m  
ail_imap_module --without-mail_smtp_module --add-module=../ngx_devel_kit-0.2.17rc2/ --add-module=../lua-nginx-module-0.7.4/make -j8  
make install   
#rm -rf /data/src  
cd /usr/local/nginx/conf/  
wget https://github.com/loveshell/ngx_lua_waf/archive/master.zip --no-check-certificate  
unzip master.zip  
mv ngx_lua_waf-master/* /usr/local/nginx/conf/  
rm -rf ngx_lua_waf-master  
rm -rf /data/src  
mkdir -p /data/logs/hack  
chmod -R 775 /data/logs/hack  

wafconf文件夹内的文件内容均可自定义样本只做参考有些可能无用

    args里面的规则get参数进行过滤的
    url是只在get请求url过滤的规则        
    post是只在post请求过滤的规则        
    whiteip是白名单
    blockip是黑名单
    user-agent是对user-agent的过滤规则
     denycc --无作用 
     returnhtml --被拦截后的提示页面(HTML) 
     returnhtml1 --被拦截后的提示页面(HTML)
     cookie --Cookie拦截规则 
      whiteurl --白名单网址 
      white-user-agent--UA白名单
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/white-user-agent 
(baidu)
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/blockip 
10.0.68.75
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/args 
\\.\\./
\\:\\$
\\$\\{
/\*|--
\\b(or|xor|and)\\b.*(=|<|>|'|")
select.+(from|limit)
(?:(union(.*?)select))
having|load_file|rongjitest
sleep\\((\\s*)(\\d*)(\\s*)\\)
benchmark\\((.*)\\,(.*)\\)
base64_decode\\(
(?:from\\W+information_schema\\W)
(?:(?:current_)user|database|schema|connection_id)\\s*\\(
(?:etc\\/\\W*passwd)
into(\\s+)+(?:dump|out)file\\s*
group\\s+by.+\\(
xwork.MethodAccessor
(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(
xwork\\.MethodAccessor
(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/
java\\.lang
\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[
\\<(iframe|script|body|img|layer|div|meta|style|base|object|input)
(onmouseover|onerror|onload)\\=
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/cookie 
\\.\\./
\\:\\$
\\$\\{
select.+(from|limit)
(?:(union(.*?)select))
having|rongjitest
sleep\\((\\s*)(\\d*)(\\s*)\\)
benchmark\\((.*)\\,(.*)\\)
base64_decode\\(
(?:from\\W+information_schema\\W)
(?:(?:current_)user|database|schema|connection_id)\\s*\\(
(?:etc\\/\\W*passwd)
into(\\s+)+(?:dump|out)file\\s*
group\\s+by.+\\(
xwork.MethodAccessor
(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(
xwork\\.MethodAccessor
(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/
java\\.lang
\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/denycc 
#ip 60/60 1800
#ip+uri 60/60 1800
#ip+domain+CookieParam:sessionid  60/60 1800
#ip+GetParam:userid 60/60 1800
#ip+PostParam:userid 60/60 1800
#$ip+header:imei 30/60  1800
ip+uri 60/60  3600
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/post 
select.+(from|limit)
(?:(union(.*?)select))
\\b(or|xor|and)\\b.*(=|<|>|'|")
having|load_file|rongjitest
sleep\\((\\s*)(\\d*)(\\s*)\\)
benchmark\\((.*)\\,(.*)\\)
base64_decode\\(
(?:from\\W+information_schema\\W)
(?:(?:current_)user|database|schema|connection_id)\\s*\\(
(?:etc\\/\\W*passwd)
into(\\s+)+(?:dump|out)file\\s*
group\\s+by.+\\(
xwork.MethodAccessor
(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(
xwork\\.MethodAccessor
(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/
java\\.lang
\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[
\\<(iframe|script|body|img|layer|div|meta|style|base|object|input)
(onmouseover|onerror|onload)\\=
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/user-agent 
(HTTrack|Apache-HttpClient|harvest|audit|dirbuster|pangolin|nmap|sqln|-scan|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|PycURL|zmeu|BabyKrokodil|netsparker|httperf|bench| SF/)
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/whiteurl 
^/phpmyadmin_
^/123$
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/url 
\\.(svn|htaccess|mysql_history|bash_history|git|DS_Store|idea|user\\.ini)
\\.(bak|inc|old|mdb|sh|sql|backup|php~|swp|java|class)$
(vhost|bbs|host|wwwroot|www|site|root|hytop|flashfxp|backup|data|ftp|db|admin|website|web).*\\.(rar|sql|zip|tar\\.gz|tar)
(phpmyadmin|elastic|jmx-console|jmxinvokerservlet)
java\\.lang
/CSV/
/(hack|shell|spy|phpspy)\\.php$
(manager|host-manager)/html$
/(attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|forumdata|upload|includes|cache|avatar)/(\\\\w+).(php|jsp)
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/whiteip 
127.0.0.1
^192\\.168\\.
8.8.8.8
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/returnhtml
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>网站防火墙</title>
<style>
*{margin:0;padding:0;color:#444}
body{font-size:14px;font-family:"宋体"}
.main{width:600px;margin:10% auto;}
.title{background: #20a53a;color: #fff;font-size: 16px;height: 40px;line-height: 40px;padding-left: 20px;}
.content{background-color:#f3f7f9; height:280px;border:1px dashed #c6d9b6;padding:20px}
.t1{border-bottom: 1px dashed #c6d9b6;color: #ff4000;font-weight: bold; margin: 0 0 20px; padding-bottom: 18px;}
.t2{margin-bottom:8px; font-weight:bold}
ol{margin:0 0 20px 22px;padding:0;}
ol li{line-height:30px}
</style>
</head>

<body>
    <div class="main">
        <div class="title">网站防火墙</div>
        <div class="content">
            <p class="t1">您的请求带有不合法参数,已被网站管理员设置拦截!</p>
            <p class="t2">可能原因:</p>
            <ol>
                <li>您提交的内容包含危险的攻击请求</li>
            </ol>
            <p class="t2">如何解决:</p>
            <ol>
                <li>检查提交内容;</li>
                <li>如网站托管,请联系空间提供商;</li>
                <li>普通网站访客,请联系网站管理员;</li>
                <li>这是误报,请联系dream_653@163.com</li>
            </ol>
        </div>
    </div>
</body>
</html>
[root@elk-master-node wafconf]# cat /opt/nginx1.14.2/conf/waf/wafconf/returnhtml1
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>网站防火墙</title>
<style>
*{margin:0;padding:0;color:#444}
body{font-size:14px;font-family:"宋体"}
.main{width:600px;margin:10% auto;}
.title{background: #20a53a;color: #fff;font-size: 16px;height: 40px;line-height: 40px;padding-left: 20px;}
.content{background-color:#f3f7f9; height:280px;border:1px dashed #c6d9b6;padding:20px}
.t1{border-bottom: 1px dashed #c6d9b6;color: #ff4000;font-weight: bold; margin: 0 0 20px; padding-bottom: 18px;}
.t2{margin-bottom:8px; font-weight:bold}
ol{margin:0 0 20px 22px;padding:0;}
ol li{line-height:30px}
</style>
</head>

<body>
    <div class="main">
        <div class="title">网站防火墙</div>
        <div class="content">
            <p class="t1">您的请求过于频繁,已被网站管理员设置拦截!</p>
            <p class="t2">可能原因:</p>
            <ol>
                <li>您提交的内容包含危险的攻击请求</li>
            </ol>
            <p class="t2">如何解决:</p>
            <ol>
                <li>检查提交内容;</li>
                <li>如网站托管,请联系空间提供商;</li>
                <li>普通网站访客,请联系网站管理员;</li>
                <li>这是误报,请联系dream_653@163.com-</li>
            </ol>
        </div>
    </div>
</body>
</html>

最后安装redis

1、检查是否有redis yum 源

yum install redis

2、下载fedora的epel仓库

yum install epel-release 

3、安装redis数据库

yum install redis 

4、安装完毕后,使用下面的命令启动redis服务

# 启动redisservice redis start
# 停止redisservice redis stop
# 查看redis运行状态service redis status
# 查看redis进程ps -ef | grep redis 

5、设置redis为开机自动启动

chkconfig redis on 

6、进入redis服务

# 进入本机redis redis-cli
# 列出所有key keys *

7.非本机链接redis需要配置

redis的配置文件bind的ip为192.168.110.132

8.redis集群参考#### Linux部署Redis5.07集群
9.redis常用命令

创建一条测试 数据 查询 (默认是 DB 0 )**
创建:set name xiaoming
查询:get name 

1、模糊搜索查询 ( redis 默认有16个DB , 0-15 )

Redis 模糊搜索  
1、keys * 匹配数据库中所有 key   
2、keys h?llo 匹配 hello , hallo 和 hxllo 等。  
3、keys h*llo 匹配 hllo 和 heeello 等。  
4、keys h[ae]llo 匹配 hallo 和 hello ,但不匹配 hillo;特殊符号用 \\ 隔开。  
redis> keys *o*  
1) "four"  
2) "two"  
3) "one"  
redis> keys t??  
1) "two"  
redis> keys t[w]*  
1) "two"  
redis> keys * # 匹配数据库内所有 key  
1) "four"  
2) "three"  
3) "two"  
4) "one"  
  
redis-cli 进入默认是第一个DB 0 ; select 切换 DB   
> select 2;

2、删除指定key :

# 删除所有以 user 开头的key 可以这样实现:  
# redis-cli keys "user*"  
1) "user1"  
2) "user2"  
  
# redis-cli keys "user*" | xargs redis-cli del  
(integer) 2  
# 删除成功  
  
# 删除当前数据库中的所有Key   
> flushdb   
  
# 删除所有数据库中的key   
> flushall  
  
# 删除单个 key  
redis> SET name zhangsan  
OK  
redis> DEL name  
(integer) 1  
  
# 删除一个不存在的 key  
redis> EXISTS lisi  
(integer) 0  
  
redis> DEL phone # 失败,没有 key 被删除  
(integer) 0  
  
# 同时删除多个 key  
redis> SET name "redis"  
OK  
redis> SET type "key-value store"  
OK  
redis> SET website "redis.com"  
OK  
redis> DEL name type website  
(integer) 3
# 批量删除匹配通配符的key用到了Linux中的管道和xargs参数:
redis-cli keys "s*" | xargs redis-cli del
# 如果需要制定数据库,需要用到 -n 数据库编号 参数,下面是删除 2数据库中 s开头的键:
redis-cli -n 2 keys "s*" | xargs redis-cli -n 2 del  
redis-cli keys "*" | xargs redis-cli del   
# 如果redis-cli没有设置成系统变量,需要指定redis-cli的完整路径   
如:  
/opt/redis/redis-cli keys "*" | xargs /opt/redis/redis-cli del

3、Redis Sortedset 数据查询

redis sortedset 数据查询:  
  
172.16.12.36:6003> zrank qa:hall 103228953392713728  
(integer) 10021  
  
172.16.12.36:6003> ZCARD qa:hall  
(integer) 10022

TTL key : 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)

    返回值:
    当 key 不存在时,返回 -2 。
    当 key 存在但没有设置剩余生存时间时,返回 -1 。
    否则,以秒为单位,返回 key 的剩余生存时间。
    在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。

查询检测 ttl 值:

# 不存在的 key  
redis> FLUSHDB  
OK  
redis> TTL key  
(integer) -2  
  
# key 存在,但没有设置剩余生存时间  
redis> SET key value  
OK  
redis> TTL key  
(integer) -1  
  
# 有剩余生存时间的 key  
redis> EXPIRE key 10086  
(integer) 1  
  
redis> TTL key  
(integer) 10010

5、redis type key
TYPE key : 返回 key 所储存的值的类型.

返回值:  
none (key不存在)  
string (字符串)  
list (列表)  
set (集合)  
zset (有序集)  
hash (哈希表)

示例:

# 字符串  
redis> SET weather "sunny"  
OK  
redis> TYPE weather  
string  
  
# 列表  
redis> LPUSH book_list "programming in scala"  
(integer) 1  
redis> TYPE book_list  
list  
  
# 集合  
redis> SADD pat "dog"  
(integer) 1  
redis> TYPE pat  
set

Nginx 使用两种防火墙

方案一:#### Nginx防火墙自动封禁解封IP

使用 nginx 共享内存,被墙的 ip 在内存中保存到达指定时间自动释放 如果想提前释放需要重启 nginx

现在是 100 秒 250 次 被强 500 秒

添加 ip 白名单后访问

特殊 url 被禁止

添加 ip 白名单后访问

特殊 user-agent 禁止


添加 ip 白名单后

方案二 (本文Nginx防火墙自动封禁解封IP(添加redis版本))

在方案一基础上添加 redis 被临时禁用 ip 写入 redis 照方案一比较性能会差一些但可手动释放被强 ip 无需重启,另外优化了拦截页面


可能出现的问题:

  1. 服务之间调用可能被误墙

2.内网频繁访问刷新可能被墙
3.指定时间访问次数有可能设置较小
4.性能问题
5.方案一和方案二均需要消耗 nginx 资源比直接使用 ip.deny 要大
6.方案一如果 nginx 为主主 DNS 使用的轮询 可能会出现被强与正常之间切换
7.方案二如果 nginx 为主主 为了高可用 redis 需要搭建集群

参考文档:
https://www.cnblogs.com/sharesdk/p/9203449.html
http://www.linuxeye.com/security/nginx-lua-redis-waf.html
https://github.com/openresty/lua-resty-redis
https://www.jianshu.com/p/2f83e5aabe03
https://www.2kss.com/38234.html
https://wpbox.cc/1332.html
https://blog.csdn.net/u014756339/article/details/90288473
https://zhih.me/ngx-lua-waf/
https://www.cnblogs.com/ph7seven/p/9941189.html
测试软件Firefox插件# simple-modify-headers

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

1

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广