mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-23 18:49:21 +08:00
feat(waf): 增加内部接口请求安全性 (#4224)
This commit is contained in:
parent
94028d97cf
commit
d7d7c7fd73
94
plugins/openresty/waf/conf/siteConfig.json
Normal file
94
plugins/openresty/waf/conf/siteConfig.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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))",
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user