mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-13 17:24:44 +08:00
feat(waf): 增加 IP 组功能 (#4277)
This commit is contained in:
parent
e3fb97fc8c
commit
1b2208a4d0
@ -1,9 +1,17 @@
|
||||
<template>
|
||||
<el-card class="config-card">
|
||||
<span class="web-tag" v-if="website">
|
||||
<el-tooltip :content="$t('xpack.waf.websiteHelper')" placement="bottom">
|
||||
<el-tag type="primary" size="small">{{ $t('menu.website') }}</el-tag>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<div class="config-header">
|
||||
<span>{{ header }}</span>
|
||||
<span>
|
||||
{{ header }}
|
||||
</span>
|
||||
<slot name="header-r" />
|
||||
</div>
|
||||
|
||||
<el-text type="info">{{ description }}</el-text>
|
||||
<div class="config-content">
|
||||
<slot name="content-r" />
|
||||
@ -19,12 +27,20 @@ defineOptions({ name: 'ConfigCard' });
|
||||
defineProps({
|
||||
header: String,
|
||||
description: String,
|
||||
website: Boolean,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.config-card {
|
||||
cursor: pointer;
|
||||
|
||||
.web-tag {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.config-header {
|
||||
margin-bottom: 18px;
|
||||
display: flex;
|
||||
|
@ -2277,6 +2277,18 @@ const message = {
|
||||
cc: 'CC attack',
|
||||
isBlocking: 'Blocking',
|
||||
isFree: 'Unblocked',
|
||||
unLock: 'Unlock',
|
||||
unLockHelper: 'Do you want to unblock IP: {0}?',
|
||||
saveDefault: 'Save default',
|
||||
saveToWebsite: 'Apply to website',
|
||||
saveToWebsiteHelper: 'Apply current settings to all websites? ',
|
||||
websiteHelper:
|
||||
'Here are the default settings for creating a website. Modifications need to be applied to the website to take effect',
|
||||
websiteHelper2:
|
||||
'Here are the default settings for creating a website. Please modify the specific configuration at the website',
|
||||
ipGroup: 'IP group',
|
||||
ipGroupHelper:
|
||||
'One IP or IP segment per line, supports IPv4 and IPv6, for example: 192.168.1.1 or 192.168.1.0/24',
|
||||
},
|
||||
monitor: {
|
||||
name: 'Website Monitor',
|
||||
|
@ -2131,6 +2131,15 @@ const message = {
|
||||
cc: 'CC 攻擊',
|
||||
isBlocking: '封鎖中',
|
||||
isFree: '已解封',
|
||||
unLock: '解封',
|
||||
unLockHelper: '是否解封 IP:{0}?',
|
||||
saveDefault: '儲存預設',
|
||||
saveToWebsite: '應用在網站',
|
||||
saveToWebsiteHelper: '是否將目前設定套用到所有網站? ',
|
||||
websiteHelper: '此處為創建網站的默認設置,修改之後需要應用到網站才能生效',
|
||||
websiteHelper2: '此處為創建網站的默認設置,具體配置請在網站處修改',
|
||||
ipGroup: 'IP 組',
|
||||
ipGroupHelper: '一行一個 IP 或 IP 段,支援 IPv4 和 IPv6, 例如:192.168.1.1 或 192.168.1.0/24',
|
||||
},
|
||||
monitor: {
|
||||
name: '網站監控',
|
||||
|
@ -2132,6 +2132,15 @@ const message = {
|
||||
cc: 'CC 攻击',
|
||||
isBlocking: '封禁中',
|
||||
isFree: '已解封',
|
||||
unLock: '解封',
|
||||
unLockHelper: '是否解封 IP:{0}?',
|
||||
saveDefault: '保存默认',
|
||||
saveToWebsite: '应用到网站',
|
||||
saveToWebsiteHelper: '是否将当前设置应用到所有网站?',
|
||||
websiteHelper: '此处为创建网站的默认设置,修改之后需要应用到网站才能生效',
|
||||
websiteHelper2: '此处为创建网站的默认设置,具体配置请在网站处修改',
|
||||
ipGroup: 'IP 组',
|
||||
ipGroupHelper: '一行一个 IP 或者 IP 段,支持 IPv4 和 IPv6, 例如:192.168.1.1 或 192.168.1.0/24',
|
||||
},
|
||||
monitor: {
|
||||
name: '网站监控',
|
||||
|
@ -438,3 +438,7 @@ html {
|
||||
.p-mt-20 {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -79,19 +79,19 @@
|
||||
"defaultIpBlack": {
|
||||
"state": "on",
|
||||
"type": "defaultIpBlack",
|
||||
"code": 444,
|
||||
"code": 403,
|
||||
"action": "deny"
|
||||
},
|
||||
"xss": {
|
||||
"state": "on",
|
||||
"type": "xss",
|
||||
"code": 444,
|
||||
"code": 403,
|
||||
"action": "deny"
|
||||
},
|
||||
"sql": {
|
||||
"state": "on",
|
||||
"type": "sql",
|
||||
"code": 444,
|
||||
"code": 403,
|
||||
"action": "deny"
|
||||
},
|
||||
"cc": {
|
||||
|
@ -1,9 +1,10 @@
|
||||
lua_shared_dict dict_locks 100k;
|
||||
lua_shared_dict dict_req_count 10m;
|
||||
lua_shared_dict waf_req_count 10m;
|
||||
lua_shared_dict waf 30m;
|
||||
lua_shared_dict waf_black_ip 10m;
|
||||
lua_shared_dict waf_block_ip 10m;
|
||||
lua_shared_dict waf_ip_arr 10m;
|
||||
lua_shared_dict waf_limit 10m;
|
||||
lua_shared_dict waf_accesstoken 10m;
|
||||
lua_shared_dict ipc_shared_dict 10m;
|
||||
|
||||
lua_package_path "/usr/local/openresty/1pwaf/?.lua;/usr/local/openresty/1pwaf/lib/?.lua;;";
|
||||
init_by_lua_file /usr/local/openresty/1pwaf/init.lua;
|
||||
|
@ -1,11 +1,13 @@
|
||||
local file_utils = require "file"
|
||||
local lfs = require "lfs"
|
||||
local utils = require "utils"
|
||||
local cjson = require "cjson"
|
||||
|
||||
local read_rule = file_utils.read_rule
|
||||
local read_file2string = file_utils.read_file2string
|
||||
local read_file2table = file_utils.read_file2table
|
||||
local set_content_to_file = file_utils.set_content_to_file
|
||||
local read_list2table = file_utils.read_list2table
|
||||
local list_dir = lfs.dir
|
||||
local attributes = lfs.attributes
|
||||
local match_str = string.match
|
||||
@ -14,6 +16,7 @@ local waf_dir = "/usr/local/openresty/1pwaf/"
|
||||
local config_dir = waf_dir .. 'conf/'
|
||||
local global_rule_dir = waf_dir .. 'rules/'
|
||||
local site_dir = waf_dir .. 'sites/'
|
||||
local ip_group_dir = global_rule_dir .. 'ip_group/'
|
||||
|
||||
local _M = {}
|
||||
local config = {}
|
||||
@ -44,6 +47,7 @@ local function init_sites_config()
|
||||
local s_rule = nil
|
||||
if rule_type == "methodWhite" then
|
||||
s_rule = read_rule(rule_dir, rule_type, true)
|
||||
|
||||
else
|
||||
s_rule = read_rule(rule_dir, rule_type)
|
||||
end
|
||||
@ -59,9 +63,6 @@ local function init_sites_config()
|
||||
end
|
||||
config.site_config = site_config
|
||||
config.site_rules = site_rules
|
||||
|
||||
local waf_dict = ngx.shared.waf
|
||||
waf_dict:set("config", config)
|
||||
end
|
||||
|
||||
local function ini_waf_info()
|
||||
@ -71,7 +72,21 @@ local function ini_waf_info()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function load_ip_group()
|
||||
local ip_group_list = {}
|
||||
for entry in list_dir(ip_group_dir) do
|
||||
if entry ~= "." and entry ~= ".." then
|
||||
local group_path = ip_group_dir .. entry
|
||||
local group_value = read_list2table(group_path)
|
||||
ip_group_list[entry] = group_value
|
||||
end
|
||||
end
|
||||
local waf_dict = ngx.shared.waf
|
||||
local ok , err = waf_dict:set("ip_group_list", cjson.encode(ip_group_list))
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Failed to set ip_group_list",err)
|
||||
end
|
||||
end
|
||||
|
||||
local function init_global_config()
|
||||
local global_config_file = config_dir .. 'global.json'
|
||||
@ -86,14 +101,14 @@ local function init_global_config()
|
||||
rules.uaWhite = read_rule(global_rule_dir, "uaWhite")
|
||||
rules.urlBlack = read_rule(global_rule_dir, "urlBlack")
|
||||
rules.urlWhite = read_rule(global_rule_dir, "urlWhite")
|
||||
rules.ipBlack = read_rule(global_rule_dir, "ipBlack")
|
||||
rules.ipWhite = read_rule(global_rule_dir, "ipWhite")
|
||||
rules.args = read_rule(global_rule_dir, "args")
|
||||
rules.cookie = read_rule(global_rule_dir, "cookie")
|
||||
rules.defaultUaBlack = read_rule(global_rule_dir, "defaultUaBlack")
|
||||
rules.defaultUrlBlack = read_rule(global_rule_dir, "defaultUrlBlack")
|
||||
rules.header = read_rule(global_rule_dir, "header")
|
||||
|
||||
rules.ipBlack = read_rule(global_rule_dir, "ipBlack")
|
||||
|
||||
config.global_rules = rules
|
||||
|
||||
local html_res = {}
|
||||
@ -113,27 +128,28 @@ local function init_global_config()
|
||||
_M.waf_log_db_path = _M.waf_db_dir .. "req_log.db"
|
||||
_M.config_dir = config_dir
|
||||
|
||||
|
||||
local waf_dict = ngx.shared.waf
|
||||
waf_dict:set("config", config)
|
||||
end
|
||||
|
||||
local function get_config()
|
||||
local waf_dict = ngx.shared.waf
|
||||
local config_table = waf_dict:get("config")
|
||||
if config_table == nil then
|
||||
init_global_config()
|
||||
init_sites_config()
|
||||
return config
|
||||
end
|
||||
config = config_table
|
||||
return config_table
|
||||
end
|
||||
|
||||
function _M.load_config_file()
|
||||
ini_waf_info()
|
||||
init_global_config()
|
||||
init_sites_config()
|
||||
load_ip_group()
|
||||
|
||||
local waf_dict = ngx.shared.waf
|
||||
local ok,err = waf_dict:set("config", cjson.encode(config))
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Failed to set config",err)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_config()
|
||||
local waf_dict = ngx.shared.waf
|
||||
local cache_config = waf_dict:get("config")
|
||||
if not cache_config then
|
||||
return config
|
||||
end
|
||||
return cjson.decode(cache_config)
|
||||
end
|
||||
|
||||
function _M.get_site_config(website_key)
|
||||
|
@ -42,54 +42,39 @@ end
|
||||
|
||||
function _M.block_ip(ip, rule)
|
||||
local ok, err = nil, nil
|
||||
--local msg = "拉黑IP : " .. ip .. "国家 " .. ngx.ctx.ip_location.country["zh"]
|
||||
--if rule then
|
||||
-- msg = msg .. " 规则 " .. rule.type
|
||||
--end
|
||||
--ngx.log(ngx.ERR, msg)
|
||||
local msg = "拉黑IP : " .. ip .. "国家 " .. ngx.ctx.ip_location.country["zh"]
|
||||
if rule then
|
||||
msg = msg .. " 规则 " .. rule.type
|
||||
end
|
||||
ngx.log(ngx.ERR, msg)
|
||||
|
||||
if config.redis_on then
|
||||
if config.is_redis_on() then
|
||||
local red, err1 = redis_util.get_conn()
|
||||
if not red then
|
||||
return nil, err1
|
||||
end
|
||||
local key = "black_ip:" .. ip
|
||||
|
||||
local exists = red:exists(key)
|
||||
if exists == 0 then
|
||||
ok, err = red:set(key, 1)
|
||||
if ok then
|
||||
ngx.ctx.ip_blocked = true
|
||||
else
|
||||
ngx.log(ngx.ERR, "failed to set redis key " .. key, err)
|
||||
end
|
||||
end
|
||||
|
||||
if rule.ipBlockTime > 0 then
|
||||
ok, err = red:expire(key, rule.ipBlockTime)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "failed to expire redis key " .. key, err)
|
||||
end
|
||||
end
|
||||
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "failed to expire redis key " .. key, err)
|
||||
end
|
||||
redis_util.close_conn(red)
|
||||
else
|
||||
local wafBlackIp = ngx.shared.waf_black_ip
|
||||
local exists = wafBlackIp:get(ip)
|
||||
local block_ip_dict = ngx.shared.waf_block_ip
|
||||
local exists = block_ip_dict:get(ip)
|
||||
if not exists then
|
||||
ok, err = wafBlackIp:set(ip, 1, rule.ipBlockTime)
|
||||
if ok then
|
||||
ngx.ctx.ip_blocked = true
|
||||
else
|
||||
ngx.log(ngx.ERR, "failed to set key " .. ip, err)
|
||||
end
|
||||
ok, err = block_ip_dict:set(ip, 1, rule.ipBlockTime)
|
||||
elseif rule.ipBlockTime > 0 then
|
||||
ok, err = wafBlackIp:expire(ip, rule.ipBlockTime)
|
||||
if ok then
|
||||
ngx.ctx.ip_blocked = true
|
||||
else
|
||||
ngx.log(ngx.ERR, "failed to expire key " .. ip, err)
|
||||
end
|
||||
ok, err = block_ip_dict:expire(ip, rule.ipBlockTime)
|
||||
end
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "failed to block ip " .. ip, err)
|
||||
end
|
||||
end
|
||||
|
||||
@ -137,9 +122,12 @@ function _M.exec_action(rule_config, match_rule, data)
|
||||
ngx.ctx.exec_rule = rule_config
|
||||
ngx.ctx.hitData = data
|
||||
ngx.ctx.is_attack = true
|
||||
|
||||
ngx.ctx.ip_blocked= false
|
||||
|
||||
if rule_config.ipBlock and rule_config.ipBlock == 'on' then
|
||||
_M.block_ip(ngx.ctx.ip, rule_config)
|
||||
if _M.block_ip(ngx.ctx.ip, rule_config) then
|
||||
ngx.ctx.ip_blocked= true
|
||||
end
|
||||
end
|
||||
|
||||
attack_count(rule_config.type)
|
||||
|
@ -18,7 +18,7 @@ function _M.set_access_token(k, v)
|
||||
--local prefix = "ac_token:"
|
||||
--redis_util.set(prefix .. accesstoken, accesstoken, timeout)
|
||||
else
|
||||
local limit = ngx.shared.waf_accesstoken
|
||||
local limit = ngx.shared.waf_limit
|
||||
limit:set(key, value, 7200)
|
||||
end
|
||||
|
||||
@ -51,7 +51,7 @@ function _M.check_access_token()
|
||||
return true
|
||||
end
|
||||
else
|
||||
local limit = ngx.shared.waf_accesstoken
|
||||
local limit = ngx.shared.waf_limit
|
||||
value = limit:get(key)
|
||||
end
|
||||
if value and value == accesstoken then
|
||||
|
@ -3,6 +3,7 @@ local pairs = pairs
|
||||
local insert_table = table.insert
|
||||
local lower_str = string.lower
|
||||
local open_file = io.open
|
||||
local gsub_str = string.gsub
|
||||
local decode = cjson.decode
|
||||
|
||||
local _M = {}
|
||||
@ -54,11 +55,27 @@ function _M.read_file2table(file_path)
|
||||
if file == nil then
|
||||
return nil
|
||||
end
|
||||
str = file:read("*a")
|
||||
local str = file:read("*a")
|
||||
file:close()
|
||||
return decode(str)
|
||||
end
|
||||
|
||||
function _M.read_list2table(filePath)
|
||||
local file, err = open_file(filePath, "r")
|
||||
if not file then
|
||||
ngx.log(ngx.ERR, "Failed to open file ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local t = {}
|
||||
for line in file:lines() do
|
||||
line = gsub_str(line, "[\r\n]", "")
|
||||
insert_table(t, line)
|
||||
end
|
||||
file:close()
|
||||
return t
|
||||
end
|
||||
|
||||
function _M.set_content_to_file(data, file_path)
|
||||
if data == nil or file_path == nil then
|
||||
return
|
||||
|
@ -7,6 +7,8 @@ local geo = require "geoip"
|
||||
local libinjection = require "resty.libinjection"
|
||||
local config = require "config"
|
||||
local utils = require "utils"
|
||||
local ipmatcher = require "resty.ipmatcher"
|
||||
local cjson = require "cjson"
|
||||
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
@ -108,14 +110,33 @@ local function match_ip(ip_rule, ip, ipn)
|
||||
if ipn == ipv4_to_int(ip_rule.ipv4) then
|
||||
return true
|
||||
end
|
||||
|
||||
elseif ip_rule.type == "ipArr" then
|
||||
local ip_start_n = ipv4_to_int(ip_rule.ipStart)
|
||||
local ip_end_n = ipv4_to_int(ip_rule.ipEnd)
|
||||
if is_ip_in_array(ipn, ip_start_n, ip_end_n) then
|
||||
return true
|
||||
end
|
||||
|
||||
elseif ip_rule.type == "ipGroup" then
|
||||
--TODO 匹配 IP 组
|
||||
if ip_rule.ipGroup == nil or ip_rule.ipGroup == "" then
|
||||
return false
|
||||
end
|
||||
local waf_dict = ngx.shared.waf
|
||||
local ip_group_list = waf_dict:get("ip_group_list")
|
||||
if ip_group_list == nil then
|
||||
return false
|
||||
end
|
||||
local ip_group_obj = cjson.decode(ip_group_list)
|
||||
local ip_group = ip_group_obj[ip_rule.ipGroup]
|
||||
if ip_group == nil then
|
||||
return false
|
||||
end
|
||||
local ip_matcher = ipmatcher.new(ip_group)
|
||||
local ok = ip_matcher:match(ip)
|
||||
if ok then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
@ -243,19 +264,24 @@ function _M.default_ip_black()
|
||||
end
|
||||
|
||||
function _M.black_ip()
|
||||
local ip = ngx.ctx.ip
|
||||
if ip == "unknown" then
|
||||
return false
|
||||
end
|
||||
local exists = false
|
||||
if config.is_redis_on() then
|
||||
exists = redis_util.get("black_ip:" .. ip)
|
||||
else
|
||||
local block_ip_dict = ngx.shared.waf_block_ip
|
||||
exists = block_ip_dict:get(ip)
|
||||
end
|
||||
|
||||
if exists then
|
||||
ngx.exit(444)
|
||||
return true
|
||||
end
|
||||
|
||||
if is_global_state_on("ipBlack") then
|
||||
local ip = ngx.ctx.ip
|
||||
if ip == "unknown" then
|
||||
return false
|
||||
end
|
||||
local exists = false
|
||||
|
||||
if config.is_redis_on() then
|
||||
exists = redis_util.get("black_ip:" .. ip)
|
||||
else
|
||||
exists = ngx.shared.waf_black_ip:get(ip)
|
||||
end
|
||||
|
||||
local ip_black_list = get_global_rules("ipBlack")
|
||||
local ipn = ipv4_to_int(ip)
|
||||
for _, ip_rule in pairs(ip_black_list) do
|
||||
@ -268,11 +294,7 @@ function _M.black_ip()
|
||||
if exists then
|
||||
exec_action(get_global_config("ipBlack"))
|
||||
end
|
||||
|
||||
return exists
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function _M.method_check()
|
||||
@ -352,20 +374,30 @@ function _M.cc()
|
||||
|
||||
if config.is_redis_on() then
|
||||
key = "cc_req_count:" .. key
|
||||
local exist = redis_util.get(key)
|
||||
if exist then
|
||||
ngx.exit(444)
|
||||
return
|
||||
end
|
||||
local count, _ = redis_util.incr(key, cc_config.duration)
|
||||
if not count then
|
||||
redis_util.set(key, 1, cc_config.duration)
|
||||
elseif count > cc_config.threshold then
|
||||
exec_action(cc_config, { rule = cc_config.rule })
|
||||
elseif count >= cc_config.threshold then
|
||||
exec_action(cc_config)
|
||||
return
|
||||
end
|
||||
else
|
||||
local block_ip_dict = ngx.shared.waf_block_ip
|
||||
local exists = block_ip_dict:get(ip)
|
||||
if exists then
|
||||
ngx.exit(444)
|
||||
end
|
||||
local limit = ngx.shared.waf_limit
|
||||
local count, _ = limit:incr(key, 1, 0, cc_config.duration)
|
||||
if not count then
|
||||
limit:set(key, 1, cc_config.duration)
|
||||
elseif count > cc_config.threshold then
|
||||
exec_action(cc_config, { rule = cc_config.rule })
|
||||
elseif count >= cc_config.threshold then
|
||||
exec_action(cc_config)
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -615,7 +647,6 @@ function _M.acl()
|
||||
if rule.state == nil or rule.state == "off" then
|
||||
goto continue
|
||||
end
|
||||
ngx.log(ngx.ERR,"acl rule: "..rule.name .. "state"..rule.state)
|
||||
local conditions = rule.conditions
|
||||
local match = true
|
||||
local condition_rule = ""
|
||||
|
407
plugins/openresty/waf/lib/resty/ipmatcher.lua
Normal file
407
plugins/openresty/waf/lib/resty/ipmatcher.lua
Normal file
@ -0,0 +1,407 @@
|
||||
local base = require("resty.core.base")
|
||||
local bit = require("bit")
|
||||
local clear_tab = require("table.clear")
|
||||
local nkeys = require("table.nkeys")
|
||||
local new_tab = base.new_tab
|
||||
local find_str = string.find
|
||||
local tonumber = tonumber
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local ffi = require "ffi"
|
||||
local ffi_cdef = ffi.cdef
|
||||
local ffi_copy = ffi.copy
|
||||
local ffi_new = ffi.new
|
||||
local C = ffi.C
|
||||
local insert_tab = table.insert
|
||||
local sort_tab = table.sort
|
||||
local string = string
|
||||
local setmetatable=setmetatable
|
||||
local type = type
|
||||
local error = error
|
||||
local str_sub = string.sub
|
||||
local str_byte = string.byte
|
||||
local cur_level = ngx.config.subsystem == "http" and
|
||||
require "ngx.errlog" .get_sys_filter_level()
|
||||
|
||||
local AF_INET = 2
|
||||
local AF_INET6 = 10
|
||||
if ffi.os == "OSX" then
|
||||
AF_INET6 = 30
|
||||
elseif ffi.os == "BSD" then
|
||||
AF_INET6 = 28
|
||||
elseif ffi.os == "Windows" then
|
||||
AF_INET6 = 23
|
||||
end
|
||||
|
||||
|
||||
local _M = {_VERSION = 0.3}
|
||||
|
||||
|
||||
ffi_cdef[[
|
||||
int inet_pton(int af, const char * restrict src, void * restrict dst);
|
||||
uint32_t ntohl(uint32_t netlong);
|
||||
]]
|
||||
|
||||
|
||||
local parse_ipv4
|
||||
do
|
||||
local inet = ffi_new("unsigned int [1]")
|
||||
|
||||
function parse_ipv4(ip)
|
||||
if not ip then
|
||||
return false
|
||||
end
|
||||
|
||||
if C.inet_pton(AF_INET, ip, inet) ~= 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
return C.ntohl(inet[0])
|
||||
end
|
||||
end
|
||||
_M.parse_ipv4 = parse_ipv4
|
||||
|
||||
local parse_bin_ipv4
|
||||
do
|
||||
local inet = ffi_new("unsigned int [1]")
|
||||
|
||||
function parse_bin_ipv4(ip)
|
||||
if not ip or #ip ~= 4 then
|
||||
return false
|
||||
end
|
||||
|
||||
ffi_copy(inet, ip, 4)
|
||||
return C.ntohl(inet[0])
|
||||
end
|
||||
end
|
||||
|
||||
local parse_ipv6
|
||||
do
|
||||
local inets = ffi_new("unsigned int [4]")
|
||||
|
||||
function parse_ipv6(ip)
|
||||
if not ip then
|
||||
return false
|
||||
end
|
||||
|
||||
if str_byte(ip, 1, 1) == str_byte('[')
|
||||
and str_byte(ip, #ip) == str_byte(']') then
|
||||
|
||||
-- strip square brackets around IPv6 literal if present
|
||||
ip = str_sub(ip, 2, #ip - 1)
|
||||
end
|
||||
|
||||
if C.inet_pton(AF_INET6, ip, inets) ~= 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
local inets_arr = new_tab(4, 0)
|
||||
for i = 0, 3 do
|
||||
insert_tab(inets_arr, C.ntohl(inets[i]))
|
||||
end
|
||||
return inets_arr
|
||||
end
|
||||
end
|
||||
_M.parse_ipv6 = parse_ipv6
|
||||
|
||||
local parse_bin_ipv6
|
||||
do
|
||||
local inets = ffi_new("unsigned int [4]")
|
||||
|
||||
function parse_bin_ipv6(ip)
|
||||
if not ip or #ip ~= 16 then
|
||||
return false
|
||||
end
|
||||
|
||||
ffi_copy(inets, ip, 16)
|
||||
local inets_arr = new_tab(4, 0)
|
||||
for i = 0, 3 do
|
||||
insert_tab(inets_arr, C.ntohl(inets[i]))
|
||||
end
|
||||
return inets_arr
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local mt = {__index = _M}
|
||||
|
||||
|
||||
local ngx_log = ngx.log
|
||||
local ngx_INFO = ngx.INFO
|
||||
local function log_info(...)
|
||||
if cur_level and ngx_INFO > cur_level then
|
||||
return
|
||||
end
|
||||
|
||||
return ngx_log(ngx_INFO, ...)
|
||||
end
|
||||
|
||||
|
||||
local function split_ip(ip_addr_org)
|
||||
local idx = find_str(ip_addr_org, "/", 1, true)
|
||||
if not idx then
|
||||
return ip_addr_org
|
||||
end
|
||||
|
||||
local ip_addr = str_sub(ip_addr_org, 1, idx - 1)
|
||||
local ip_addr_mask = str_sub(ip_addr_org, idx + 1)
|
||||
return ip_addr, tonumber(ip_addr_mask)
|
||||
end
|
||||
_M.split_ip = split_ip
|
||||
|
||||
|
||||
local idxs = {}
|
||||
local function gen_ipv6_idxs(inets_ipv6, mask)
|
||||
clear_tab(idxs)
|
||||
|
||||
for _, inet in ipairs(inets_ipv6) do
|
||||
local valid_mask = mask
|
||||
if valid_mask > 32 then
|
||||
valid_mask = 32
|
||||
end
|
||||
|
||||
if valid_mask == 32 then
|
||||
insert_tab(idxs, inet)
|
||||
else
|
||||
insert_tab(idxs, bit.rshift(inet, 32 - valid_mask))
|
||||
end
|
||||
|
||||
mask = mask - 32
|
||||
if mask <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return idxs
|
||||
end
|
||||
|
||||
|
||||
local function cmp(x, y)
|
||||
return x > y
|
||||
end
|
||||
|
||||
|
||||
local function new(ips, with_value)
|
||||
if not ips or type(ips) ~= "table" then
|
||||
error("missing valid ip argument", 2)
|
||||
end
|
||||
|
||||
local parsed_ipv4s = {}
|
||||
local parsed_ipv4s_mask = {}
|
||||
local ipv4_match_all_value
|
||||
|
||||
local parsed_ipv6s = {}
|
||||
local parsed_ipv6s_mask = {}
|
||||
local ipv6_values = {}
|
||||
local ipv6s_values_idx = 1
|
||||
local ipv6_match_all_value
|
||||
|
||||
local iter = with_value and pairs or ipairs
|
||||
for a, b in iter(ips) do
|
||||
local ip_addr_org, value
|
||||
if with_value then
|
||||
ip_addr_org = a
|
||||
value = b
|
||||
|
||||
else
|
||||
ip_addr_org = b
|
||||
value = true
|
||||
end
|
||||
|
||||
local ip_addr, ip_addr_mask = split_ip(ip_addr_org)
|
||||
|
||||
local inet_ipv4 = parse_ipv4(ip_addr)
|
||||
if inet_ipv4 then
|
||||
ip_addr_mask = ip_addr_mask or 32
|
||||
if ip_addr_mask == 32 then
|
||||
parsed_ipv4s[inet_ipv4] = value
|
||||
|
||||
elseif ip_addr_mask == 0 then
|
||||
ipv4_match_all_value = value
|
||||
|
||||
else
|
||||
local valid_inet_addr = bit.rshift(inet_ipv4, 32 - ip_addr_mask)
|
||||
|
||||
parsed_ipv4s_mask[ip_addr_mask] = parsed_ipv4s_mask[ip_addr_mask] or {}
|
||||
parsed_ipv4s_mask[ip_addr_mask][valid_inet_addr] = value
|
||||
log_info("ipv4 mask: ", ip_addr_mask,
|
||||
" valid inet: ", valid_inet_addr)
|
||||
end
|
||||
|
||||
goto continue
|
||||
end
|
||||
|
||||
local inets_ipv6 = parse_ipv6(ip_addr)
|
||||
if inets_ipv6 then
|
||||
ip_addr_mask = ip_addr_mask or 128
|
||||
if ip_addr_mask == 128 then
|
||||
parsed_ipv6s[ip_addr] = value
|
||||
|
||||
elseif ip_addr_mask == 0 then
|
||||
ipv6_match_all_value = value
|
||||
end
|
||||
|
||||
parsed_ipv6s[ip_addr_mask] = parsed_ipv6s[ip_addr_mask] or {}
|
||||
|
||||
local inets_idxs = gen_ipv6_idxs(inets_ipv6, ip_addr_mask)
|
||||
local node = parsed_ipv6s[ip_addr_mask]
|
||||
for i, inet in ipairs(inets_idxs) do
|
||||
if i == #inets_idxs then
|
||||
if with_value then
|
||||
ipv6_values[ipv6s_values_idx] = value
|
||||
node[inet] = ipv6s_values_idx
|
||||
ipv6s_values_idx = ipv6s_values_idx + 1
|
||||
else
|
||||
node[inet] = true
|
||||
end
|
||||
end
|
||||
node[inet] = node[inet] or {}
|
||||
node = node[inet]
|
||||
end
|
||||
|
||||
parsed_ipv6s_mask[ip_addr_mask] = true
|
||||
|
||||
goto continue
|
||||
end
|
||||
|
||||
if not inet_ipv4 and not inets_ipv6 then
|
||||
return nil, "invalid ip address: " .. ip_addr
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
local ipv4_mask_arr = new_tab(nkeys(parsed_ipv4s_mask), 0)
|
||||
local i = 1
|
||||
for k, _ in pairs(parsed_ipv4s_mask) do
|
||||
ipv4_mask_arr[i] = k
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
sort_tab(ipv4_mask_arr, cmp)
|
||||
|
||||
local ipv6_mask_arr = new_tab(nkeys(parsed_ipv6s_mask), 0)
|
||||
|
||||
i = 1
|
||||
for k, _ in pairs(parsed_ipv6s_mask) do
|
||||
ipv6_mask_arr[i] = k
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
sort_tab(ipv6_mask_arr, cmp)
|
||||
|
||||
return setmetatable({
|
||||
ipv4 = parsed_ipv4s,
|
||||
ipv4_mask = parsed_ipv4s_mask,
|
||||
ipv4_mask_arr = ipv4_mask_arr,
|
||||
ipv4_match_all_value = ipv4_match_all_value,
|
||||
|
||||
ipv6 = parsed_ipv6s,
|
||||
ipv6_mask = parsed_ipv6s_mask,
|
||||
ipv6_mask_arr = ipv6_mask_arr,
|
||||
ipv6_values = ipv6_values,
|
||||
ipv6_match_all_value = ipv6_match_all_value,
|
||||
}, mt)
|
||||
end
|
||||
|
||||
function _M.new(ips)
|
||||
return new(ips, false)
|
||||
end
|
||||
|
||||
function _M.new_with_value(ips)
|
||||
return new(ips, true)
|
||||
end
|
||||
|
||||
|
||||
local function match_ipv4(self, ip)
|
||||
local ipv4s = self.ipv4
|
||||
local value = ipv4s[ip]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
|
||||
local ipv4_mask = self.ipv4_mask
|
||||
if self.ipv4_match_all_value ~= nil then
|
||||
return self.ipv4_match_all_value -- match any ip
|
||||
end
|
||||
|
||||
for _, mask in ipairs(self.ipv4_mask_arr) do
|
||||
local valid_inet_addr = bit.rshift(ip, 32 - mask)
|
||||
|
||||
log_info("ipv4 mask: ", mask,
|
||||
" valid inet: ", valid_inet_addr)
|
||||
|
||||
value = ipv4_mask[mask][valid_inet_addr]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function match_ipv6(self, ip)
|
||||
local ipv6s = self.ipv6
|
||||
if self.ipv6_match_all_value ~= nil then
|
||||
return self.ipv6_match_all_value -- match any ip
|
||||
end
|
||||
|
||||
for _, mask in ipairs(self.ipv6_mask_arr) do
|
||||
local node = ipv6s[mask]
|
||||
local inet_idxs = gen_ipv6_idxs(ip, mask)
|
||||
for _, inet in ipairs(inet_idxs) do
|
||||
if not node[inet] then
|
||||
break
|
||||
else
|
||||
node = node[inet]
|
||||
if node == true then
|
||||
return true
|
||||
end
|
||||
if type(node) == "number" then
|
||||
-- fetch with the ipv6s_values_idx
|
||||
return self.ipv6_values[node]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function _M.match(self, ip)
|
||||
local inet_ipv4 = parse_ipv4(ip)
|
||||
if inet_ipv4 then
|
||||
return match_ipv4(self, inet_ipv4)
|
||||
end
|
||||
|
||||
local inets_ipv6 = parse_ipv6(ip)
|
||||
if not inets_ipv6 then
|
||||
return false, "invalid ip address, not ipv4 and ipv6"
|
||||
end
|
||||
|
||||
local ipv6s = self.ipv6
|
||||
local value = ipv6s[ip]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
|
||||
return match_ipv6(self, inets_ipv6)
|
||||
end
|
||||
|
||||
|
||||
function _M.match_bin(self, bin_ip)
|
||||
local inet_ipv4 = parse_bin_ipv4(bin_ip)
|
||||
if inet_ipv4 then
|
||||
return match_ipv4(self, inet_ipv4)
|
||||
end
|
||||
|
||||
local inets_ipv6 = parse_bin_ipv6(bin_ip)
|
||||
if not inets_ipv6 then
|
||||
return false, "invalid ip address, not ipv4 and ipv6"
|
||||
end
|
||||
|
||||
return match_ipv6(self, inets_ipv6)
|
||||
end
|
||||
|
||||
|
||||
return _M
|
@ -208,7 +208,7 @@ end
|
||||
|
||||
local function count_req_status(is_attack)
|
||||
local status = ngx.status
|
||||
local req_count = ngx.shared.dict_req_count
|
||||
local req_count = ngx.shared.waf_req_count
|
||||
add_count(req_count, "req_count")
|
||||
if (status >= 400 and status < 500) then
|
||||
add_count(req_count, "count_4xx")
|
||||
|
@ -16,6 +16,7 @@
|
||||
}
|
||||
],
|
||||
"action": "deny",
|
||||
"code": 403,
|
||||
"res": "",
|
||||
"ipBlock": "off",
|
||||
"ipBlockTime": 60,
|
||||
|
@ -3,23 +3,8 @@
|
||||
{
|
||||
"name": "拦截IP",
|
||||
"state": "on",
|
||||
"type": "ipv4",
|
||||
"ipv4": "123",
|
||||
"description": "拦截IP"
|
||||
},
|
||||
{
|
||||
"name": "拦截IP",
|
||||
"state": "on",
|
||||
"type": "ipv6",
|
||||
"ipv6": "kjdhsakjdhsakjdhakshd",
|
||||
"description": "拦截IP"
|
||||
},
|
||||
{
|
||||
"name": "拦截IP",
|
||||
"state": "on",
|
||||
"type": "ipArr",
|
||||
"ipStart": "192.168.1.1",
|
||||
"ipEnd": "192.168.1.10",
|
||||
"type": "ipGroup",
|
||||
"ipGroup": "test",
|
||||
"description": "拦截IP"
|
||||
}
|
||||
]
|
||||
|
@ -1,9 +1,4 @@
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"state": "on",
|
||||
"type": "ipv4",
|
||||
"ipv4": "123"
|
||||
}
|
||||
]
|
||||
}
|
1
plugins/openresty/waf/rules/ip_group/test
Normal file
1
plugins/openresty/waf/rules/ip_group/test
Normal file
@ -0,0 +1 @@
|
||||
192.168.1.1
|
@ -117,17 +117,13 @@ local function waf_api()
|
||||
if not body_data then
|
||||
return false
|
||||
end
|
||||
ngx.log(ngx.ERR,"1111")
|
||||
local args
|
||||
if body_data then
|
||||
args = cjson.decode(body_data)
|
||||
end
|
||||
ngx.log(ngx.ERR,"2222")
|
||||
if args == nil or args.token == nil then
|
||||
return false
|
||||
end
|
||||
ngx.log(ngx.ERR,"token",args.token)
|
||||
ngx.log(ngx.ERR,"config token",config.get_token())
|
||||
if args.token ~= config.get_token() then
|
||||
return false
|
||||
end
|
||||
@ -136,11 +132,17 @@ local function waf_api()
|
||||
config.load_config_file()
|
||||
ngx.exit(200)
|
||||
end
|
||||
if uri == '/get_black_ip' then
|
||||
if uri == '/get_block_ip' then
|
||||
--TODO 从 redis 获取黑名单
|
||||
local data = ngx.shared.waf_black_ip:get_keys(0)
|
||||
local block_ip_dict = ngx.shared.waf_block_ip
|
||||
local data = block_ip_dict:get_keys(0)
|
||||
return_json(encode(data))
|
||||
end
|
||||
if uri == '/remove_block_ip' and args.ip then
|
||||
local block_ip_dict = ngx.shared.waf_block_ip
|
||||
block_ip_dict:delete(args.ip)
|
||||
ngx.exit(200)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -166,6 +168,8 @@ if config.is_waf_on() then
|
||||
lib.default_ua_black()
|
||||
|
||||
--lib.cc_url()
|
||||
lib.cc()
|
||||
|
||||
if lib.is_white_url() then
|
||||
return true
|
||||
end
|
||||
@ -175,7 +179,6 @@ if config.is_waf_on() then
|
||||
lib.allow_location_check()
|
||||
lib.method_check()
|
||||
lib.acl()
|
||||
lib.cc()
|
||||
--lib.bot_check()
|
||||
lib.args_check()
|
||||
lib.cookie_check()
|
||||
|
@ -5,7 +5,7 @@ local config = require "config"
|
||||
uuid.seed()
|
||||
|
||||
local update_req_count = function()
|
||||
local req_count = ngx.shared.dict_req_count
|
||||
local req_count = ngx.shared.waf_req_count
|
||||
local req_count_update = req_count:get("req_count") or 0
|
||||
req_count:set("req_count", 0)
|
||||
local count_4xx_update = req_count:get("count_4xx") or 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user