From d7d7c7fd736ffebe9ca7b9a96c65235a84b00ff8 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Mon, 18 Mar 2024 17:36:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(waf):=20=E5=A2=9E=E5=8A=A0=E5=86=85?= =?UTF-8?q?=E9=83=A8=E6=8E=A5=E5=8F=A3=E8=AF=B7=E6=B1=82=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=80=A7=20(#4224)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/openresty/waf/conf/siteConfig.json | 94 +++++++++++++++++++ plugins/openresty/waf/config.lua | 31 +++++- plugins/openresty/waf/lib/action.lua | 43 +++++---- plugins/openresty/waf/lib/file.lua | 10 ++ plugins/openresty/waf/lib/lib.lua | 23 ++--- plugins/openresty/waf/lib/utils.lua | 35 +++++-- plugins/openresty/waf/log_and_traffic.lua | 8 +- .../openresty/waf/rules/defaultUrlBlack.json | 48 ++++++++++ plugins/openresty/waf/waf.lua | 22 ++++- 9 files changed, 259 insertions(+), 55 deletions(-) create mode 100644 plugins/openresty/waf/conf/siteConfig.json diff --git a/plugins/openresty/waf/conf/siteConfig.json b/plugins/openresty/waf/conf/siteConfig.json new file mode 100644 index 000000000..25e78eec3 --- /dev/null +++ b/plugins/openresty/waf/conf/siteConfig.json @@ -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" + } +} \ No newline at end of file diff --git a/plugins/openresty/waf/config.lua b/plugins/openresty/waf/config.lua index 9d839393f..5ec0b4a0c 100644 --- a/plugins/openresty/waf/config.lua +++ b/plugins/openresty/waf/config.lua @@ -1,10 +1,11 @@ local file_utils = require "file" local lfs = require "lfs" -local cjson = require "cjson" +local utils = require "utils" 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_json_file = file_utils.set_content_to_json_file local list_dir = lfs.dir local attributes = lfs.attributes local match_str = string.match @@ -16,6 +17,7 @@ local site_dir = waf_dir .. 'sites/' local _M = {} local config = {} +local global_config = {} local function init_sites_config() local site_config = {} @@ -55,9 +57,7 @@ local function init_sites_config() end end end - ngx.log(ngx.NOTICE, "Load config" .. cjson.encode(site_config)) config.site_config = site_config - ngx.log(ngx.NOTICE, "Load rules" .. cjson.encode(site_rules)) config.site_rules = site_rules end @@ -69,9 +69,19 @@ local function ini_waf_info() end 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.isProtectionMode = global_config["mode"] == "protection" and true or false + local rules = {} rules.uaBlack = read_rule(global_rule_dir, "uaBlack") @@ -156,4 +166,17 @@ function _M.get_secret() return config.global_config["waf"]["secret"] 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 \ No newline at end of file diff --git a/plugins/openresty/waf/lib/action.lua b/plugins/openresty/waf/lib/action.lua index 85eefef7d..76b99c4c3 100644 --- a/plugins/openresty/waf/lib/action.lua +++ b/plugins/openresty/waf/lib/action.lua @@ -5,6 +5,9 @@ local format_str = string.format local _M = {} local function deny(status_code, res) + if not status_code then + status_code = 403 + end ngx.status = status_code if res then ngx.header.content_type = "text/html; charset=UTF-8" @@ -38,11 +41,11 @@ 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 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) - local msg = "访问 IP " .. ngx.ctx.ip .. " 访问 URL" .. ngx.var.uri .. " 触发动作 " .. action .. " 规则类型 " .. rule_config.type - if match_rule then - if match_rule.type then - msg = msg .. " 触发规则类型 " .. match_rule.type - else - msg = msg .. " 触发规则 " .. match_rule.rule - end - end - - ngx.log(ngx.ERR, msg) + --local msg = "访问 IP " .. ngx.ctx.ip .. " 访问 URL" .. ngx.var.uri .. " 触发动作 " .. action .. " 规则类型 " .. rule_config.type + --if match_rule then + -- if match_rule.type then + -- msg = msg .. " 触发规则类型 " .. match_rule.type + -- else + -- msg = msg .. " 触发规则 " .. match_rule.rule + -- end + --end + -- + --ngx.log(ngx.ERR, msg) if action == "allow" then return elseif action == "deny" then - if rule_config.code and rule_config.code ~= 444 then - deny(rule_config.code, rule_config.res) - else - deny(444) - end - + deny(rule_config.code, rule_config.res) + elseif action == "slide" then slide() @@ -167,7 +166,7 @@ function _M.exec_action(rule_config, match_rule, data) five_second() else - redirect(444) + redirect(403) end end diff --git a/plugins/openresty/waf/lib/file.lua b/plugins/openresty/waf/lib/file.lua index b402c1795..ecda723a0 100644 --- a/plugins/openresty/waf/lib/file.lua +++ b/plugins/openresty/waf/lib/file.lua @@ -59,6 +59,16 @@ function _M.read_file2table(file_path) return decode(str) 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) if not file_path then ngx.log(ngx.ERR, "No file found ", file_path) diff --git a/plugins/openresty/waf/lib/lib.lua b/plugins/openresty/waf/lib/lib.lua index 96497b3a0..2cc90cf72 100644 --- a/plugins/openresty/waf/lib/lib.lua +++ b/plugins/openresty/waf/lib/lib.lua @@ -143,7 +143,6 @@ end local function xss_and_sql_check(kv) if type(kv) ~= 'string' then return - end if is_site_state_on("xss") then local is_xss, fingerprint = libinjection.xss(tostring(kv)) @@ -161,7 +160,6 @@ local function xss_and_sql_check(kv) return end end - end @@ -464,12 +462,13 @@ function _M.args_check() val_arr = concat_table(val, ", ") end 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 exec_action(get_global_config("args"), mr) return end - xss_and_sql_check(val_arr) end end end @@ -495,21 +494,19 @@ function _M.header_check() local headers_config = get_site_config("header") local referer = ngx.var.http_referer 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 exec_action(headers_config) end + xss_and_sql_check(check_value) end local headers = utils.get_headers() if headers then - for k, v in pairs(headers) do - local m1, mr1 = match_rule(headers_rule, k) - if m1 then - exec_action(headers_config, mr1) - end - local m2, mr2 = match_rule(headers_rule, v) - if m2 then - exec_action(headers_config, mr2) + for _, v in pairs(headers) do + local m, mr = match_rule(headers_rule, v) + if m then + exec_action(headers_config, mr) end end end diff --git a/plugins/openresty/waf/lib/utils.lua b/plugins/openresty/waf/lib/utils.lua index b6ab0403a..b9b8ad251 100644 --- a/plugins/openresty/waf/lib/utils.lua +++ b/plugins/openresty/waf/lib/utils.lua @@ -8,7 +8,8 @@ local type = type local find_str = string.find local gmatch_str = string.gmatch local pcall = pcall -local cjson = require "cjson" +local random = math.random +local unescape_uri = ngx.unescape_uri local _M = {} @@ -34,7 +35,7 @@ end function _M.unescape_uri(str) local newStr = str for t = 1, 2 do - local temp = ngx.unescape_uri(newStr) + local temp = unescape_uri(newStr) if not temp then break end @@ -128,14 +129,14 @@ function _M.get_ip_location(ip) else geoip.init() local geo_res = geoip.lookup(ip) - local msg = "访问 IP " .. ip - if geo_res.country then - msg = msg .. " 国家 " .. cjson.encode(geo_res.country) - end - if geo_res.province then - msg = msg .. " 省份 " .. cjson.encode(geo_res.province) - end - ngx.log(ngx.ERR, msg) + --local msg = "访问 IP " .. ip + --if geo_res.country then + -- msg = msg .. " 国家 " .. cjson.encode(geo_res.country) + --end + --if geo_res.province then + -- msg = msg .. " 省份 " .. cjson.encode(geo_res.province) + --end + --ngx.log(ngx.ERR, msg) return geo_res end @@ -182,4 +183,18 @@ function _M.get_wafdb(waf_db_path) end return sqlite3.open(waf_db_path) 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 diff --git a/plugins/openresty/waf/log_and_traffic.lua b/plugins/openresty/waf/log_and_traffic.lua index be81d416f..6949c9431 100644 --- a/plugins/openresty/waf/log_and_traffic.lua +++ b/plugins/openresty/waf/log_and_traffic.lua @@ -160,10 +160,10 @@ local function write_req_log(attack) wafdb:execute([[COMMIT]]) - local error_msg = wafdb:errmsg() - if error_msg then - ngx.log(ngx.ERR, "insert attack_log error ", error_msg .. " ") - end + --local error_msg = wafdb:errmsg() + --if error_msg then + -- ngx.log(ngx.ERR, "insert attack_log error ", error_msg .. " ") + --end end diff --git a/plugins/openresty/waf/rules/defaultUrlBlack.json b/plugins/openresty/waf/rules/defaultUrlBlack.json index 3beecbdb3..4e189066f 100644 --- a/plugins/openresty/waf/rules/defaultUrlBlack.json +++ b/plugins/openresty/waf/rules/defaultUrlBlack.json @@ -72,6 +72,54 @@ "name": "appFilter4", "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", "rule": "(?:(union(.*?)select))", diff --git a/plugins/openresty/waf/waf.lua b/plugins/openresty/waf/waf.lua index 396d18c2e..40c480450 100644 --- a/plugins/openresty/waf/waf.lua +++ b/plugins/openresty/waf/waf.lua @@ -1,4 +1,3 @@ -local geoip = require "geoip" local lib = require "lib" local file_utils = require "file" local config = require "config" @@ -104,10 +103,28 @@ local function waf_api() if uri == "/5s_check_" .. ngx.md5(ngx.ctx.ip) .. ".js" then return_js("five_second_js") end - + local method = ngx.req.get_method() + if method ~= 'POST' then + return false + end if ngx.var.remote_addr ~= '127.0.0.1' then return false 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 config.load_config_file() ngx.exit(200) @@ -119,6 +136,7 @@ local function waf_api() end end + if config.is_waf_on() then init() waf_api()