feat(waf): 增加内部接口请求安全性 (#4224)

This commit is contained in:
zhengkunwang 2024-03-18 17:36:08 +08:00 committed by GitHub
parent 94028d97cf
commit d7d7c7fd73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 259 additions and 55 deletions

View File

@ -0,0 +1,94 @@
{
"waf": {
"state": "on",
"mode": "protection",
"secret": "qwer1234"
},
"args": {
"state": "on",
"type": "args",
"code": 403,
"action": "deny"
},
"defaultUaBlack": {
"type": "defaultUaBlack",
"state": "on",
"code": 403,
"action": "deny"
},
"cookie": {
"state": "on",
"type": "cookie",
"code": 403,
"action": "deny"
},
"bot": {
"type": "bot",
"state": "on",
"uri": "/1pwaf/bot/trap",
"action": "deny",
"ipBlock": "off",
"ipBlockTime": 600
},
"geoRestrict": {
"state": "off",
"rules": [
"CN"
],
"action": "allow"
},
"defaultIpBlack": {
"state": "on",
"type": "defaultIpBlack",
"code": 403,
"action": "deny"
},
"xss": {
"state": "on",
"type": "xss",
"code": 403,
"action": "deny"
},
"sql": {
"state": "on",
"type": "sql",
"code": 403,
"action": "deny"
},
"cc": {
"state": "on",
"type": "cc",
"rule": "cc",
"tokenTimeOut": 1800,
"threshold": 300,
"duration": 60,
"action": "deny",
"ipBlock": "on",
"ipBlockTime": 600
},
"ccurl": {
"state": "on",
"type": "ccurl",
"action": "deny",
"ipBlock": "off",
"ipBlockTime": 600
},
"fileExt": {
"state": "on",
"action": "deny",
"code": 403,
"type": "fileExtCheck"
},
"header": {
"state": "on",
"type": "header",
"code": 403,
"action": "deny"
},
"defaultUrlBlack": {
"type": "defaultUrlBlack",
"state": "on",
"code": 403,
"action": "deny"
}
}

View File

@ -1,10 +1,11 @@
local file_utils = require "file" local file_utils = require "file"
local lfs = require "lfs" local lfs = require "lfs"
local cjson = require "cjson" local utils = require "utils"
local read_rule = file_utils.read_rule local read_rule = file_utils.read_rule
local read_file2string = file_utils.read_file2string local read_file2string = file_utils.read_file2string
local read_file2table = file_utils.read_file2table local read_file2table = file_utils.read_file2table
local set_content_to_json_file = file_utils.set_content_to_json_file
local list_dir = lfs.dir local list_dir = lfs.dir
local attributes = lfs.attributes local attributes = lfs.attributes
local match_str = string.match local match_str = string.match
@ -16,6 +17,7 @@ local site_dir = waf_dir .. 'sites/'
local _M = {} local _M = {}
local config = {} local config = {}
local global_config = {}
local function init_sites_config() local function init_sites_config()
local site_config = {} local site_config = {}
@ -55,9 +57,7 @@ local function init_sites_config()
end end
end end
end end
ngx.log(ngx.NOTICE, "Load config" .. cjson.encode(site_config))
config.site_config = site_config config.site_config = site_config
ngx.log(ngx.NOTICE, "Load rules" .. cjson.encode(site_rules))
config.site_rules = site_rules config.site_rules = site_rules
end end
@ -69,9 +69,19 @@ local function ini_waf_info()
end end
local function init_global_config() local function init_global_config()
local global_config = read_file2table(config_dir .. 'global.json') local global_config_file = config_dir .. 'global.json'
global_config = file_utils.read_file2table(global_config_file)
local token = utils.random_string(20)
global_config["waf"]["token"] = token
local waf_dict = ngx.shared.waf
waf_dict:set("token", token, 7200)
set_content_to_json_file(global_config,global_config_file)
config.global_config = global_config config.global_config = global_config
config.isProtectionMode = global_config["mode"] == "protection" and true or false config.isProtectionMode = global_config["mode"] == "protection" and true or false
local rules = {} local rules = {}
rules.uaBlack = read_rule(global_rule_dir, "uaBlack") rules.uaBlack = read_rule(global_rule_dir, "uaBlack")
@ -156,4 +166,17 @@ function _M.get_secret()
return config.global_config["waf"]["secret"] return config.global_config["waf"]["secret"]
end end
function _M.get_token()
local waf_dict = ngx.shared.waf
local token = waf_dict:get("token")
if not token then
token = utils.random_string(20)
waf_dict:set("token", token, 86400)
global_config["waf"]["token"] = token
local global_config_file = config_dir .. 'global.json'
set_content_to_json_file(global_config,global_config_file)
end
return token
end
return _M return _M

View File

@ -5,6 +5,9 @@ local format_str = string.format
local _M = {} local _M = {}
local function deny(status_code, res) local function deny(status_code, res)
if not status_code then
status_code = 403
end
ngx.status = status_code ngx.status = status_code
if res then if res then
ngx.header.content_type = "text/html; charset=UTF-8" ngx.header.content_type = "text/html; charset=UTF-8"
@ -38,11 +41,11 @@ end
function _M.block_ip(ip, rule) function _M.block_ip(ip, rule)
local ok, err = nil, nil local ok, err = nil, nil
local msg = "拉黑IP : " .. ip .. "国家 " .. ngx.ctx.ip_location.country["zh"] --local msg = "拉黑IP : " .. ip .. "国家 " .. ngx.ctx.ip_location.country["zh"]
if rule then --if rule then
msg = msg .. " 规则 " .. rule.type -- msg = msg .. " 规则 " .. rule.type
end --end
ngx.log(ngx.ERR, msg) --ngx.log(ngx.ERR, msg)
if config.redis_on then if config.redis_on then
local red, err1 = redis_util.get_conn() local red, err1 = redis_util.get_conn()
@ -140,26 +143,22 @@ function _M.exec_action(rule_config, match_rule, data)
attack_count(rule_config.type) attack_count(rule_config.type)
local msg = "访问 IP " .. ngx.ctx.ip .. " 访问 URL" .. ngx.var.uri .. " 触发动作 " .. action .. " 规则类型 " .. rule_config.type --local msg = "访问 IP " .. ngx.ctx.ip .. " 访问 URL" .. ngx.var.uri .. " 触发动作 " .. action .. " 规则类型 " .. rule_config.type
if match_rule then --if match_rule then
if match_rule.type then -- if match_rule.type then
msg = msg .. " 触发规则类型 " .. match_rule.type -- msg = msg .. " 触发规则类型 " .. match_rule.type
else -- else
msg = msg .. " 触发规则 " .. match_rule.rule -- msg = msg .. " 触发规则 " .. match_rule.rule
end -- end
end --end
--
ngx.log(ngx.ERR, msg) --ngx.log(ngx.ERR, msg)
if action == "allow" then if action == "allow" then
return return
elseif action == "deny" then elseif action == "deny" then
if rule_config.code and rule_config.code ~= 444 then deny(rule_config.code, rule_config.res)
deny(rule_config.code, rule_config.res)
else
deny(444)
end
elseif action == "slide" then elseif action == "slide" then
slide() slide()
@ -167,7 +166,7 @@ function _M.exec_action(rule_config, match_rule, data)
five_second() five_second()
else else
redirect(444) redirect(403)
end end
end end

View File

@ -59,6 +59,16 @@ function _M.read_file2table(file_path)
return decode(str) return decode(str)
end end
function _M.set_content_to_json_file(data, file_path)
local json_str = cjson.encode(data)
local file = open_file(file_path, "w")
if file then
file:write(json_str)
file:close()
end
end
function _M.read_file2string(file_path, binary) function _M.read_file2string(file_path, binary)
if not file_path then if not file_path then
ngx.log(ngx.ERR, "No file found ", file_path) ngx.log(ngx.ERR, "No file found ", file_path)

View File

@ -143,7 +143,6 @@ end
local function xss_and_sql_check(kv) local function xss_and_sql_check(kv)
if type(kv) ~= 'string' then if type(kv) ~= 'string' then
return return
end end
if is_site_state_on("xss") then if is_site_state_on("xss") then
local is_xss, fingerprint = libinjection.xss(tostring(kv)) local is_xss, fingerprint = libinjection.xss(tostring(kv))
@ -161,7 +160,6 @@ local function xss_and_sql_check(kv)
return return
end end
end end
end end
@ -464,12 +462,13 @@ function _M.args_check()
val_arr = concat_table(val, ", ") val_arr = concat_table(val, ", ")
end end
if val_arr and type(val_arr) ~= "boolean" and val_arr ~= "" then if val_arr and type(val_arr) ~= "boolean" and val_arr ~= "" then
local m, mr = match_rule(args_list, utils.unescape_uri(val_arr)) local check_value = utils.unescape_uri(val_arr)
xss_and_sql_check(check_value)
local m, mr = match_rule(args_list,check_value)
if m then if m then
exec_action(get_global_config("args"), mr) exec_action(get_global_config("args"), mr)
return return
end end
xss_and_sql_check(val_arr)
end end
end end
end end
@ -495,21 +494,19 @@ function _M.header_check()
local headers_config = get_site_config("header") local headers_config = get_site_config("header")
local referer = ngx.var.http_referer local referer = ngx.var.http_referer
if referer and referer ~= "" then if referer and referer ~= "" then
local m = match_rule(headers_rule, referer) local check_value = utils.unescape_uri(referer)
local m = match_rule(headers_rule, check_value)
if m then if m then
exec_action(headers_config) exec_action(headers_config)
end end
xss_and_sql_check(check_value)
end end
local headers = utils.get_headers() local headers = utils.get_headers()
if headers then if headers then
for k, v in pairs(headers) do for _, v in pairs(headers) do
local m1, mr1 = match_rule(headers_rule, k) local m, mr = match_rule(headers_rule, v)
if m1 then if m then
exec_action(headers_config, mr1) exec_action(headers_config, mr)
end
local m2, mr2 = match_rule(headers_rule, v)
if m2 then
exec_action(headers_config, mr2)
end end
end end
end end

View File

@ -8,7 +8,8 @@ local type = type
local find_str = string.find local find_str = string.find
local gmatch_str = string.gmatch local gmatch_str = string.gmatch
local pcall = pcall local pcall = pcall
local cjson = require "cjson" local random = math.random
local unescape_uri = ngx.unescape_uri
local _M = {} local _M = {}
@ -34,7 +35,7 @@ end
function _M.unescape_uri(str) function _M.unescape_uri(str)
local newStr = str local newStr = str
for t = 1, 2 do for t = 1, 2 do
local temp = ngx.unescape_uri(newStr) local temp = unescape_uri(newStr)
if not temp then if not temp then
break break
end end
@ -128,14 +129,14 @@ function _M.get_ip_location(ip)
else else
geoip.init() geoip.init()
local geo_res = geoip.lookup(ip) local geo_res = geoip.lookup(ip)
local msg = "访问 IP " .. ip --local msg = "访问 IP " .. ip
if geo_res.country then --if geo_res.country then
msg = msg .. " 国家 " .. cjson.encode(geo_res.country) -- msg = msg .. " 国家 " .. cjson.encode(geo_res.country)
end --end
if geo_res.province then --if geo_res.province then
msg = msg .. " 省份 " .. cjson.encode(geo_res.province) -- msg = msg .. " 省份 " .. cjson.encode(geo_res.province)
end --end
ngx.log(ngx.ERR, msg) --ngx.log(ngx.ERR, msg)
return geo_res return geo_res
end end
@ -182,4 +183,18 @@ function _M.get_wafdb(waf_db_path)
end end
return sqlite3.open(waf_db_path) return sqlite3.open(waf_db_path)
end end
math.randomseed(os.time())
function _M.random_string(length)
local charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
local str = ""
for i = 1, length do
local rand_index = random(1, #charset)
str = str .. sub_str(charset, rand_index, rand_index)
end
return str
end
return _M return _M

View File

@ -160,10 +160,10 @@ local function write_req_log(attack)
wafdb:execute([[COMMIT]]) wafdb:execute([[COMMIT]])
local error_msg = wafdb:errmsg() --local error_msg = wafdb:errmsg()
if error_msg then --if error_msg then
ngx.log(ngx.ERR, "insert attack_log error ", error_msg .. " ") -- ngx.log(ngx.ERR, "insert attack_log error ", error_msg .. " ")
end --end
end end

View File

@ -72,6 +72,54 @@
"name": "appFilter4", "name": "appFilter4",
"type": "appFilter" "type": "appFilter"
}, },
{
"state": "on",
"rule": "/password_change.cgi",
"name": "appFilter5",
"type": "appFilter"
},
{
"state": "on",
"rule": "/service/extdirect",
"name": "appFilter6",
"type": "appFilter"
},
{
"state": "on",
"rule": "/api/jsonws/invoke",
"name": "appFilter7",
"type": "appFilter"
},
{
"state": "on",
"rule": "/jars/upload",
"name": "appFilter8",
"type": "appFilter"
},
{
"state": "on",
"rule": "/example/tree/a/search",
"name": "appFilter9",
"type": "appFilter"
},
{
"state": "on",
"rule": "/actuator/gateway/routes/hacktest",
"name": "appFilter10",
"type": "appFilter"
},
{
"state": "on",
"rule": "/api/v1/method.callAnon/getPasswordPolicy",
"name": "appFilter11",
"type": "appFilter"
},
{
"state": "on",
"rule": "/functionRouter",
"name": "appFilter12",
"type": "appFilter"
},
{ {
"state": "on", "state": "on",
"rule": "(?:(union(.*?)select))", "rule": "(?:(union(.*?)select))",

View File

@ -1,4 +1,3 @@
local geoip = require "geoip"
local lib = require "lib" local lib = require "lib"
local file_utils = require "file" local file_utils = require "file"
local config = require "config" local config = require "config"
@ -104,10 +103,28 @@ local function waf_api()
if uri == "/5s_check_" .. ngx.md5(ngx.ctx.ip) .. ".js" then if uri == "/5s_check_" .. ngx.md5(ngx.ctx.ip) .. ".js" then
return_js("five_second_js") return_js("five_second_js")
end end
local method = ngx.req.get_method()
if method ~= 'POST' then
return false
end
if ngx.var.remote_addr ~= '127.0.0.1' then if ngx.var.remote_addr ~= '127.0.0.1' then
return false return false
end end
ngx.req.read_body()
local body_data = ngx.req.get_body_data()
if not body_data then
return false
end
local args
if body_data then
args = cjson.decode(body_data)
end
if args == nil or args.token == nil then
return false
end
if args.token ~= config.get_token() then
return false
end
if uri == '/reload_waf_config' then if uri == '/reload_waf_config' then
config.load_config_file() config.load_config_file()
ngx.exit(200) ngx.exit(200)
@ -119,6 +136,7 @@ local function waf_api()
end end
end end
if config.is_waf_on() then if config.is_waf_on() then
init() init()
waf_api() waf_api()