宝塔面板Nginx的Lua-Waf防火墙终极改进 动态封禁IP

宝塔面板自带的Nginx防火墙有些鸡肋,对于大量的恶意攻击只能临时拦截,而不能封禁IP。

延伸阅读:Oneinstack一键开启安装ngx_lua_waf防火墙功能

下面的修改可以帮你做到,频繁CC攻击,频繁的漏洞扫描,同一个IP段轮流攻击,使用了CDN进行攻击,都可以全部封禁。

宝塔面板Nginx的Lua-Waf防火墙终极改进 动态封禁IP1域名主机主机格调

宝塔面板的nginx修改/www/server/nginx/waf/目录下的三个文件即可,如果没有宝塔面板,nginx必须安装Lua,然后对下面的代码稍加修改,并且自己加上正则黑名单(或者下载个宝塔面板把规则文件拷出来)也可以正常使用。

代码:config.lua

RulePath = "/www/server/panel/vhost/wafconf/"    --规则文件夹
attacklog="on"
logdir = "/www/wwwlogs/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
【声明1】:如本站转载别的站的文章,我个人没有添加来源,您可以发电邮:admin#zhuji.gd 提醒我,我会尽快添加文章来源。 【声明2】:本博客不参与任何交易及中介服务,只记录 VPS 测评和优惠,内容均不作直接、间接、法定、约定的保证。访问本博客请务必遵守有关互联网的相关法律、规定与规则。一旦您访问本博客,即表示您已经知晓并接受了此声明通告。
(0)
上一篇 11/04/2019 17:08
下一篇 27/04/2019 10:30

