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
[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后拷贝过来进行的整改
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>
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 无需重启,另外优化了拦截页面
可能出现的问题:
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 条评论