pull/136/merge
weakestan 2018-08-07 06:45:48 +00:00 committed by GitHub
commit 8d4aaef8f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 789 additions and 25 deletions

View File

@ -1,3 +1,34 @@
水平太烂,只能在原基础上改改。
1.原cc功能有点弱改进后可以提高对攻击和普通访问的限制
默认同ip触发规则攻击超过10次/5秒,限制该ip访问nginx服务器1800秒。
同1 ip访问同1地址30次/60秒限制访问该地址30秒。
2.日志主要是用于syslog服务器能力。
1.--保存日志到syslog,可以用nginx设置的日志服务器保存日志。
logtoserver = "on"
2.--通过加载socket.lua同时使用其他日志服务器默认关闭。这个有点粗糙有需要的自己改
loghack="off"
3.修改whiteurl可以针对域名设置白名单。
--whiteurl start
site:^www.baidu.com/whiteurl/
--end
------------------分割线-------------------------------------------------
---------------lovshell记录----------------------------------------------
##ngx_lua_waf
ngx_lua_waf是我刚入职趣游时候开发的一个基于ngx_lua的web应用防火墙。
@ -136,3 +167,51 @@ nginx安装路径假设为:/usr/local/nginx/conf/
</table>
感谢ngx_lua模块的开发者[@agentzh](https://github.com/agentzh/),春哥是我所接触过开源精神最好的人
syntax: captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)
syntax: from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)
Specify options to control how the match operation will be performed. The following option characters are supported:
a anchored mode (only match from the beginning)
d enable the DFA mode (or the longest token match semantics).
this requires PCRE 6.0+ or else a Lua exception will be thrown.
first introduced in ngx_lua v0.3.1rc30.
D enable duplicate named pattern support. This allows named
subpattern names to be repeated, returning the captures in
an array-like Lua table. for example,
local m = ngx.re.match("hello, world",
"(?<named>\w+), (?<named>\w+)",
"D")
-- m["named"] == {"hello", "world"}
this option was first introduced in the v0.7.14 release.
this option requires at least PCRE 8.12.
i case insensitive mode (similar to Perl's /i modifier)
j enable PCRE JIT compilation, this requires PCRE 8.21+ which
must be built with the --enable-jit option. for optimum performance,
this option should always be used together with the 'o' option.
first introduced in ngx_lua v0.3.1rc30.
J enable the PCRE Javascript compatible mode. this option was
first introduced in the v0.7.14 release. this option requires
at least PCRE 8.12.
m multi-line mode (similar to Perl's /m modifier)
o compile-once mode (similar to Perl's /o modifier),
to enable the worker-process-level compiled-regex cache
s single-line mode (similar to Perl's /s modifier)
u UTF-8 mode. this requires PCRE to be built with
the --enable-utf8 option or else a Lua exception will be thrown.
U similar to "u" but disables PCRE's UTF-8 validity check on
the subject string. first introduced in ngx_lua v0.8.1.
x extended mode (similar to Perl's /x modifier)

View File

@ -1,23 +1,35 @@
RulePath = "/usr/local/nginx/conf/waf/wafconf/"
RulePath = "/etc/nginx/waf/wafconf/"
attacklog = "on"
logdir = "/usr/local/nginx/logs/hack/"
--保存日志到文件
logtofile = "off"
logdir = "/var/log/nginx/"
--保存日志到syslog,采用nginx设置
logtoserver = "on"
--通过syslog日志方式提交hack_ip记录到日志服务器
loghack="off"
------------
UrlDeny="on"
Redirect="on"
CookieMatch="on"
postMatch="on"
whiteModule="on"
black_fileExt={"php","jsp"}
ipWhitelist={"127.0.0.1"}
ipWhitelist={"127.0.0.1","192.168.2.1"}
ipBlocklist={"1.0.0.1"}
CCDeny="off"
CCrate="100/60"
--违规ip登记是否限制访问。
--hackrate超过10次/5秒,限制访问1800秒。
hackipdeny="on"
hackrate="10/60/1800"
--cc攻击防范
CCDeny="on"
CCrate="30/60/30"
html=[[
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<style>
p {
line-height:20px;
line-height:20px;
}
ul{ list-style-type:none;}
li{ list-style-type:none;}

143
init.lua
View File

@ -1,19 +1,52 @@
require 'config'
local match = string.match
local ngxmatch=ngx.re.match
--ngx_lua如果是0.9.2以上版本建议正则过滤函数改为ngx.re.find匹配效率会提高三倍左右。
--因nginx和lua一起的关系正则表达式使用\d\w\s会出问题
--local ngxmatch=ngx.re.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
loghack=optionIsOn(loghack)
--载入socket.lua用于发送log到独立syslog服务器。
local logger = require "socket"
if loghack then
if not logger.initted() then
local ok, err = logger.init{
--host = '192.168.0.1',
host = 'logserver.local',
port = 514,
sock_type = "udp", --udp协议
flush_limit = 1, --立即发送
--drop_limit = 5678,
pool_size = 100,--连接池大小
}
if not ok then
ngx.log(ngx.ERR, "failed to initialize the logger: ",
err)
return
end
end
end
logpath = logdir
rulepath = RulePath
logtofile = optionIsOn(logtofile)
logtoserver = optionIsOn(logtoserver)
UrlDeny = optionIsOn(UrlDeny)
PostCheck = optionIsOn(postMatch)
CookieCheck = optionIsOn(cookieMatch)
WhiteCheck = optionIsOn(whiteModule)
PathInfoFix = optionIsOn(PathInfoFix)
attacklog = optionIsOn(attacklog)
hackipdeny = optionIsOn(hackipdeny)
CCDeny = optionIsOn(CCDeny)
Redirect=optionIsOn(Redirect)
local file = io.open('config')
function getClientIp()
IP = ngx.var.remote_addr
if IP == nil then
@ -28,19 +61,38 @@ function write(logfile,msg)
fd:flush()
fd:close()
end
function swrite(msg)
--保存警告等级要高于nginx error_log的默认等级。
ngx.log(ngx.CRIT,msg)
end
function log(method,url,data,ruletag)
if attacklog then
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"
if ua == nil then
ua="null"
end
local servername=ngx.var.host
local time=ngx.localtime()
if logtofile then
local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log"
line=realIp.." ["..time.."]".."\""..method.." "..servername..url.."\""..data.."\""..ua.."\""..ruletag.."\"".."\n"
write(filename,line)
end
if logtoserver then
line=realIp.."\""..method.." "..servername..url.."\""..data.."\""..ua.."\""..ruletag.."\""
--line="lua_waf:"..line
swrite(line)
end
--发送ip到独立syslog服务器。
if loghack then local bytes, err = logger.log(getClientIp()) end
--只要log记录说明被攻击利用denyhackip将ip记录。
if hackipdeny then denyhackip(0) end
end
end
------------------------------------规则读取函数-------------------------------------------------------------------
@ -78,21 +130,35 @@ function whiteurl()
if WhiteCheck then
if wturlrules ~=nil then
for _,rule in pairs(wturlrules) do
if ngxmatch(ngx.var.uri,rule,"isjo") then
--针对site:开始的进行域名匹配。增加白名单用处。
local sitemod,_=string.find(rule,"site:")
if sitemod==1 then
rule=string.gsub(rule,"site:","",1)
--调试whiteurl
--if ngx.var.host=='domino.cqhrss.gov.cn' then
-- log('debug',ngx.var.uri,"",rule)
--end
if ngxmatch(ngx.var.host..ngx.var.uri,rule,"isjo") then
return true
end
end
else
if ngxmatch(ngx.var.uri,rule,"isjo") then
return true
end
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)
for rule,_ in pairs(items) do
if ngxmatch(ext,rule,"isjo") then
log('POST',ngx.var.request_uri,"-","file attack with ext "..ext)
say_html()
end
end
@ -184,17 +250,25 @@ end
function denycc()
if CCDeny then
local uri=ngx.var.uri
CCcount=tonumber(string.match(CCrate,'(.*)/'))
CCseconds=tonumber(string.match(CCrate,'/(.*)'))
local m, err = ngx.re.match(CCrate,'([0-9]+)/([0-9]+)/([0-9]+)')
local CCcount=tonumber(m[1]) --计数器上限
local CCseconds=tonumber(m[2]) --计时器
local CClimits=tonumber(m[3]) --阻止访问时间
local token = getClientIp()..uri
local limit = ngx.shared.limit
local req,_=limit:get(token)
local req,_=limit:get(token) --计数器当前值
if req then
if req > CCcount then
ngx.exit(503)
ngx.exit(404)
return true
else
limit:incr(token,1)
if req == CCcount then limit:set(token,CCcount+1,CClimits) end
limit:incr(token,1)
--调试在syslog日志中查看
--swrite('计数器:'..token..'当前计数器'..req..'阻止访问时间:'..CClimits)
end
else
limit:set(token,1,CCseconds)
@ -203,6 +277,41 @@ function denycc()
return false
end
--chk为1表示检测值不增加不创建返回检测结果。
function denyhackip(chk)
if hackipdeny then
local m, err = ngx.re.match(hackrate,'([0-9]+)/([0-9]+)/([0-9]+)')
local hicount=tonumber(m[1]) --计数器上限
local hiseconds=tonumber(m[2]) --计时器
local hilimits=tonumber(m[3]) --阻止访问时间
local token = "hackip"..getClientIp()
local limit = ngx.shared.limit
local req,_=limit:get(token) --计数器当前值
if req then
if req > hicount then
ngx.exit(404)
return true
else
if req == hicount then
limit:set(token,hicount+1,hilimits)
swrite("ip:"..getClientIp().."因攻击被暂停访问"..hilimits.."秒。")
end
if chk ~=1 then limit:incr(token,1) end
--调试在syslog日志中查看
--swrite("计数器:"..token.."检测状态:"..chk.."当前计数器"..req.."阻止访问时间:"..hilimits)
end
else
if chk ~=1 then limit:set(token,1,hiseconds) end
end
end
return false
end
function get_boundary()
local header = get_headers()["content-type"]
if not header then

558
socket.lua Normal file
View File

@ -0,0 +1,558 @@
-- Copyright (C) 2013-2014 Jiale Zhi (calio), CloudFlare Inc.
--require "luacov"
local concat = table.concat
local tcp = ngx.socket.tcp
local udp = ngx.socket.udp
local timer_at = ngx.timer.at
local ngx_log = ngx.log
local ngx_sleep = ngx.sleep
local type = type
local pairs = pairs
local tostring = tostring
local debug = ngx.config.debug
local DEBUG = ngx.DEBUG
local CRIT = ngx.CRIT
local MAX_PORT = 65535
-- table.new(narr, nrec)
local succ, new_tab = pcall(require, "table.new")
if not succ then
new_tab = function () return {} end
end
local _M = new_tab(0, 5)
local is_exiting
if not ngx.config or not ngx.config.ngx_lua_version
or ngx.config.ngx_lua_version < 9003 then
is_exiting = function() return false end
ngx_log(CRIT, "We strongly recommend you to update your ngx_lua module to "
.. "0.9.3 or above. lua-resty-logger-socket will lose some log "
.. "messages when Nginx reloads if it works with ngx_lua module "
.. "below 0.9.3")
else
is_exiting = ngx.worker.exiting
end
_M._VERSION = '0.03'
-- user config
local flush_limit = 4096 -- 4KB
local drop_limit = 1048576 -- 1MB
local timeout = 1000 -- 1 sec
local host
local port
local ssl = false
local ssl_verify = true
local sni_host
local path
local max_buffer_reuse = 10000 -- reuse buffer for at most 10000
-- times
local periodic_flush = nil
local need_periodic_flush = nil
local sock_type = 'tcp'
-- internal variables
local buffer_size = 0
-- 2nd level buffer, it stores logs ready to be sent out
local send_buffer = ""
-- 1st level buffer, it stores incoming logs
local log_buffer_data = new_tab(20000, 0)
-- number of log lines in current 1st level buffer, starts from 0
local log_buffer_index = 0
local last_error
local connecting
local connected
local exiting
local retry_connect = 0
local retry_send = 0
local max_retry_times = 3
local retry_interval = 100 -- 0.1s
local pool_size = 10
local flushing
local logger_initted
local counter = 0
local ssl_session
local function _write_error(msg)
last_error = msg
end
local function _do_connect()
local ok, err, sock
if not connected then
if (sock_type == 'udp') then
sock, err = udp()
else
sock, err = tcp()
end
if not sock then
_write_error(err)
return nil, err
end
sock:settimeout(timeout)
end
-- "host"/"port" and "path" have already been checked in init()
if host and port then
if (sock_type == 'udp') then
ok, err = sock:setpeername(host, port)
else
ok, err = sock:connect(host, port)
end
elseif path then
ok, err = sock:connect("unix:" .. path)
end
if not ok then
return nil, err
end
return sock
end
local function _do_handshake(sock)
if not ssl then
return sock
end
local session, err = sock:sslhandshake(ssl_session, sni_host or host,
ssl_verify)
if not session then
return nil, err
end
ssl_session = session
return sock
end
local function _connect()
local err, sock
if connecting then
if debug then
ngx_log(DEBUG, "previous connection not finished")
end
return nil, "previous connection not finished"
end
connected = false
connecting = true
retry_connect = 0
while retry_connect <= max_retry_times do
sock, err = _do_connect()
if sock then
sock, err = _do_handshake(sock)
if sock then
connected = true
break
end
end
if debug then
ngx_log(DEBUG, "reconnect to the log server: ", err)
end
-- ngx.sleep time is in seconds
if not exiting then
ngx_sleep(retry_interval / 1000)
end
retry_connect = retry_connect + 1
end
connecting = false
if not connected then
return nil, "try to connect to the log server failed after "
.. max_retry_times .. " retries: " .. err
end
return sock
end
local function _prepare_stream_buffer()
local packet = concat(log_buffer_data, "", 1, log_buffer_index)
send_buffer = send_buffer .. packet
log_buffer_index = 0
counter = counter + 1
if counter > max_buffer_reuse then
log_buffer_data = new_tab(20000, 0)
counter = 0
if debug then
ngx_log(DEBUG, "log buffer reuse limit (" .. max_buffer_reuse
.. ") reached, create a new \"log_buffer_data\"")
end
end
end
local function _do_flush()
local ok, err, sock, bytes
local packet = send_buffer
sock, err = _connect()
if not sock then
return nil, err
end
bytes, err = sock:send(packet)
if not bytes then
-- "sock:send" always closes current connection on error
return nil, err
end
if debug then
ngx.update_time()
ngx_log(DEBUG, ngx.now(), ":log flush:" .. bytes .. ":" .. packet)
end
if (sock_type ~= 'udp') then
ok, err = sock:setkeepalive(0, pool_size)
if not ok then
return nil, err
end
end
return bytes
end
local function _need_flush()
if buffer_size > 0 then
return true
end
return false
end
local function _flush_lock()
if not flushing then
if debug then
ngx_log(DEBUG, "flush lock acquired")
end
flushing = true
return true
end
return false
end
local function _flush_unlock()
if debug then
ngx_log(DEBUG, "flush lock released")
end
flushing = false
end
local function _flush()
local err
-- pre check
if not _flush_lock() then
if debug then
ngx_log(DEBUG, "previous flush not finished")
end
-- do this later
return true
end
if not _need_flush() then
if debug then
ngx_log(DEBUG, "no need to flush:", log_buffer_index)
end
_flush_unlock()
return true
end
-- start flushing
retry_send = 0
if debug then
ngx_log(DEBUG, "start flushing")
end
local bytes
while retry_send <= max_retry_times do
if log_buffer_index > 0 then
_prepare_stream_buffer()
end
bytes, err = _do_flush()
if bytes then
break
end
if debug then
ngx_log(DEBUG, "resend log messages to the log server: ", err)
end
-- ngx.sleep time is in seconds
if not exiting then
ngx_sleep(retry_interval / 1000)
end
retry_send = retry_send + 1
end
_flush_unlock()
if not bytes then
local err_msg = "try to send log messages to the log server "
.. "failed after " .. max_retry_times .. " retries: "
.. err
_write_error(err_msg)
return nil, err_msg
else
if debug then
ngx_log(DEBUG, "send " .. bytes .. " bytes")
end
end
buffer_size = buffer_size - #send_buffer
send_buffer = ""
return bytes
end
local function _periodic_flush(premature)
if premature then
exiting = true
end
if need_periodic_flush or exiting then
-- no regular flush happened after periodic flush timer had been set
if debug then
ngx_log(DEBUG, "performing periodic flush")
end
_flush()
else
if debug then
ngx_log(DEBUG, "no need to perform periodic flush: regular flush "
.. "happened before")
end
need_periodic_flush = true
end
timer_at(periodic_flush, _periodic_flush)
end
local function _flush_buffer()
local ok, err = timer_at(0, _flush)
need_periodic_flush = false
if not ok then
_write_error(err)
return nil, err
end
end
local function _write_buffer(msg, len)
log_buffer_index = log_buffer_index + 1
log_buffer_data[log_buffer_index] = msg
buffer_size = buffer_size + len
return buffer_size
end
function _M.init(user_config)
if (type(user_config) ~= "table") then
return nil, "user_config must be a table"
end
for k, v in pairs(user_config) do
if k == "host" then
if type(v) ~= "string" then
return nil, '"host" must be a string'
end
host = v
elseif k == "port" then
if type(v) ~= "number" then
return nil, '"port" must be a number'
end
if v < 0 or v > MAX_PORT then
return nil, ('"port" out of range 0~%s'):format(MAX_PORT)
end
port = v
elseif k == "path" then
if type(v) ~= "string" then
return nil, '"path" must be a string'
end
path = v
elseif k == "sock_type" then
if type(v) ~= "string" then
return nil, '"sock_type" must be a string'
end
if v ~= "tcp" and v ~= "udp" then
return nil, '"sock_type" must be "tcp" or "udp"'
end
sock_type = v
elseif k == "flush_limit" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "flush_limit"'
end
flush_limit = v
elseif k == "drop_limit" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "drop_limit"'
end
drop_limit = v
elseif k == "timeout" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "timeout"'
end
timeout = v
elseif k == "max_retry_times" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "max_retry_times"'
end
max_retry_times = v
elseif k == "retry_interval" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "retry_interval"'
end
-- ngx.sleep time is in seconds
retry_interval = v
elseif k == "pool_size" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "pool_size"'
end
pool_size = v
elseif k == "max_buffer_reuse" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "max_buffer_reuse"'
end
max_buffer_reuse = v
elseif k == "periodic_flush" then
if type(v) ~= "number" or v < 0 then
return nil, 'invalid "periodic_flush"'
end
periodic_flush = v
elseif k == "ssl" then
if type(v) ~= "boolean" then
return nil, '"ssl" must be a boolean value'
end
ssl = v
elseif k == "ssl_verify" then
if type(v) ~= "boolean" then
return nil, '"ssl_verify" must be a boolean value'
end
ssl_verify = v
elseif k == "sni_host" then
if type(v) ~= "string" then
return nil, '"sni_host" must be a string'
end
sni_host = v
end
end
if not (host and port) and not path then
return nil, "no logging server configured. \"host\"/\"port\" or "
.. "\"path\" is required."
end
if (flush_limit >= drop_limit) then
return nil, "\"flush_limit\" should be < \"drop_limit\""
end
flushing = false
exiting = false
connecting = false
connected = false
retry_connect = 0
retry_send = 0
logger_initted = true
if periodic_flush then
if debug then
ngx_log(DEBUG, "periodic flush enabled for every "
.. periodic_flush .. " seconds")
end
need_periodic_flush = true
timer_at(periodic_flush, _periodic_flush)
end
return logger_initted
end
function _M.log(msg)
if not logger_initted then
return nil, "not initialized"
end
local bytes
if type(msg) ~= "string" then
msg = tostring(msg)
end
local msg_len = #msg
if (debug) then
ngx.update_time()
ngx_log(DEBUG, ngx.now(), ":log message length: " .. msg_len)
end
-- response of "_flush_buffer" is not checked, because it writes
-- error buffer
if (is_exiting()) then
exiting = true
_write_buffer(msg, msg_len)
_flush_buffer()
if (debug) then
ngx_log(DEBUG, "Nginx worker is exiting")
end
bytes = 0
elseif (msg_len + buffer_size < flush_limit) then
_write_buffer(msg, msg_len)
bytes = msg_len
elseif (msg_len + buffer_size <= drop_limit) then
_write_buffer(msg, msg_len)
_flush_buffer()
bytes = msg_len
else
_flush_buffer()
if (debug) then
ngx_log(DEBUG, "logger buffer is full, this log message will be "
.. "dropped")
end
bytes = 0
--- this log message doesn't fit in buffer, drop it
end
if last_error then
local err = last_error
last_error = nil
return bytes, err
end
return bytes
end
function _M.initted()
return logger_initted
end
_M.flush = _flush
return _M

View File

@ -1,8 +1,12 @@
local content_length=tonumber(ngx.req.get_headers()['content-length'])
local method=ngx.req.get_method()
--ngx_lua如果是0.9.2以上版本建议正则过滤函数改为ngx.re.find匹配效率会提高三倍左右。
local ngxmatch=ngx.re.match
--local ngxmatch=ngx.re.find
if whiteip() then
elseif blockip() then
--检测攻击ip是否被拦截。
elseif denyhackip(1) then
elseif denycc() then
elseif ngx.var.http_Acunetix_Aspect then
ngx.exit(444)
@ -42,7 +46,7 @@ elseif PostCheck then
return true
end
size = size + len(data)
local m = ngxmatch(data,[[Content-Disposition: form-data;(.+)filename="(.+)\\.(.*)"]],'ijo')
local m = ngxmatch(data,[[Content-Disposition: form-data;(.+)filename="(.+)\.(.*)"]],'ijo')
if m then
fileExtCheck(m[3])
filetranslate = true
@ -82,7 +86,8 @@ elseif PostCheck then
end
end
end
end
end
else
return
end

View File

@ -1 +1,2 @@
^/123/$
site:^www\.baidu\.com/whiteurl/