mirror of https://github.com/1Panel-dev/1Panel
233 lines
7.2 KiB
Lua
233 lines
7.2 KiB
Lua
|
local utils = require "utils"
|
|||
|
local stringutf8 = require "stringutf8"
|
|||
|
local logger_factory = require "logger_factory"
|
|||
|
local db = require "db"
|
|||
|
local config = require "config"
|
|||
|
local redis_util = require "redis_util"
|
|||
|
local action = require "action"
|
|||
|
|
|||
|
local upper_str = string.upper
|
|||
|
local concat_table = table.concat
|
|||
|
local tonumber = tonumber
|
|||
|
local get_expire_time = utils.get_expire_time
|
|||
|
local get_date_hour = utils.get_date_hour
|
|||
|
local get_today = ngx.today
|
|||
|
|
|||
|
local ATTACK_PREFIX = "attack_"
|
|||
|
local ATTACK_TYPE_PREFIX = "attack_type_"
|
|||
|
|
|||
|
local function writeAttackLog()
|
|||
|
local rule_table = ngx.ctx.rule_table
|
|||
|
local data = ngx.ctx.hitData
|
|||
|
local action = ngx.ctx.action
|
|||
|
local rule = rule_table.rule
|
|||
|
|
|||
|
local rule_type = rule_table.type
|
|||
|
if not rule_type then
|
|||
|
rule_type = "default"
|
|||
|
end
|
|||
|
|
|||
|
local realIp = ngx.ctx.ip
|
|||
|
|
|||
|
local geoip = ngx.ctx.geoip
|
|||
|
local country = geoip.country["zh"] or ""
|
|||
|
local province = geoip.province["zh"] or ""
|
|||
|
local city = ""
|
|||
|
local longitude = geoip.longitude
|
|||
|
local latitude = geoip.latitude
|
|||
|
|
|||
|
local method = ngx.req.get_method()
|
|||
|
local uri = ngx.var.request_uri
|
|||
|
local ua = ngx.ctx.ua
|
|||
|
local host = ngx.var.server_name
|
|||
|
local protocol = ngx.var.server_protocol
|
|||
|
local attackTime = ngx.localtime()
|
|||
|
|
|||
|
local website_key = ngx.ctx.website_key
|
|||
|
|
|||
|
local address = country .. province .. city
|
|||
|
address = stringutf8.default_if_blank(address, '-')
|
|||
|
ua = stringutf8.default_if_blank(ua, '-')
|
|||
|
data = stringutf8.default_if_blank(data, '-')
|
|||
|
|
|||
|
local log_path = "/www/sites/" .. website_key .. "/attack.log"
|
|||
|
local logStr = concat_table({ rule_type, realIp, address, "[" .. attackTime .. "]", '"' .. method, host, uri, protocol .. '"', data, '"' .. ua .. '"', '"' .. rule .. '"', action }, ' ')
|
|||
|
local host_logger = logger_factory.get_logger(log_path, host, true)
|
|||
|
host_logger:log(logStr .. '\n')
|
|||
|
|
|||
|
db.init_db()
|
|||
|
if wafdb == nil then
|
|||
|
return false
|
|||
|
end
|
|||
|
|
|||
|
local isBlock = 0
|
|||
|
local blocking_time = 0
|
|||
|
if ngx.ctx.ipBlocked then
|
|||
|
isBlock = 1
|
|||
|
blocking_time = tonumber(rule_table.ipBlockTime)
|
|||
|
end
|
|||
|
|
|||
|
local insertQuery = [[
|
|||
|
INSERT INTO attack_log (
|
|||
|
ip, ip_city, ip_country, ip_subdivisions, ip_continent,
|
|||
|
ip_longitude, ip_latitude, time, localtime, server_name,
|
|||
|
website_key, host, method, uri, user_agent, rule,
|
|||
|
nginx_log, blocking_time, action, msg, params, is_block
|
|||
|
) VALUES (
|
|||
|
:realIp, :city, :country, :subdivisions, :continent,
|
|||
|
:longitude, :latitude, :time, :localtime, :host,
|
|||
|
:website_key, :host, :method, :uri, :ua, :rule_type,
|
|||
|
:logStr, :blocking_time, :action, :msg, :params, :is_block
|
|||
|
)
|
|||
|
]]
|
|||
|
|
|||
|
local stmt = wafdb:prepare(insertQuery)
|
|||
|
|
|||
|
stmt:bind_names {
|
|||
|
realIp = realIp,
|
|||
|
city = city,
|
|||
|
country = country,
|
|||
|
subdivisions = "",
|
|||
|
continent = "",
|
|||
|
longitude = longitude,
|
|||
|
latitude = latitude,
|
|||
|
time = os.time(),
|
|||
|
localtime = os.date("%Y-%m-%d %H:%M:%S", os.time()),
|
|||
|
host = host,
|
|||
|
website_key = website_key,
|
|||
|
method = method,
|
|||
|
uri = uri,
|
|||
|
ua = ua,
|
|||
|
rule_type = rule_type,
|
|||
|
logStr = logStr,
|
|||
|
blocking_time = blocking_time or 0,
|
|||
|
action = action,
|
|||
|
msg = "msg",
|
|||
|
params = "Params",
|
|||
|
is_block = isBlock
|
|||
|
}
|
|||
|
|
|||
|
local code = stmt:step()
|
|||
|
stmt:finalize()
|
|||
|
|
|||
|
if code ~= 101 then
|
|||
|
local errorMsg = wafdb:errmsg()
|
|||
|
if errorMsg then
|
|||
|
ngx.log(ngx.ERR, "insert attack_log error", errorMsg)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
end
|
|||
|
|
|||
|
local function writeIPBlockLog()
|
|||
|
local rule_table = ngx.ctx.rule_table
|
|||
|
local ip = ngx.ctx.ip
|
|||
|
local website_key = ngx.ctx.website_key
|
|||
|
local log_path = "/www/sites/" .. website_key .. "/attack.log"
|
|||
|
local host_logger = logger_factory.get_logger(log_path .. "ipBlock.log", 'ipBlock', false)
|
|||
|
|
|||
|
host_logger:log(concat_table({ ngx.localtime(), ip, rule_table.type, rule_table.ipBlockTime .. 's' }, ' ') .. "\n")
|
|||
|
|
|||
|
--todo 永久拉黑IP
|
|||
|
--if rule_table.ipBlockTimeout == 0 then
|
|||
|
-- local ipBlackLogger = logger_factory.get_logger(rulePath .. "ipBlackList", 'ipBlack', false)
|
|||
|
-- ipBlackLogger:log(ip .. "\n")
|
|||
|
--end
|
|||
|
end
|
|||
|
|
|||
|
-- 按小时统计当天请求流量,存入缓存,key格式:2023-05-05 09
|
|||
|
local function countRequestTraffic()
|
|||
|
local hour = get_date_hour()
|
|||
|
local dict = ngx.shared.dict_req_count
|
|||
|
local expire_time = get_expire_time()
|
|||
|
local count, err = dict:incr(hour, 1, 0, expire_time)
|
|||
|
if not count then
|
|||
|
dict:set(hour, 1, expire_time)
|
|||
|
ngx.log(ngx.ERR, "failed to count traffic ", err)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
--[[
|
|||
|
按小时统计当天攻击请求流量,存入缓存,key格式:attack_2023-05-05 09
|
|||
|
按天统计当天所有攻击类型流量,存入缓存,key格式:attack_type_2023-05-05_ARGS
|
|||
|
]]
|
|||
|
local function countAttackRequestTraffic()
|
|||
|
local rule_table = ngx.ctx.rule_table
|
|||
|
local rule_type = ""
|
|||
|
if rule_table.rule_type then
|
|||
|
rule_type = upper_str(rule_table.rule_type)
|
|||
|
end
|
|||
|
if rule_table.type then
|
|||
|
rule_type = upper_str(rule_table.type)
|
|||
|
end
|
|||
|
local dict = ngx.shared.dict_req_count
|
|||
|
local count, err = nil, nil
|
|||
|
local expire_time = get_expire_time()
|
|||
|
|
|||
|
if rule_type ~= 'WHITEIP' then
|
|||
|
local hour = get_date_hour()
|
|||
|
local key = ATTACK_PREFIX .. hour
|
|||
|
count, err = dict:incr(key, 1, 0, expire_time)
|
|||
|
if not count then
|
|||
|
dict:set(key, 1, expire_time)
|
|||
|
ngx.log(ngx.ERR, "failed to count attack traffic ", err)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local today = get_today() .. '_'
|
|||
|
local type_key = ATTACK_TYPE_PREFIX .. today .. rule_type
|
|||
|
count, err = dict:incr(type_key, 1, 0, expire_time)
|
|||
|
|
|||
|
if not count and err == "not found" then
|
|||
|
dict:set(type_key, 1, expire_time)
|
|||
|
ngx.log(ngx.ERR, "failed to count attack traffic ", err)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local function count_not_found()
|
|||
|
if ngx.status ~= 404 then
|
|||
|
return
|
|||
|
end
|
|||
|
if config.is_global_state_on("notFoundCount") then
|
|||
|
local ip = ngx.ctx.ip
|
|||
|
local not_found_config = config.get_global_config("notFoundCount")
|
|||
|
local key = ip
|
|||
|
|
|||
|
if config.is_redis_on() then
|
|||
|
key = "cc_attack_count:" .. key
|
|||
|
local count, _ = redis_util.incr(key, not_found_config.duration)
|
|||
|
if not count then
|
|||
|
redis_util.set(key, 1, not_found_config.duration)
|
|||
|
elseif count >= not_found_config.threshold then
|
|||
|
action.block_ip(ip, not_found_config)
|
|||
|
return
|
|||
|
end
|
|||
|
else
|
|||
|
key = ip .. "not_found"
|
|||
|
local limit = ngx.shared.waf_limit
|
|||
|
local count, _ = limit:incr(key, 1, 0, not_found_config.duration)
|
|||
|
if not count then
|
|||
|
limit:set(key, 1, not_found_config.duration)
|
|||
|
elseif count >= not_found_config.threshold then
|
|||
|
action.block_ip(ip, not_found_config)
|
|||
|
return
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
if config.is_waf_on() then
|
|||
|
count_not_found()
|
|||
|
countRequestTraffic()
|
|||
|
local isAttack = ngx.ctx.isAttack
|
|||
|
|
|||
|
if isAttack then
|
|||
|
writeAttackLog()
|
|||
|
countAttackRequestTraffic()
|
|||
|
end
|
|||
|
|
|||
|
-- if ngx.ctx.ipBlocked then
|
|||
|
-- writeIPBlockLog()
|
|||
|
-- end
|
|||
|
end
|