Simple Linux Panel
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mdserver-web/plugins/op_waf/waf/lua/common.lua

698 lines
18 KiB

6 years ago
6 years ago
local setmetatable = setmetatable
local _M = { _VERSION = '0.02' }
6 years ago
local mt = { __index = _M }
6 years ago
local json = require "cjson"
6 years ago
local ngx_match = ngx.re.find
6 years ago
3 years ago
local debug_mode = true
local waf_root = "{$WAF_ROOT}"
local cpath = waf_root.."/waf/"
local logdir = waf_root.."/logs/"
local rpath = cpath.."/rule/"
function _M.new(self)
6 years ago
local self = {
waf_root = waf_root,
cpath = cpath,
rpath = rpath,
logdir = logdir,
config = '',
site_config = '',
3 years ago
server_name = '',
3 years ago
global_tatal = nil,
params = nil,
6 years ago
}
return setmetatable(self, mt)
6 years ago
end
3 years ago
function _M.getInstance(self)
if rawget(self, "instance") == nil then
rawset(self, "instance", self.new())
end
assert(self.instance ~= nil)
return self.instance
end
function _M.setDebug(self, mode)
debug_mode = mode
end
-- 调试方式
function _M.D(self, msg)
if not debug_mode then return true end
local _msg = ''
if type(msg) == 'table' then
for key, val in pairs(msg) do
_msg = tostring( key)..':'.."\n"
end
elseif type(msg) == 'string' then
_msg = msg
elseif type(msg) == 'nil' then
_msg = 'nil'
else
_msg = msg
end
local fp = io.open(waf_root.."/debug.log", "ab")
if fp == nil then
return nil
end
3 years ago
-- local localtime = os.date("%Y-%m-%d %H:%M:%S")
local localtime = ngx.localtime()
if server_name then
fp:write(tostring(_msg) .. "\n")
else
fp:write(localtime..":"..tostring(_msg) .. "\n")
end
fp:flush()
fp:close()
return true
end
local function write_file_clear(filename, body)
fp = io.open(filename,'w')
if fp == nil then
return nil
end
fp:write(body)
fp:flush()
fp:close()
return true
end
6 years ago
function _M.setConfData( self, config, site_config )
self.config = config
self.site_config = site_config
6 years ago
end
function _M.setParams( self, params )
self.params = params
end
6 years ago
function _M.is_min(self, ip1, ip2)
6 years ago
n = 0
for _,v in ipairs({1,2,3,4})
do
if ip1[v] == ip2[v] then
n = n + 1
elseif ip1[v] > ip2[v] then
break
else
return false
end
end
return true
6 years ago
end
6 years ago
function _M.is_max(self,ip1,ip2)
n = 0
for _,v in ipairs({1,2,3,4})
do
if ip1[v] == ip2[v] then
n = n + 1
elseif ip1[v] < ip2[v] then
break
else
return false
end
end
return true
end
6 years ago
6 years ago
function _M.split(self, str,reps )
3 years ago
local rsList = {}
6 years ago
string.gsub(str,'[^'..reps..']+',function(w)
3 years ago
table.insert(rsList,w)
6 years ago
end)
3 years ago
return rsList
6 years ago
end
function _M.arrip(self, ipstr)
if ipstr == 'unknown' then return {0,0,0,0} end
if string.find(ipstr,':') then return ipstr end
iparr = self:split(ipstr,'.')
iparr[1] = tonumber(iparr[1])
iparr[2] = tonumber(iparr[2])
iparr[3] = tonumber(iparr[3])
iparr[4] = tonumber(iparr[4])
return iparr
end
function _M.compare_ip(self,ips)
local ip = self.params["ip"]
local ipn = self.params["ipn"]
if ip == 'unknown' then return true end
if string.find(ip,':') then return false end
if not self:is_max(ipn,ips[2]) then return false end
if not self:is_min(ipn,ips[1]) then return false end
return true
end
6 years ago
3 years ago
function _M.to_json(self, msg)
return json.encode(msg)
end
3 years ago
function _M.return_state(status,msg)
result = {}
result['status'] = status
result['msg'] = msg
return result
end
6 years ago
function _M.return_message(self, status, msg)
3 years ago
ngx.header.content_type = "application/json"
3 years ago
local data = self:return_state(status, msg)
3 years ago
ngx.say(json.encode(data))
ngx.exit(200)
6 years ago
end
3 years ago
function _M.return_html(self,status, html)
6 years ago
ngx.header.content_type = "text/html"
ngx.status = status
ngx.say(html)
ngx.exit(status)
end
6 years ago
function _M.read_file_body(self, filename)
6 years ago
fp = io.open(filename, 'r')
if fp == nil then
6 years ago
return nil
end
3 years ago
local fbody = fp:read("*a")
6 years ago
fp:close()
if fbody == '' then
return nil
end
6 years ago
return fbody
6 years ago
end
3 years ago
function _M.read_file(self, name)
f = self.rpath .. name .. '.json'
3 years ago
local fbody = self:read_file_body(f)
3 years ago
if fbody == nil then
return {}
end
3 years ago
local data = json.decode(fbody)
3 years ago
return data
end
6 years ago
3 years ago
function _M.select_rule(self, rules)
if not rules then return {} end
new_rules = {}
for i,v in ipairs(rules)
do
if v[1] == 1 then
table.insert(new_rules,v[2])
end
3 years ago
end
3 years ago
return new_rules
end
3 years ago
3 years ago
function _M.read_file_table( self, name )
return self:select_rule(self:read_file(name))
end
function _M.read_file_body_decode(self, name)
return json.decode(self:read_file_body(name))
3 years ago
end
6 years ago
function _M.write_file(self, filename, body)
6 years ago
fp = io.open(filename,'ab')
if fp == nil then
return nil
end
fp:write(body)
fp:flush()
fp:close()
return true
end
function _M.write_file_clear(self, filename, body)
return write_file_clear(filename, body)
6 years ago
end
3 years ago
function _M.write_to_file(self, logstr)
local server_name = self.params['server_name']
local filename = self.logdir .. '/' .. server_name .. '_' .. ngx.today() .. '.log'
self:write_file(filename, logstr)
return true
end
6 years ago
3 years ago
function _M.write_drop_ip(self, is_drop, drop_time)
local filename = self.logdir .. 'waf_drop_ip.log'
3 years ago
6 years ago
local fp = io.open(filename,'ab')
6 years ago
local server_name = self.params["server_name"]
local ip = self.params["server_name"]
local request_uri = self.params["request_uri"]
6 years ago
if fp == nil then return false end
local logtmp = {os.time(),ip,server_name,request_uri,drop_time,is_drop}
local logstr = json.encode(logtmp) .. "\n"
fp:write(logstr)
fp:flush()
fp:close()
return true
end
6 years ago
function _M.continue_key(self,key)
key = tostring(key)
if string.len(key) > 64 then return false end;
3 years ago
local keys = { "content", "contents", "body", "msg", "file", "files", "img", "newcontent" }
6 years ago
for _,k in ipairs(keys)
do
if k == key then return false end;
end
return true;
end
6 years ago
function _M.array_len(self, arr)
if not arr then return 0 end
local count = 0
for _,v in ipairs(arr)
do
count = count + 1
end
return count
end
function _M.is_ipaddr(self, client_ip)
3 years ago
local cipn = self:split(client_ip,'.')
6 years ago
if self:array_len(cipn) < 4 then return false end
for _,v in ipairs({1,2,3,4})
do
local ipv = tonumber(cipn[v])
if ipv == nil then return false end
if ipv > 255 or ipv < 0 then return false end
end
return true
end
3 years ago
-- 定时异步同步统计信息
function _M.timer_stats_total(self)
local total_path = self.cpath .. 'total.json'
local total = ngx.shared.waf_limit:get(total_path)
if not total then
return false
end
3 years ago
return self:write_file_clear(total_path,total)
end
3 years ago
function _M.add_log(self, name, rule)
6 years ago
local server_name = self.params['server_name']
3 years ago
local total_path = cpath .. 'total.json'
local total = ngx.shared.waf_limit:get(total_path)
if not total then
local tbody = self:read_file_body(total_path)
total = json.decode(tbody)
else
total = json.decode(total)
6 years ago
end
3 years ago
if not total then return false end
-- 开始计算
6 years ago
if not total['sites'] then total['sites'] = {} end
if not total['sites'][server_name] then total['sites'][server_name] = {} end
if not total['sites'][server_name][name] then total['sites'][server_name][name] = 0 end
if not total['rules'] then total['rules'] = {} end
if not total['rules'][name] then total['rules'][name] = 0 end
if not total['total'] then total['total'] = 0 end
total['total'] = total['total'] + 1
total['sites'][server_name][name] = total['sites'][server_name][name] + 1
total['rules'][name] = total['rules'][name] + 1
3 years ago
ngx.shared.waf_limit:set(total_path,json.encode(total))
-- 异步执行
3 years ago
-- 现在改再init_workder.lua 定时执行
3 years ago
-- ngx.timer.every(3, timer_stats_total_log)
6 years ago
end
3 years ago
-- 获取配置域名
function _M.get_sn(self, config_domains)
local request_name = ngx.var.server_name
local cache_name = ngx.shared.waf_limit:get(request_name)
if cache_name then return cache_name end
6 years ago
3 years ago
for _,v in ipairs(config_domains)
6 years ago
do
3 years ago
for _,cd_name in ipairs(v['domains'])
6 years ago
do
3 years ago
if request_name == cd_name then
ngx.shared.waf_limit:set(request_name,v['name'],86400)
6 years ago
return v['name']
end
end
end
3 years ago
return request_name
6 years ago
end
3 years ago
function _M.get_random(self,n)
math.randomseed(ngx.time())
local t = {
"0","1","2","3","4","5","6","7","8","9",
"a","b","c","d","e","f","g","h","i","j",
"k","l","m","n","o","p","q","r","s","t",
"u","v","w","x","y","z",
"A","B","C","D","E","F","G","H","I","J",
"K","L","M","N","O","P","Q","R","S","T",
"U","V","W","X","Y","Z",
}
local s = ""
for i =1, n do
s = s .. t[math.random(#t)]
end
return s
end
6 years ago
6 years ago
function _M.is_ngx_match_orgin(self,rule,match, sign)
if ngx_match(ngx.unescape_uri(match), rule,"isjo") then
error_rule = rule .. ' >> ' .. sign .. ':' .. match
return true
end
return false
end
3 years ago
function _M.ngx_match_string(self, rule, content,sign)
local t = self:is_ngx_match_orgin(rule, content, sign)
if t then
return true
end
return false
end
3 years ago
function _M.ngx_match_list(self, rules, content)
for i,rule in ipairs(rules)
do
if rule[1] == 1 then
local t = self:is_ngx_match_orgin(rule[2], content, rule[3])
if t then
return true
end
end
end
return false
end
function _M.is_ngx_match_ua(self, rules, content)
-- ngx.header.content_type = "text/html"
for i,rule in ipairs(rules)
do
-- 开启的规则,才匹配。
if rule[1] == 1 then
local t = self:is_ngx_match_orgin(rule[2], content, rule[3])
if t then
return true
end
end
end
return false
end
function _M.is_ngx_match_post(self, rules, content)
for i,rule in ipairs(rules)
do
-- 开启的规则,才匹配。
if rule[1] == 1 then
local t = self:is_ngx_match_orgin(rule[2],content, rule[3])
if t then
return true
end
end
end
return false
end
6 years ago
function _M.is_ngx_match(self, rules, sbody, rule_name)
if rules == nil or sbody == nil then return false end
if type(sbody) == "string" then
sbody = {sbody}
end
if type(rules) == "string" then
rules = {rules}
end
for k,body in pairs(sbody)
do
if self:continue_key(k) then
for i,rule in ipairs(rules)
do
if self.site_config[server_name] and rule_name then
local n = i - 1
for _,j in ipairs(self.site_config[server_name]['disable_rule'][rule_name])
do
if n == j then
rule = ""
end
end
end
if body and rule ~="" then
if type(body) == "string" then
if ngx_match(ngx.unescape_uri(body),rule,"isjo") then
error_rule = rule .. ' >> ' .. k .. ':' .. body
return true
end
end
if type(k) == "string" then
if ngx_match(ngx.unescape_uri(k),rule,"isjo") then
error_rule = rule .. ' >> ' .. k
return true
end
end
end
end
end
end
return false
end
function _M.write_log(self, name, rule)
3 years ago
local config = self.config
6 years ago
local ip = self.params['ip']
3 years ago
local retry = config['retry']['retry']
local retry_time = config['retry']['retry_time']
local retry_cycle = config['retry']['retry_cycle']
6 years ago
3 years ago
local count, _ = ngx.shared.waf_drop_ip:get(ip)
6 years ago
if count then
3 years ago
ngx.shared.waf_drop_ip:incr(ip, 1)
6 years ago
else
3 years ago
ngx.shared.waf_drop_ip:set(ip, 1, retry_cycle)
6 years ago
end
3 years ago
3 years ago
if config['log'] ~= true or self:is_site_config('log') ~= true then return false end
local method = self.params['method']
6 years ago
if error_rule then
rule = error_rule
error_rule = nil
end
6 years ago
local logtmp = {ngx.localtime(), ip, method, ngx.var.request_uri, ngx.var.http_user_agent, name, rule}
6 years ago
local logstr = json.encode(logtmp) .. "\n"
3 years ago
local count,_ = ngx.shared.waf_drop_ip:get(ip)
6 years ago
if count > retry and name ~= 'cc' then
3 years ago
local safe_count,_ = ngx.shared.waf_drop_sum:get(ip)
6 years ago
if not safe_count then
3 years ago
ngx.shared.waf_drop_sum:set(ip, 1, 86400)
6 years ago
safe_count = 1
else
3 years ago
ngx.shared.waf_drop_sum:incr(ip, 1)
6 years ago
end
local lock_time = retry_time * safe_count
if lock_time > 86400 then lock_time = 86400 end
6 years ago
logtmp = {ngx.localtime(),ip,method,ngx.var.request_uri, ngx.var.http_user_agent,name,retry_cycle .. '秒以内累计超过'..retry..'次以上非法请求,封锁'.. lock_time ..''}
6 years ago
logstr = logstr .. json.encode(logtmp) .. "\n"
3 years ago
ngx.shared.waf_drop_ip:set(ip,retry+1,lock_time)
self:write_drop_ip('inc',lock_time)
6 years ago
end
self:write_to_file(logstr)
3 years ago
self:add_log(name,rule)
6 years ago
end
local ffi = require("ffi")
ffi.cdef[[
struct timeval {
long int tv_sec;
long int tv_usec;
};
int gettimeofday(struct timeval *tv, void *tz);
]];
local tm = ffi.new("struct timeval");
-- 返回微秒级时间戳
function _M.current_time_millis()
ffi.C.gettimeofday(tm,nil);
local sec = tonumber(tm.tv_sec);
local usec = tonumber(tm.tv_usec);
return sec + usec * 10^-6;
end
3 years ago
function _M.bench(self, waf_limit, sign, call)
local func_start = self.current_time_millis()
3 years ago
for i=1,waf_limit do
call()
end
local func_end = self.current_time_millis()
local cos = func_end - func_start
self:D("["..sign.."][start]:"..tostring(func_start))
self:D("["..sign.."][end]:"..tostring(func_end))
self:D("cos["..sign.."]:"..tostring(cos))
end
-- 测试方法保留
function _M.split_bylog_debug(self, str,reps)
local resultStrList = {}
self:bench(1000000, "string.gsub",function()
string.gsub(str,'[^'..reps..']+', function(w)
table.insert(resultStrList,w)
return w
end)
end)
-- string.gsub(str,'[^'..reps..']+', function(w)
-- table.insert(resultStrList,w)
-- end)
self:bench(1000000, "ngx.re.gsub" ,function()
ngx.re.gsub(str,'[^'..reps..']+', function(w)
table.insert(resultStrList,w[0])
return w
end, "ijo")
end)
return resultStrList
end
3 years ago
function _M.get_real_ip(self, server_name)
local client_ip = "unknown"
local site_config = self.site_config
if site_config[server_name] then
if site_config[server_name]['cdn'] then
local request_header = ngx.req.get_headers()
3 years ago
for _,v in ipairs(site_config[server_name]['cdn_header'])
3 years ago
do
if request_header[v] ~= nil and request_header[v] ~= "" then
local header_tmp = request_header[v]
if type(header_tmp) == "table" then header_tmp = header_tmp[1] end
3 years ago
client_ip = self:split(header_tmp,',')[1]
-- return client_ip
3 years ago
break;
end
end
end
end
3 years ago
3 years ago
-- ipv6
if type(client_ip) == 'table' then client_ip = "" end
if client_ip ~= "unknown" and ngx.re.match(client_ip,"^([a-fA-F0-9]*):") then
3 years ago
return client_ip
3 years ago
end
3 years ago
-- ipv4
if not ngx.re.match(client_ip,"\\d+\\.\\d+\\.\\d+\\.\\d+") == nil or not self:is_ipaddr(client_ip) then
6 years ago
client_ip = ngx.var.remote_addr
if client_ip == nil then
client_ip = "unknown"
end
end
return client_ip
end
6 years ago
function _M.is_site_config(self,cname)
3 years ago
local site_config = self.site_config
if site_config[server_name] ~= nil then
6 years ago
if cname == 'cc' then
3 years ago
return site_config[server_name][cname]['open']
6 years ago
else
3 years ago
return site_config[server_name][cname]
6 years ago
end
end
return true
end
6 years ago
function _M.get_boundary(self)
local header = self.params["request_header"]["content-type"]
if not header then return nil end
if type(header) == "table" then
header = header[1]
end
local m = string.match(header, ";%s*boundary=\"([^\"]+)\"")
if m then
return m
end
return string.match(header, ";%s*boundary=([^\",;]+)")
end
function _M.return_post_data(self)
if method ~= "POST" then return false end
content_length = tonumber(self.params["request_header"]['content-length'])
if not content_length then return false end
max_len = 2560 * 1024000
if content_length > max_len then return false end
local boundary = self:get_boundary()
if boundary then
ngx.req.read_body()
local data = ngx.req.get_body_data()
if not data then return false end
local tmp = ngx.re.match(data,[[filename=\"(.+)\.(.*)\"]])
if not tmp then return false end
if not tmp[2] then return false end
return tmp[2]
end
return false
end
6 years ago
6 years ago
function _M.t(self)
ngx.say(',,,')
end
return _M