相关推荐

  • 八个HTTPS和SSL优化使用心得-减少等待时间和降低Https性能损耗

    HTTPS SSL现在基本上是建站标配了。 得益于Let’s Encrypt、Digicert、TrustAsia、Symantec等提供的免费SSL证书,现在不管是个人建站还是企业建站,上Https的成本可以忽略不计了。 为了安全,我们要上Https,但是开启 SSL 会增加内存、CPU、网络带宽的开销。 http使用TCP 三次握手建立连接,客户端和服务器需要交换3个包。 https除了 TCP 的三个包,还要加上 ssl握手需要的9个包,一共是12个包。所以,HTTPS优化得不好,反而容易...

    01/04/2019
    13.2K0
  • 主机推荐:WOPUS IDC 也许是最靠谱的虚拟主机

    最近把我手里的三个站全部从Linode迁移到了酋长的WOPUS IDC。再也不想折腾了。 想找个靠谱,省心的主机非常难。 很多人刚开始就想用VPS,可是小白根本驾驭不了,没什么问题的时候,还好,要是有问题,轻者弄个焦头烂额,重者数据丢失得不偿失。 [ad] 如果这样,保险起见,还不如退其次,用虚拟主机,稳定,不用折腾,可以用心在内容上。 国内的虚拟主机商比如老薛主机、衡天主机、Wopus IDC,还有其他的一些主机。就不一一介绍...

    17/03/2019
    12.1K0
  • 如何查看 VPS 的去程和回程

    使用BestTrace来查看VPS的去程和回程,可以了解 VPS 线路是 CN2 还是 CN2 GIA ? CN2线路路由表格 [template id="2812"] 如何查看去程? 去程就是本地到搬瓦工VPS的路由。 下载BestTrace软件,打开软件选择路由跟踪,输入VPS IP地址,选择谷歌国际地图,可以形象地看到去程,一目了然。 在移动网络,CN2线路的去程显示移动自有节点。 如何查看回程? 回程是搬瓦工VPS到本地的路由。只要看到59.43 开头的IP段,就可以断定是CN2线...

    16/02/2020
    15.7K0
  • NameCheap美国 .US 域名注册优惠,首年1.88美元

    今天看到美国域名注册商 NameCheap 放出 .US 域名注册特价优惠,首年付 1.88 美元,续费恢复原价,原价 8.48 美元,折扣力度比较大,如果有 .US 域名需求的同学可以考虑,除了首年优惠,还赠送两个月的电子邮件试用。 截止日期:2023年7月4日0点。注册地址:.US 域名注册 为什么注册 .US 域名? .US 域名除了美国国别区分, .US 域名还会被看成英文 US “我们”的意思,通过单词组合来使用。 .US 域名注意事项 .US 域名注册要求注...

    01/07/2023
    2690
  • 基于CentOS系统的VPS安全设置与优化

    本文所有代码基于CentOS 6.4操作系统为例进行说明,于6.x版本应该都是适用的,其他版本的话主要是命令的路径不同,思路是一致的。本文也可以称为:CentOS操作系统全设置与优化。安装好CentOS系统后,建议不要急着去做这些安装设置和优化,因为过早操作,会在Web环境搭建(特别是用主机控制面板的)过程当中因为早过禁止某些权限和程序而造成问题。所以这些安全设置和优化,建议最后才来操作。 第一步、账户安全管理 1. 修改密码...

    06/03/2019
    620
  • 宝塔面板安装memcached详细配置教程

    宝塔面板安装memcached后可以给服务器提速,进而加快网站访问速度,提高用户体验。很多朋友不知道如何操作,本文中魏艾斯博客分享通过宝塔面板安装memcached及详细配置方法,希望对你有用。在前面的文章中多次介绍过宝塔面板安装memcached的过程,只是没有写的很详细,对于新手来说帮不上忙。这次老魏就详细的记录下来整个过程。 安装memcached拓展 以php7.0为例,在宝塔面板的“软件管理”>运行环境中往下拉找到php7.0,点击...

    14/03/2019
    12.1K0
  • OneinStack如何启用 Brotli 压缩

    Brotli是由谷歌开发的压缩算法,与其他压缩算法相比,它有着更高的压缩效率。 一般来说我们的VPS主机已经默认开启了GZIP压缩了,而Brotli与GZIP可以同时共存,当同时开启两种压缩算法时,Brotli 压缩等级优先级高于 Gzip。 OneinStack一键脚本开启方法挺简单的,修改一下 OneinStack 的 nginx 升级脚本,下载 Brotli,然后升级 nginx,修改一下配置,重启 nginx 即可。 下载 Brotli cd oneinstack/src git clone https://github...

    31/03/2019
    14.0K0
  • 最便宜域名注册商 Porkbun 促销 .us后缀域名 首年注册免费

    about.us 与国外知名域名注册商 Porkbun 联合推出.us后缀域名首年免费注册活动。不过 .us后缀需要美国公民才可以注册,所以大家注册时候,需要填写美国公民信息,不要瞎写,有一定概率被抽查到。 活动链接:https://www.about.us/startup/ 如何生成美国身份可以到以下几个网站: 全套 http://www.fakenamegenerator.com/index.php http://www.haoweichi.com/Index/random http://www.fakeaddressgenerator.com/ http://www.getn...

    13/01/2021
    5.9K0
  • Gandi.net新注册.COM域名只需7元/首年带免费Whois隐私保护

    Gandi.net成立于1999年,主要从事域名注册、虚拟主机等建站相关的服务。Gandi.net为 ICANN 最早认可的域名注册商之一,主要提供 .COM、.NET、.ORG、.BIZ、.INFO、.NAME、.BE、.FR、.EU 域名。 Gandi 自许为具有商业道德的域名注册商,不但重视客户的权益和隐私胜于一切,并尽己所能以捍卫这些价值。 Gandi.net目前管理接近 2,300,000 个域名遍布 192 个国家,成为法国名列第一、在欧洲排名第六、在全球排名前 15 的域名注册商。...

    22/10/2019
    14.8K0
  • 如何注册解析.tk,.ml,.ga,.cf,.gq等免费域名

    免费域名对于搭建WordPress博客(如果是不注重SEO的话)也是一个不错的选择。目前免费顶级域名有.tk,.ml,.ga,.cf,.gq域名后缀。 虽然在很长一段时间里,这些免费顶级域名都被用烂了,但是因为免费,还是有很多童鞋在用来做为测试站使用。并且有专门的免费顶级域名网站Freenom,大家可以在上面申请。 那么要如何在Freenom注册.tk、.ml、.ga、.cf、.gq免费域名,以及如何设置DNS域名解析。 .tk、.ml、.ga、.cf、.gq免费域名目前可...

    26/03/2019
    15.2K0
返回顶部