dream_653
作者dream_653·2020-04-18 10:34
系统应用运维·*****

Nginx防火墙自动封禁解封IP

字数 13766阅读 1411评论 0赞 0

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
 
 cat > /opt/nginx1.14.2/conf/waf.conf << EOF  
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";  
EOF

mkdir -p /opt/nginx1.14.2/logs/waf/
chown www.www /opt/nginx1.14.2/logs/waf  注:www.www 用户和组参照nginx.conf中user

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

include waf.conf; 

动态封禁IP

下面的修改可以帮你做到,频繁CC攻击,频繁的漏洞扫描,同一个IP段轮流攻击,使用了CDN进行攻击,都可以全部封禁。(宝塔nginx控制面板规则文件)
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={}
ipBlocklist={}
CCDeny="on"
CCrate="500/100"    --这个是CC攻击的几秒钟允许请求几次

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 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 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')
html=read_rule('returnhtml')
 
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 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"
         ngx.status = ngx.HTTP_FORBIDDEN
                 ngx.say("老哥你手速也忒快了吧,要不休息"..CCcount.."秒?")
                 ngx.exit(ngx.status)
                 return true
            else
                 limit:incr(token,1)
            end
        else
            limit:set(token,1,CCseconds)
        end
    end
    return false
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
        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
         for _,ip in pairs(ipBlocklist) do
             if getClientIp()==ip then
                 ngx.exit(444)
                 return true
             end
         end
     end
         return false
end

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

第二台nginx部署相同配置

scp nginx 192.168.110.128:/opt/nginx1.14.2/sbin/  
scp nginx.conf 192.168.110.128:/opt/nginx1.14.2/conf/nginx.conf
scp -r waf 192.168.110.128:/opt/nginx1.14.2/conf/  
scp -r /usr/local/lib/* 192.168.110.128:/usr/local/lib/  
scp waf.conf 192.168.110.128:/opt/nginx1.14.2/conf/
 echo '/usr/local/lib' >> /etc/ld.so.conf.d/local.conf  
ldconfig

扩展:

    # 获取客户端ip
    location /myip {
        default_type 'text/plain';
        content_by_lua '
            clientIP = ngx.req.get_headers()["x_forwarded_for"]
            ngx.say("IP:",clientIP) ';
    }

拦截提示如果中文乱码修改init.lua

ngx.header.content_type = "text/html;charset=utf8"

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

0

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广