diff --git a/plugins/openresty/waf/conf/global.json b/plugins/openresty/waf/conf/global.json deleted file mode 100644 index e53bfe670..000000000 --- a/plugins/openresty/waf/conf/global.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "waf": { - "state": "on", - "mode": "protection", - "secret": "qwer1234" - }, - "redis": { - "state": "off", - "host": "127.0.0.1", - "port": 6379, - "password": "Calong@2015", - "ssl": false, - "poolSize": 10 - }, - "ipWhite": { - "state": "on", - "type": "ipWhite", - "action": "allow" - }, - "ipBlack": { - "state": "on", - "code": 403, - "action": "deny", - "type": "ipBlack", - "res": "ip" - }, - "urlWhite": { - "type": "urlWhite", - "state": "on", - "action": "allow" - }, - "urlBlack": { - "type": "urlBlack", - "state": "on", - "code": 403, - "action": "deny" - }, - "uaWhite": { - "type": "uaWhite", - "state": "off", - "action": "allow" - }, - "uaBlack": { - "type": "uaBlack", - "state": "on", - "code": 403, - "action": "deny" - }, - "notFoundCount": { - "state": "off", - "type": "notFoundCount", - "threshold": 10, - "duration": 60, - "action": "deny", - "ipBlock": "on", - "ipBlockTime": 600 - }, - "methodWhite": { - "type": "methodWhite", - "state": "on", - "code": 444, - "action": "deny" - }, - "bot": { - "state": "on", - "type": "bot", - "uri": "/1pwaf/bot/trap", - "action": "REDIRECT_JS", - "ipBlock": "on", - "ipBlockTime": 600 - }, - "geoRestrict": { - "state": "on", - "rules": [], - "code": 444, - "action": "deny", - "type": "geoRestrict" - }, - "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": "off", - "type": "cc", - "tokenTimeOut": 1800, - "threshold": 120, - "duration": 60, - "action": "deny", - "ipBlock": "on", - "ipBlockTime": 600 - }, - "ccurl": { - "state": "off", - "type": "urlcc", - "action": "deny", - "ipBlock": "on", - "ipBlockTime": 600 - }, - "attackCount": { - "state": "off", - "type": "attackCount", - "threshold": 20, - "duration": 60, - "action": "deny", - "ipBlock": "on", - "ipBlockTime": 600 - }, - "fileExt": { - "state": "on", - "action": "deny", - "code": 403, - "type": "fileExtCheck" - }, - "cookie": { - "type": "cookie", - "state": "on", - "code": 403, - "action": "deny" - }, - "header": { - "state": "on", - "type": "header", - "code": 403, - "action": "deny" - }, - "defaultUaBlack": { - "type": "defaultUaBlack", - "state": "on", - "code": 403, - "action": "deny" - }, - "defaultUrlBlack": { - "type": "defaultUrlBlack", - "state": "on", - "code": 403, - "action": "deny" - }, - "args": { - "type": "args", - "state": "on", - "code": 403, - "action": "deny" - } -} diff --git a/plugins/openresty/waf/conf/siteConfig.json b/plugins/openresty/waf/conf/siteConfig.json deleted file mode 100644 index 25e78eec3..000000000 --- a/plugins/openresty/waf/conf/siteConfig.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "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/conf/token b/plugins/openresty/waf/conf/token deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/openresty/waf/conf/waf.conf b/plugins/openresty/waf/conf/waf.conf deleted file mode 100644 index 2d69226c7..000000000 --- a/plugins/openresty/waf/conf/waf.conf +++ /dev/null @@ -1,13 +0,0 @@ -lua_shared_dict dict_locks 100k; -lua_shared_dict waf_req_count 10m; -lua_shared_dict waf 30m; -lua_shared_dict waf_block_ip 10m; -lua_shared_dict waf_ip_arr 10m; -lua_shared_dict waf_limit 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; -access_by_lua_file /usr/local/openresty/1pwaf/waf.lua; -log_by_lua_file /usr/local/openresty/1pwaf/log_and_traffic.lua; -init_worker_by_lua_file /usr/local/openresty/1pwaf/worker.lua; diff --git a/plugins/openresty/waf/config.lua b/plugins/openresty/waf/config.lua deleted file mode 100644 index 0e9cb3056..000000000 --- a/plugins/openresty/waf/config.lua +++ /dev/null @@ -1,215 +0,0 @@ -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 - -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 = {} -local global_config = {} - -local function init_sites_config() - local site_config = {} - local site_rules = {} - for entry in list_dir(site_dir) do - if entry ~= "." and entry ~= ".." then - local site_path = site_dir .. entry .. "/" - if attributes(site_path, "mode") == "directory" then - local site_key = entry - for s_entry in list_dir(site_path) do - local s_entry_path = site_path .. s_entry - if attributes(s_entry_path, "mode") == "file" and s_entry == "config.json" then - local s_config = read_file2table(s_entry_path) - site_config[site_key] = s_config - end - if attributes(s_entry_path, "mode") == "directory" and s_entry == "rules" then - local s_rules = {} - local rule_dir = s_entry_path .. "/" - for r_file in list_dir(rule_dir) do - if r_file ~= "." and r_file ~= ".." then - local rule_path = rule_dir .. r_file - local rule_type = match_str(r_file, "(.-)%.json$") - if attributes(rule_path, "mode") == "file" then - 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 - s_rules[rule_type] = s_rule - end - end - end - site_rules[site_key] = s_rules - end - end - end - end - end - config.site_config = site_config - config.site_rules = site_rules -end - -local function ini_waf_info() - local waf_info = read_file2table(waf_dir .. 'waf.json') - if waf_info then - ngx.log(ngx.NOTICE, "Load " .. waf_info.name .. " Version:" .. waf_info.version) - 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 ok, err = cache:set("ip_group_list", { - ipc_shm = "ipc_shared_dict", - },ip_group_list) - if not ok then - ngx.log(ngx.ERR, "Failed to set config",err) - end -end - -local function init_global_config() - local global_config_file = config_dir .. 'global.json' - global_config = file_utils.read_file2table(global_config_file) - config.global_config = global_config - config.isProtectionMode = global_config["mode"] == "protection" and true or false - - _M.get_token() - - local rules = {} - rules.uaBlack = read_rule(global_rule_dir, "uaBlack") - 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.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 = {} - local htmDir = waf_dir .. "html/" - html_res.slide = read_file2string(htmDir .. "slide.html") - html_res.slide_js = read_file2string(htmDir .. "slide.js") - html_res.five_second = read_file2string(htmDir .. "5s.html") - html_res.five_second_js = read_file2string(htmDir .. "5s.js") - html_res.redirect = read_file2string(htmDir .. "redirect.html") - html_res.ip = read_file2string(htmDir .. "ip.html") - - config.html_res = html_res - - _M.waf_dir = waf_dir - _M.waf_db_dir = waf_dir .. "db/" - _M.waf_db_path = _M.waf_db_dir .. "1pwaf.db" - _M.waf_log_db_path = _M.waf_db_dir .. "req_log.db" - _M.config_dir = config_dir - -end - -function _M.load_config_file() - ini_waf_info() - init_global_config() - init_sites_config() - load_ip_group() - - local ok, err = cache:set("config", { - ipc_shm = "ipc_shared_dict", - },config) - if not ok then - ngx.log(ngx.ERR, "Failed to set config",err) - end -end - -local function get_config() - local cache_config = cache:get("config", { - ipc_shm = "ipc_shared_dict", - }) - if not cache_config then - return config - end - return cache_config -end - -function _M.get_site_config(website_key) - return get_config().site_config[website_key] -end - -function _M.get_site_rules(website_key) - return get_config().site_rules[website_key] -end - -function _M.get_global_config(name) - return get_config().global_config[name] -end - -function _M.get_global_rules(name) - return get_config().global_rules[name] -end - -function _M.is_global_state_on(name) - return get_config().global_config[name]["state"] == "on" and true or false -end - -function _M.is_site_state_on(name) - return get_config().site_config[name]["state"] == "on" and true or false -end - -function _M.get_redis_config() - return get_config().global_config["redis"] -end - -function _M.get_html_res(name) - return get_config().html_res[name] -end - -function _M.is_waf_on() - return _M.is_global_state_on("waf") -end - -function _M.is_redis_on() - return _M.is_global_state_on("redis") -end - -function _M.get_secret() - return get_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) - local token_path = config_dir .. 'token' - set_content_to_file(token,token_path) - end - return token -end - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/db.lua b/plugins/openresty/waf/db.lua deleted file mode 100644 index 792030adb..000000000 --- a/plugins/openresty/waf/db.lua +++ /dev/null @@ -1,119 +0,0 @@ -local config = require "config" - -local open_file = io.open -local exec = os.execute -local pcall = pcall - -local _M = {} - -local function init_dir(path) - local file = open_file(path, "rb") - if not file then - exec("mkdir -p " .. path) - end -end - -local function check_table(table_name,wafdb) - if wafdb == nil then - return false - end - local stmt = wafdb:prepare("SELECT COUNT(*) FROM sqlite_master where type='table' and name=?") - local rows = 0 - if stmt ~= nil then - stmt:bind_values(table_name) - stmt:step() - rows = stmt:get_uvalues() - stmt:finalize() - end - return rows > 0 -end - -local function init_db_config(db_path) - local ok, sqlite3 = pcall(function() - return require "lsqlite3" - end) - if not ok then - return false - end - local wafdb = sqlite3.open(db_path) - if wafdb == nil then - return false - end - wafdb:exec([[PRAGMA journal_mode = wal]]) - wafdb:exec([[PRAGMA synchronous = OFF]]) - wafdb:exec([[PRAGMA page_size = 8192]]) - wafdb:exec([[PRAGMA journal_size_limit = 2147483648]]) - return wafdb -end - -function _M.init() - init_dir(config.waf_db_dir) - local wafdb = init_db_config(config.waf_db_path) - if not wafdb then - return false - end - - local status = {} - if not check_table("waf_stat",wafdb) then - status = wafdb:exec([[ - CREATE TABLE waf_stat ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - day TEXT, - req_count INTEGER, - attack_count INTEGER, - count4xx INTEGER, - count5xx INTEGER, - create_date DATETIME - )]]) - ngx.log(ngx.ERR, "init waf_stat status"..status) - end - - local logdb = init_db_config(config.waf_log_db_path) - if not check_table("req_logs",logdb) then - status = logdb:exec([[ - CREATE TABLE req_logs ( - id TEXT PRIMARY KEY, - ip TEXT, - ip_iso TEXT, - ip_country_zh TEXT, - ip_country_en TEXT, - ip_province_zh TEXT, - ip_province_en TEXT, - ip_longitude TEXT, - ip_latitude TEXT, - localtime DATETIME, - server_name TEXT, - website_key TEXT, - host TEXT, - method TEXT, - uri TEXT, - user_agent TEXT, - exec_rule TEXT, - rule_type TEXT, - match_rule TEXT, - match_value TEXT, - nginx_log TEXT, - blocking_time INTEGER, - action TEXT, - is_block INTEGER, - is_attack INTEGER - )]]) - end - - if not check_table("block_ips",logdb) then - status = logdb:exec([[ - CREATE TABLE block_ips ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - ip TEXT, - is_block INTEGER, - blocking_time INTEGER, - req_log_id TEXT, - create_date DATETIME - )]]) - ngx.log(ngx.ERR, "init block_ip status"..status) - end - - ngx.log(ngx.ERR, "init db success") -end - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/html/5s.html b/plugins/openresty/waf/html/5s.html deleted file mode 100644 index 245ad7708..000000000 --- a/plugins/openresty/waf/html/5s.html +++ /dev/null @@ -1,21 +0,0 @@ - - - -5s - - - -
-
正在验证...
-
验证成功
-
-
- - - diff --git a/plugins/openresty/waf/html/5s.js b/plugins/openresty/waf/html/5s.js deleted file mode 100644 index 7be80bc94..000000000 --- a/plugins/openresty/waf/html/5s.js +++ /dev/null @@ -1,24 +0,0 @@ -window.onload = function () { - setTimeout(function () { - showSuccess(); - verifySucc(); - }, 5000); - - function showSuccess() { - document.getElementById("loadingText").style.display = "none"; - document.getElementById("loadingSuccess").style.display = "block"; - document.querySelector(".loadingSpinner").style.display = "none"; - } - - function verifySucc() { - let xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4 && xhr.status === 200) { - window.location.reload(); - } - }; - const requestUrl = "%s-%s-%s"; - xhr.open("GET", requestUrl, true); - xhr.send(); - } -} \ No newline at end of file diff --git a/plugins/openresty/waf/html/ip.html b/plugins/openresty/waf/html/ip.html deleted file mode 100644 index 95b3c4f02..000000000 --- a/plugins/openresty/waf/html/ip.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - 访问被拒绝 - - - -
-
无法访问
-
-

很抱歉,您的 IP 已被禁止访问

-
    -
  1. 如被误封,请联系网站管理员解封
  2. -
-
- -
- - diff --git a/plugins/openresty/waf/html/redirect.html b/plugins/openresty/waf/html/redirect.html deleted file mode 100644 index 64c776b47..000000000 --- a/plugins/openresty/waf/html/redirect.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - 网站防火墙 - - - -
-
网站防火墙
-
-

您的请求不合法,已被拒绝

-
- -
- - diff --git a/plugins/openresty/waf/html/slide.html b/plugins/openresty/waf/html/slide.html deleted file mode 100644 index 2b62dfbad..000000000 --- a/plugins/openresty/waf/html/slide.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -滑动认证 - - - -
-
-
-
-
-
-
- - - diff --git a/plugins/openresty/waf/html/slide.js b/plugins/openresty/waf/html/slide.js deleted file mode 100644 index d5df91929..000000000 --- a/plugins/openresty/waf/html/slide.js +++ /dev/null @@ -1 +0,0 @@ -window.onload=function(){(function(){const dragContainer=document.getElementById("dragContainer");const dragBg=document.getElementById("dragBg");const dragText=document.getElementById("dragText");const dragHandler=document.getElementById("dragHandler");const maxHandleOffset=dragContainer.clientWidth-dragHandler.clientWidth;let isVertifySucc=false;initDrag();function initDrag(){dragText.textContent="拖动滑块验证";dragHandler.addEventListener("mousedown",onDragStart);dragHandler.addEventListener("touchstart",onDragStart)}function onDragStart(a){a.preventDefault();if(a.type==="mousedown"||(a.type==="touchstart"&&a.touches.length===1)){document.addEventListener("mousemove",onDragMove);document.addEventListener("touchmove",onDragMove);document.addEventListener("mouseup",onDragEnd);document.addEventListener("touchend",onDragEnd)}}function onDragMove(a){let clientX;if(a.type==="mousemove"){clientX=a.clientX}else if(a.type==="touchmove"&&a.touches.length===1){clientX=a.touches[0].clientX}let containerOffsetX=clientX-dragContainer.getBoundingClientRect().left;let left=containerOffsetX-dragHandler.clientWidth/2;if(left<0){left=0}else if(left>maxHandleOffset){left=maxHandleOffset}dragHandler.style.left=left+"px";dragBg.style.width=dragHandler.style.left}function onDragEnd(){document.removeEventListener("mousemove",onDragMove);document.removeEventListener("touchmove",onDragMove);document.removeEventListener("mouseup",onDragEnd);document.removeEventListener("touchend",onDragEnd);if(!isVertifySucc){let left=dragHandler.offsetLeft;if(left>=maxHandleOffset){verifySucc()}else{dragHandler.style.left="0px";dragBg.style.width="0px"}}}function verifySucc(){isVertifySucc=true;dragText.textContent="验证通过";dragText.style.color="white";dragHandler.setAttribute("class","dragHandlerOkBg");dragHandler.removeEventListener("mousedown",onDragStart);dragHandler.removeEventListener("touchstart",onDragStart);let xhr=new XMLHttpRequest();xhr.onreadystatechange=function(){if(xhr.readyState===4&&xhr.status===200){window.location.reload()}};const requestUrl="%s-%s-%s";xhr.open("GET",requestUrl,true);xhr.send()}})()}; \ No newline at end of file diff --git a/plugins/openresty/waf/init.lua b/plugins/openresty/waf/init.lua deleted file mode 100644 index e856ce256..000000000 --- a/plugins/openresty/waf/init.lua +++ /dev/null @@ -1,21 +0,0 @@ -local db = require "db" -local config = require "config" -local mlcache = require "resty.mlcache" - -local cache, err = mlcache.new("config", "waf", { - lru_size = 1000, - ipc_shm = "ipc_shared_dict", -}) -if not cache then - error("could not create mlcache: " .. err) -end -_G.cache = cache - - -config.load_config_file() -db.init() - - - - - diff --git a/plugins/openresty/waf/lib/action.lua b/plugins/openresty/waf/lib/action.lua deleted file mode 100644 index d5313ea8d..000000000 --- a/plugins/openresty/waf/lib/action.lua +++ /dev/null @@ -1,167 +0,0 @@ -local config = require "config" -local redis_util = require "redis_util" -local format_str = string.format - -local _M = {} - -local function deny(status_code, res) - if status_code == nil then - status_code = 403 - end - - ngx.status = status_code - if res ~= nil and res ~= "" then - ngx.header.content_type = "text/html; charset=UTF-8" - ngx.say(config.get_html_res(res)) - end - ngx.exit(ngx.status) -end - -local function redirect(status_code) - ngx.header.content_type = "text/html; charset=UTF-8" - ngx.say(config.get_html_res("redirect")) - ngx.status = status_code - ngx.exit(ngx.status) -end - -local function slide() - ngx.header.content_type = "text/html; charset=UTF-8" - ngx.header.Cache_Control = "no-cache" - ngx.status = 200 - ngx.say(format_str(config.get_html_res("slide"), ngx.md5(ngx.ctx.ip))) - ngx.exit(ngx.status) -end - -local function five_second() - ngx.header.content_type = "text/html; charset=UTF-8" - ngx.header.Cache_Control = "no-cache" - ngx.status = 200 - ngx.say(format_str(config.get_html_res("five_second"), ngx.md5(ngx.ctx.ip))) - ngx.exit(ngx.status) -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) - - 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) - end - if rule.ipBlockTime > 0 then - ok, err = red:expire(key, rule.ipBlockTime) - end - if not ok then - ngx.log(ngx.ERR, "failed to expire redis key " .. key, err) - end - redis_util.close_conn(red) - else - local block_ip_dict = ngx.shared.waf_block_ip - local exists = block_ip_dict:get(ip) - if not exists then - ok, err = block_ip_dict:set(ip, 1, rule.ipBlockTime) - elseif rule.ipBlockTime > 0 then - 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 - - return ok -end - -local function attack_count(config_type) - if config_type == "ipBlack" then - return - end - if config.is_global_state_on("attackCount") then - local ip = ngx.ctx.ip - local attack_config = config.get_global_config("attackCount") - local key = ip - - if config.is_redis_on() then - key = "cc_attack_count:" .. key - local count, _ = redis_util.incr(key, attack_config.duration) - if not count then - redis_util.set(key, 1, attack_config.duration) - elseif count >= attack_config.threshold then - _M.block_ip(ip, attack_config) - return - end - else - key = ip .. "attack" - local limit = ngx.shared.waf_limit - local count, _ = limit:incr(key, 1, 0, attack_config.duration) - - if not count then - limit:set(key, 1, attack_config.duration) - elseif count >= attack_config.threshold then - _M.block_ip(ip, attack_config) - return - end - end - end -end - -function _M.exec_action(rule_config, match_rule, data) - local action = rule_config.action - if match_rule then - rule_config.match_rule = match_rule - end - 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 - if _M.block_ip(ngx.ctx.ip, rule_config) then - ngx.ctx.ip_blocked= true - end - end - - 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) - if action == "allow" then - return - - elseif action == "deny" then - if rule_config.code and rule_config.res then - deny(rule_config.code, rule_config.res) - else - ngx.exit(403) - end - - elseif action == "slide" then - slide() - - elseif action == "fives" then - five_second() - - else - redirect(403) - end - -end - -return _M diff --git a/plugins/openresty/waf/lib/cc.lua b/plugins/openresty/waf/lib/cc.lua deleted file mode 100644 index 2c96e4d1b..000000000 --- a/plugins/openresty/waf/lib/cc.lua +++ /dev/null @@ -1,67 +0,0 @@ -local config = require "config" -local redis_util = require "redis_util" -local utils = require "utils" - -local _M = {} - -function _M.set_access_token(k, v) - local secret = config.get_secret() - local key = ngx.md5(ngx.ctx.ip .. ngx.var.server_name .. ngx.ctx.website_key - .. ngx.ctx.ua .. ngx.ctx.today .. secret) - local value = ngx.md5(ngx.time() .. ngx.ctx.ip) - --TODO check value - if key ~= k then - ngx.exit(444) - end - ngx.log(ngx.ERR, "set cc key: ", key) - if config.redis_on then - --local prefix = "ac_token:" - --redis_util.set(prefix .. accesstoken, accesstoken, timeout) - else - local limit = ngx.shared.waf_limit - limit:set(key, value, 7200) - end - - local cookie_expire = ngx.cookie_time(ngx.time() + 86400) - ngx.header['Set-Cookie'] = { key .. '=' .. value .. '; path=/; Expires=' .. cookie_expire } - ngx.exit(200) -end - -function _M.check_access_token() - local secret = config.get_secret() - local key = ngx.md5(ngx.ctx.ip .. ngx.var.server_name .. ngx.ctx.website_key - .. ngx.ctx.ua .. ngx.ctx.today .. secret) - if not ngx.var.http_cookie then - return false - end - local cookies = utils.get_cookie_list(ngx.var.http_cookie) - if not cookies then - return false - end - if not cookies[key] then - return false - end - local accesstoken = cookies[key] - local value = nil - - if config.redis_on then - local prefix = "ac_token:" - value = redis_util.get(prefix .. key) - if value and value == accesstoken then - return true - end - else - local limit = ngx.shared.waf_limit - value = limit:get(key) - end - if value and value == accesstoken then - return true - end - return false -end - -function _M.clear_access_token() - ngx.header['Set-Cookie'] = { 'a_token=; path=/; Expires=Thu, 01-Jan-1970 00:00:00 GMT' } -end - -return _M diff --git a/plugins/openresty/waf/lib/file.lua b/plugins/openresty/waf/lib/file.lua deleted file mode 100644 index 81eb1d324..000000000 --- a/plugins/openresty/waf/lib/file.lua +++ /dev/null @@ -1,122 +0,0 @@ -local cjson = require "cjson" -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 = {} - -function _M.read_rule(file_path, file_name, read_all) - local file, err = open_file(file_path .. file_name .. ".json", "r") - if not file then - ngx.log(ngx.ERR, "Failed to open file ", err) - return - end - - local rules_table = {} - local other_table = {} - local text = file:read('*a') - - file:close() - - if #text > 0 then - local result = decode(text) - - if result then - for key, value in pairs(result) do - if key == "rules" then - for _, r in pairs(value) do - if read_all then - r.hits = 0 - r.totalHits = 0 - insert_table(rules_table, r) - else - if lower_str(r.state) == 'on' then - r.hits = 0 - r.totalHits = 0 - insert_table(rules_table, r) - end - end - end - else - other_table[key] = value - end - end - end - end - - return rules_table, other_table -end - -function _M.read_file2table(file_path) - local file = open_file(file_path, 'r') - if file == nil then - return nil - end - 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 - end - local file = open_file(file_path, "w") - if file then - file:write(data) - 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) - return - end - - local mode = "r" - if binary == true then - mode = "rb" - end - - local file, err = open_file(file_path, mode) - if not file then - ngx.log(ngx.ERR, "Failed to open file ", err) - return - end - - local content = "" - repeat - local chunk = file:read(8192) -- 读取 8KB 的块 - if chunk then - content = content .. chunk - else - break - end - until not chunk - - file:close() - return content -end - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/lib/geoip.lua b/plugins/openresty/waf/lib/geoip.lua deleted file mode 100644 index 6f2edeb79..000000000 --- a/plugins/openresty/waf/lib/geoip.lua +++ /dev/null @@ -1,51 +0,0 @@ -local geo = require "resty.maxminddb" - -local pcall = pcall - -local _M = {} -local geo_ip_file = "/usr/local/openresty/1pwaf/data/GeoIP.mmdb" -local black_ip_file = "/usr/local/openresty/1pwaf/data/BlackIP.mmdb" - -function _M.init() - if not geo.initted() then - geo.init({ - geo_ip = geo_ip_file, - black_ip = black_ip_file - }) - end -end - -function _M.is_default_black_ip(ip) - local pass, res, err = pcall(geo.lookup, "black_ip", ip) - if not pass then - ngx.log(ngx.ERR, 'failed to lookup black ip,reason:', err) - elseif res and res['isBlack'] then - return true - end - return false -end - -function _M.lookup(ip) - local geo_res = { - iso = "", - country = "", - city = "", - longitude = 0, - latitude = 0, - province = "" - } - local pass, res, err = pcall(geo.lookup, "geo_ip", ip) - if not pass then - ngx.log(ngx.ERR, 'failed to lookup by ip,reason:', err) - elseif res and res['iso'] then - geo_res.iso = res['iso'] - geo_res.country = res['country'] - geo_res.province = res['province'] - geo_res.longitude = res['longitude'] - geo_res.latitude = res['latitude'] - return geo_res - end - return geo_res -end - -return _M diff --git a/plugins/openresty/waf/lib/lib.lua b/plugins/openresty/waf/lib/lib.lua deleted file mode 100644 index f1e8c7924..000000000 --- a/plugins/openresty/waf/lib/lib.lua +++ /dev/null @@ -1,720 +0,0 @@ -local redis_util = require "redis_util" -local action = require "action" -local cc = require "cc" -local file_utils = require "file" -local ck = require "resty.cookie" -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 -local tostring = tostring -local type = type -local next = next -local concat_table = table.concat -local ngx_re_find = ngx.re.find -local ngx_re_gmatch = ngx.re.gmatch -local ngx_re_match = ngx.re.match -local ipv4_to_int = utils.ipv4_to_int -local is_ip_in_array = utils.is_ip_in_array -local is_ipv6 = utils.is_ipv6 - -local exec_action = action.exec_action - -local _M = {} - -local function is_global_state_on(name) - return config.is_global_state_on(name) -end - -local function is_site_state_on(name) - local site_config = config.get_site_config(ngx.ctx.website_key) - if site_config ~= nil then - return site_config[name]["state"] == "on" and true or false - end - return true -end - -local function is_state_on(name) - return is_site_state_on(name) and is_global_state_on(name) -end - -local function get_site_config(name) - local site_config = config.get_site_config(ngx.ctx.website_key) - if site_config ~= nil then - return site_config[name] - end - return config.get_global_config(name) -end - -local function get_site_rule(name) - local site_rules = config.get_site_rules(ngx.ctx.website_key) - if site_rules ~= nil then - return site_rules[name] - end - return config.get_global_rules(name) -end - -local function get_global_rules(name) - return config.get_global_rules(name) -end - -local function get_global_config(name) - return config.get_global_config(name) -end - -local function is_rule_state_on(rule_table) - return rule_table["state"] == "on" and true or false -end - -local function matches(input, regex, ctx, nth) - if not nth then - nth = 0 - end - return ngx_re_find(input, regex, "isjo", ctx, nth) -end - -local function match_rule(rule_table, str) - if str == nil or next(rule_table) == nil then - return false - end - for _, t in ipairs(rule_table) do - if matches(str, t.rule) then - return true, t - end - end - - return false -end - -local function match_ip(ip_rule, ip, ipn) - if ip_rule == nil or ip == nil then - return false - end - if is_rule_state_on(ip_rule) == false then - return false - end - local ip_rule_type = ip_rule.type - if is_ipv6(ip) and ip_rule_type == "ipv6" then - if ip == ip_rule.ipv6 then - return true - end - return false - end - - if ip_rule.type == "ipv4" then - 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 - if ip_rule.ipGroup == nil or ip_rule.ipGroup == "" then - return false - end - local ip_group_list = cache:get("ip_group_list", { - ipc_shm = "ipc_shared_dict", - }) - if ip_group_list == nil then - return false - end - local ip_group = ip_group_list[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 -end - -local function get_boundary() - local header = utils.get_headers()["content-type"] - if not header then - return nil - end - - if type(header) == "table" then - header = header[1] - end - - local m = ngx_re_match(header, ";%s*boundary=\"([^\"]+)\"") - if m then - return m - end - - return ngx_re_match(header, ";%s*boundary=([^\",;]+)") -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)) - local xss_config = get_site_config("xss") - if is_xss then - exec_action(xss_config, { rule = kv }) - return - end - end - if is_site_state_on("sql") then - local is_sqli, fingerprint = libinjection.sqli(tostring(kv)) - local sql_config = get_site_config("sql") - if is_sqli then - exec_action(sql_config, { rule = kv }) - return - end - end -end - - -local function get_request_body() - ngx.req.read_body() - local body_data = ngx.req.get_body_data() - if not body_data then - local body_file = ngx.req.get_body_file() - if body_file then - body_data = file_utils.read_file2string(body_file, true) - end - end - return body_data -end - -function _M.is_white_ip() - if is_global_state_on("ipWhite") then - local ip = ngx.ctx.ip - if ip == "unknown" then - return false - end - if ip == "127.0.0.1" then - return true - end - local ipn = utils.ipv4_to_int(ip) - local ip_rules = get_global_rules("ipWhite") - for _, ip_rule in pairs(ip_rules) do - if match_ip(ip_rule, ip, ipn) then - return true - end - end - end - return false -end - -function _M.allow_location_check() - if is_state_on("geoRestrict") then - local ip_location = ngx.ctx.ip_location - if ip_location and ip_location.iso and ip_location.iso ~= "" then - local iso = ip_location.iso - local geo_config = get_site_config("geoRestrict") - local exist = false - for _, rule in ipairs(geo_config.rules) do - if iso == rule then - exist = true - break - - end - end - local default_geo_config = { - action = "deny", - code = 444, - type = "geoRestrict", - state = "on", - rule = iso - } - if exist then - if geo_config.action == "allow" then - return true - end - if geo_config.action == "deny" then - exec_action(default_geo_config, default_geo_config) - return false - end - else - if geo_config.action == "allow" then - exec_action(default_geo_config, default_geo_config) - return false - end - end - end - end -end - -function _M.default_ip_black() - if is_state_on("defaultIpBlack") then - if geo.is_default_black_ip(ngx.ctx.ip) then - exec_action(get_site_config("defaultIpBlack"), { rule = ngx.ctx.ip }) - end - end -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_black_list = get_global_rules("ipBlack") - local ipn = ipv4_to_int(ip) - for _, ip_rule in pairs(ip_black_list) do - if match_ip(ip_rule, ip, ipn) then - exists = true - break - end - end - - if exists then - exec_action(get_global_config("ipBlack")) - end - end -end - -function _M.method_check() - local method = ngx.req.get_method() - local method_white_list = get_site_rule("methodWhite") - for _, method_rule in ipairs(method_white_list) do - if method_rule.rule == method and method_rule.state == 'off' then - local method_config = get_global_config("methodWhite") - exec_action(method_config, method_rule) - return false - end - end - return true -end - -function _M.bot_check() - if is_state_on("bot") then - local ruri = ngx.var.request_uri - local uri = ngx.var.uri - local bot_rule = get_site_config("bot") - if uri == bot_rule.uri or ruri == bot_rule.uri then - exec_action(bot_rule) - end - end -end - -function _M.black_ua() - if is_global_state_on("uaBlack") then - if type(ngx.ctx.ua) ~= 'string' then - ngx.exit(200) - end - local m, mr = match_rule(get_global_rules("uaBlack"), ngx.ctx.ua) - if m then - exec_action(get_global_config("uaBlack"), mr) - end - end -end - -function _M.default_ua_black() - if is_state_on("defaultUaBlack") then - if type(ngx.ctx.ua) ~= 'string' then - ngx.exit(200) - end - local m, mr = match_rule(get_global_rules('defaultUaBlack'), ngx.ctx.ua) - if m then - exec_action(get_global_config('defaultUaBlack'), mr) - end - end -end - -function _M.is_white_ua() - if is_global_state_on("uaWhite") then - local ua = utils.get_header("user-agent") - if not ua then - return false - end - if type(ua) ~= 'string' then - ngx.exit(200) - end - for _, wa in ipairs(get_global_rules("uaWhite")) do - if ngx.ctx.ua == wa then - return true - end - end - end - return false -end - -function _M.cc() - if is_state_on("cc") then - if cc.check_access_token() then - return - end - local ip = ngx.ctx.ip - local cc_config = get_site_config("cc") - local key = ip - - 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) - 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) - return - end - end - end -end - -function _M.cc_url() - if is_state_on("ccurl") then - local ip = ngx.ctx.ip - local key = ip - local urlcc_rules = get_site_rule("ccurl") - local urlcc_config = get_site_config("ccurl") - local uri = ngx.var.uri - - local m, mr = match_rule(urlcc_rules, uri) - if not m or not mr then - return - end - key = uri .. key - if config.is_redis_on() then - key = "url_cc_req_count:" .. key - local count, _ = redis_util.incr(key, mr.duration) - if not count then - redis_util.set(key, 1, mr.duration) - elseif count > mr.threshold then - exec_action(urlcc_config, { rule = mr.rule }) - return - end - else - local limit = ngx.shared.waf_limit - local count, _ = limit:incr(key, 1, 0, mr.duration) - if not count then - limit:set(key, 1, urlcc_config.duration) - elseif count > mr.threshold then - exec_action(urlcc_config, { rule = mr.rule }) - return - end - end - end -end - -function _M.is_white_url() - if is_global_state_on("urlWhite") then - local url = ngx.var.uri - if url == nil or url == " " then - return false - end - local m, _ = match_rule(get_global_rules("urlWhite"), url) - if m then - return true - end - return false - end - - return false -end - -function _M.black_url() - if is_global_state_on("urlBlack") then - local url = ngx.var.uri - if url == nil or url == "" then - return false - end - local m, mr = match_rule(get_global_rules("urlBlack"), url) - if m then - exec_action(get_global_config("urlBlack"), mr) - return - end - end -end - -function _M.default_url_black() - if is_state_on("defaultUrlBlack") then - local url = ngx.var.uri - if url == nil or url == "" then - return false - end - local m, mr = match_rule(get_global_rules('defaultUrlBlack'), url) - if m then - exec_action(get_global_config('defaultUrlBlack'), mr) - return - end - end -end - -function _M.args_check() - if is_state_on("args") then - local args = ngx.req.get_uri_args() - if args then - local args_list = get_global_rules("args") - for _, val in pairs(args) do - local val_arr = val - if type(val) == "table" then - val_arr = concat_table(val, ", ") - end - if val_arr and type(val_arr) ~= "boolean" and val_arr ~= "" then - 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 - end - end - end - end -end - -function _M.cookie_check() - local cookie = ngx.var.http_cookie - if cookie and is_state_on("cookie") then - local cookieList = get_site_rule('cookie') - local m, mr = match_rule(cookieList, cookie) - if m then - exec_action(get_global_config('cookie'), mr) - return true - end - end - return false -end - -function _M.header_check() - if is_state_on("header") then - local headers_rule = get_site_rule("header") - local headers_config = get_site_config("header") - local referer = ngx.var.http_referer - if referer and referer ~= "" then - 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 _, v in pairs(headers) do - local m, mr = match_rule(headers_rule, v) - if m then - exec_action(headers_config, mr) - end - end - end - end -end - -function _M.post_check() - local content_type = ngx.ctx.content_type - local content_length = ngx.ctx.content_length - - if ngx.ctx.method == "GET" or not content_type or type(content_type) ~= 'string' then - return - end - - if content_length == nil or content_length == 0 then - return - end - - local boundary = get_boundary() - - if boundary and is_state_on('fileExt') then - if not ngx_re_match(content_type, '^multipart/form-data; boundary=') or not ngx_re_find(content_type, [[multipart]], 'ijo')then - return - end - local boundary_value = ngx_re_match(content_type, '^multipart/form-data; boundary=(.+)') - if boundary_value == nil then - return - end - local data = get_request_body() - if data == nil then - return - end - local iterator = ngx_re_gmatch(data, [[Content-Disposition.+filename=.+]], 'ijo') - if not iterator then - return - end - - local rule = get_site_rule("fileExt") - while true do - local m = iterator() - if m then - local match = ngx_re_match(m[0], 'Content-Disposition: form-data; (.+)filename="(.+)\\.(.*)"', 'ijo') - if match then - local extension = match[3] - for _, ext in ipairs(rule.rules) do - if extension == ext then - exec_action(rule) - end - end - end - else - break - end - end - else - ngx.req.read_body() - local body_obj = ngx.req.get_post_args() - if not body_obj then - return - end - - for key, val in pairs(body_obj) do - if is_global_state_on("xss") or is_global_state_on("sql") then - xss_and_sql_check(key) - xss_and_sql_check(val) - end - if is_state_on("args") then - local post_rules = get_global_rules("args") - local m, mr = match_rule(post_rules, val) - if m then - exec_action(get_global_config("args"), mr) - return - end - end - end - end - -end - -local function match_acl_rule(match_value, pattern,rule) - if pattern == "eq" then - if match_value == rule then - return true - end - - elseif pattern == "notEq" then - if match_value ~= rule then - return true - end - - elseif pattern == "regex" then - if matches(match_value, rule) then - return true - end - - elseif pattern == "contain" then - if ngx_re_find(match_value, rule, "isjo") then - return true - end - end -end - -function _M.acl() - local rules = get_site_rule("acl") - for _, rule in pairs(rules) do - if rule.state == nil or rule.state == "off" then - goto continue - end - local conditions = rule.conditions - local match = true - local condition_rule = "" - for _, condition in pairs(conditions) do - local field = condition.field - local field_name = condition.name - local pattern = condition.pattern - condition_rule = condition.rule - local match_value = '' - if field == 'URL' then - match_value = ngx.var.request_uri - - elseif field == 'Cookie' then - if field_name ~= nil and field_name ~= '' then - local cookies, _ = ck:new() - if not cookies then - match = false - break - else - match_value, _ = cookies:get(field_name) - end - else - match_value = ngx.var.http_cookie - end - - elseif field == 'Header' then - local headers = ngx.req.get_headers() - if headers then - if field_name ~= nil and field_name ~= '' then - match_value = headers[field_name] - else - match_value = concat_table(headers, '') - end - else - match = false - break - end - - elseif field == 'Referer' then - match_value = ngx.var.http_referer - - elseif field == 'User-Agent' then - match_value = ngx.var.http_user_agent - - elseif field == 'IP' then - match_value = ngx.ctx.ip - end - - if pattern == '' then - match = false - break - end - - if not match_acl_rule(match_value, pattern,condition_rule) then - match = false - break - end - end - if match then - rule.type = "acl" - local mr = { - type = rule.name, - rule = condition_rule - } - exec_action(rule,mr) - end - :: continue :: - end -end - -return _M diff --git a/plugins/openresty/waf/lib/logger.lua b/plugins/openresty/waf/lib/logger.lua deleted file mode 100644 index c79f05448..000000000 --- a/plugins/openresty/waf/lib/logger.lua +++ /dev/null @@ -1,148 +0,0 @@ -local type = type -local concat_table = table.concat -local new_table = table.new -local tostring = tostring -local setmetatable = setmetatable -local open_file = io.open -local ngx_timer_at = ngx.timer.at - -local _M = {} - -local mt = { __index = _M } - -function _M:new(log_path, host, rolling) - local t = { - flush_limit = 4096, -- 4kb - flush_timeout = 1, - - buffered_size = 0, - buffer_index = 0, - buffer_data = new_table(20000, 0), - - log_path = log_path, - prefix = log_path .. host .. '_', - rolling = rolling or false, - host = host, - timer = nil } - - setmetatable(t, mt) - return t -end - -local function needFlush(self) - if self.buffered_size > 0 then - return true - end - - return false -end - -local function flush_lock(self) - local dic_lock = ngx.shared.dict_locks - local locked = dic_lock:get(self.host) - if not locked then - local succ, err = dic_lock:set(self.host, true) - if not succ then - ngx.log(ngx.ERR, "failed to lock logfile " .. self.host .. ": ", err) - end - return succ - end - return false -end - -local function flush_unlock(self) - local dic_lock = ngx.shared.dict_locks - local success, err = dic_lock:set(self.host, false) - if not success then - ngx.log(ngx.ERR, "failed to unlock logfile " .. self.host .. ": ", err) - end - return success -end - -local function write_file(self, value) - local file_name = '' - if self.rolling then - file_name = self.prefix .. ngx.today() .. ".log" - else - file_name = self.log_path - end - - local file = open_file(file_name, "a+") - - if file == nil or value == nil then - return - end - - file:write(value) - file:flush() - file:close() - - return -end - -local function flushBuffer(self) - if not needFlush(self) then - return true - end - - if not flush_lock(self) then - return true - end - - local buffer = concat_table(self.buffer_data, "", 1, self.buffer_index) - write_file(self, buffer) - - self.buffered_size = 0 - self.buffer_index = 0 - self.buffer_data = new_table(20000, 0) - - flush_unlock(self) -end - -local function flushPeriod(premature, self) - flushBuffer(self) - self.timer = false -end - -local function writeBuffer(self, msg, msg_len) - self.buffer_index = self.buffer_index + 1 - - self.buffer_data[self.buffer_index] = msg - - self.buffered_size = self.buffered_size + msg_len - - return self.buffered_size -end - -local function startTimer(self) - if not self.timer then - local ok, err = ngx_timer_at(self.flush_timeout, flushPeriod, self) - if not ok then - ngx.log(ngx.ERR, "failed to create the timer: ", err) - return - end - if ok then - self.timer = true - end - end - return self.timer -end - -function _M:log(msg) - if type(msg) ~= "string" then - msg = tostring(msg) - end - - local msg_len = #msg - local len = msg_len + self.buffered_size - - if len < self.flush_limit then - writeBuffer(self, msg, msg_len) - startTimer(self) - elseif len >= self.flush_limit then - writeBuffer(self, msg, msg_len) - flushBuffer(self) - end -end - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/lib/logger_factory.lua b/plugins/openresty/waf/lib/logger_factory.lua deleted file mode 100644 index 506bb08e8..000000000 --- a/plugins/openresty/waf/lib/logger_factory.lua +++ /dev/null @@ -1,16 +0,0 @@ -local logger = require "logger" - -local loggers = {} - -local _M = {} - -function _M.get_logger(log_path, host, rolling) - local host_logger = loggers[host] - if not host_logger then - host_logger = logger:new(log_path, host, rolling) - loggers[host] = host_logger - end - return host_logger -end - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/lib/redis_util.lua b/plugins/openresty/waf/lib/redis_util.lua deleted file mode 100644 index e260e7422..000000000 --- a/plugins/openresty/waf/lib/redis_util.lua +++ /dev/null @@ -1,131 +0,0 @@ -local redis = require "resty.redis" -local config = require "config" - -local _M = {} - -local connect_timeout, send_timeout, read_timeout = 1000, 1000, 1000 - -function _M.get_conn() - local red, err1 = redis:new() - if not red then - ngx.log(ngx.ERR, "failed to new redis:", err1) - return nil, err1 - end - - local redis_config = config.get_redis_config() - - red:set_timeouts(connect_timeout, send_timeout, read_timeout) - - local ok, err = red:connect(redis_config.host, redis_config.port, { ssl = redis_config.ssl, pool_size = redis_config.poolSize }) - - if not ok then - ngx.log(ngx.ERR, "failed to connect: ", err .. "\n") - return nil, err - end - - if redis_config.password ~= nil and #redis_config.password ~= 0 then - local times = 0 - times, err = red:get_reused_times() - - if times == 0 then - local res, err2 = red:auth(redis_config.password) - if not res then - ngx.log(ngx.ERR, "failed to authenticate: ", err2) - return nil, err2 - end - end - end - - return red, err -end - -function _M.close_conn(red) - local ok, err = red:set_keepalive(10000, 100) - if not ok then - ngx.log(ngx.ERR, "failed to set keepalive: ", err) - end - - return ok, err -end - -function _M.set(key, value, expire_time) - local red, _ = _M.get_conn() - local ok, err = nil, nil - if red then - ok, err = red:set(key, value) - if not ok then - ngx.log(ngx.ERR, "failed to set key: " .. key .. " ", err) - elseif expire_time and expire_time > 0 then - red:expire(key, expire_time) - end - - _M.close_conn(red) - end - - return ok, err -end - -function _M.bath_set(keyTable, value, keyPrefix) - local red, _ = _M.get_conn() - local results, err = nil, nil - if red then - red:init_pipeline() - - if keyPrefix then - for _, ip in ipairs(keyTable) do - red:set(keyPrefix .. ip, value) - end - else - for _, ip in ipairs(keyTable) do - red:set(ip, value) - end - end - - results, err = red:commit_pipeline() - if not results then - ngx.log(ngx.ERR, "failed to set keys: ", err) - end - - _M.close_conn(red) - end - - return results, err -end - -function _M.get(key) - local red, err = _M.get_conn() - local value = nil - if red then - value, err = red:get(key) - if not value then - ngx.log(ngx.ERR, "failed to get key: " .. key, err) - return value, err - end - if value == ngx.null then - value = nil - end - - _M.close_conn(red) - end - - return value, err -end - -function _M.incr(key, expire_time) - local red, err = _M.get_conn() - local res = nil - if red then - res, err = red:incr(key) - if not res then - ngx.log(ngx.ERR, "failed to incr key: " .. key, err) - elseif res == 1 and expire_time and expire_time > 0 then - red:expire(key, expire_time) - end - - _M.close_conn(red) - end - - return res, err -end - -return _M diff --git a/plugins/openresty/waf/lib/resty/cookie.lua b/plugins/openresty/waf/lib/resty/cookie.lua deleted file mode 100644 index f4c81ab0f..000000000 --- a/plugins/openresty/waf/lib/resty/cookie.lua +++ /dev/null @@ -1,213 +0,0 @@ --- Copyright (C) 2013-2016 Jiale Zhi (calio), CloudFlare Inc. --- See RFC6265 http://tools.ietf.org/search/rfc6265 --- require "luacov" - -local type = type -local byte = string.byte -local sub = string.sub -local format = string.format -local log = ngx.log -local ERR = ngx.ERR -local WARN = ngx.WARN -local ngx_header = ngx.header - -local EQUAL = byte("=") -local SEMICOLON = byte(";") -local SPACE = byte(" ") -local HTAB = byte("\t") - --- table.new(narr, nrec) -local ok, new_tab = pcall(require, "table.new") -if not ok then - new_tab = function () return {} end -end - -local ok, clear_tab = pcall(require, "table.clear") -if not ok then - clear_tab = function(tab) for k, _ in pairs(tab) do tab[k] = nil end end -end - -local _M = new_tab(0, 2) - -_M._VERSION = '0.01' - - -local function get_cookie_table(text_cookie) - if type(text_cookie) ~= "string" then - log(ERR, format("expect text_cookie to be \"string\" but found %s", - type(text_cookie))) - return {} - end - - local EXPECT_KEY = 1 - local EXPECT_VALUE = 2 - local EXPECT_SP = 3 - - local n = 0 - local len = #text_cookie - - for i=1, len do - if byte(text_cookie, i) == SEMICOLON then - n = n + 1 - end - end - - local cookie_table = new_tab(0, n + 1) - - local state = EXPECT_SP - local i = 1 - local j = 1 - local key, value - - while j <= len do - if state == EXPECT_KEY then - if byte(text_cookie, j) == EQUAL then - key = sub(text_cookie, i, j - 1) - state = EXPECT_VALUE - i = j + 1 - end - elseif state == EXPECT_VALUE then - if byte(text_cookie, j) == SEMICOLON - or byte(text_cookie, j) == SPACE - or byte(text_cookie, j) == HTAB - then - value = sub(text_cookie, i, j - 1) - cookie_table[key] = value - - key, value = nil, nil - state = EXPECT_SP - i = j + 1 - end - elseif state == EXPECT_SP then - if byte(text_cookie, j) ~= SPACE - and byte(text_cookie, j) ~= HTAB - then - state = EXPECT_KEY - i = j - j = j - 1 - end - end - j = j + 1 - end - - if key ~= nil and value == nil then - cookie_table[key] = sub(text_cookie, i) - end - - return cookie_table -end - -function _M.new(self) - local _cookie = ngx.var.http_cookie - --if not _cookie then - --return nil, "no cookie found in current request" - --end - return setmetatable({ _cookie = _cookie, set_cookie_table = new_tab(4, 0) }, - { __index = self }) -end - -function _M.get(self, key) - if not self._cookie then - return nil, "no cookie found in the current request" - end - if self.cookie_table == nil then - self.cookie_table = get_cookie_table(self._cookie) - end - - return self.cookie_table[key] -end - -function _M.get_all(self) - if not self._cookie then - return nil, "no cookie found in the current request" - end - - if self.cookie_table == nil then - self.cookie_table = get_cookie_table(self._cookie) - end - - return self.cookie_table -end - -function _M.get_cookie_size(self) - if not self._cookie then - return 0 - end - - return string.len(self._cookie) -end - -local function bake(cookie) - if not cookie.key or not cookie.value then - return nil, 'missing cookie field "key" or "value"' - end - - if cookie["max-age"] then - cookie.max_age = cookie["max-age"] - end - - if (cookie.samesite) then - local samesite = cookie.samesite - - -- if we don't have a valid-looking attribute, ignore the attribute - if (samesite ~= "Strict" and samesite ~= "Lax" and samesite ~= "None") then - log(WARN, "SameSite value must be 'Strict', 'Lax' or 'None'") - cookie.samesite = nil - end - end - - local str = cookie.key .. "=" .. cookie.value - .. (cookie.expires and "; Expires=" .. cookie.expires or "") - .. (cookie.max_age and "; Max-Age=" .. cookie.max_age or "") - .. (cookie.domain and "; Domain=" .. cookie.domain or "") - .. (cookie.path and "; Path=" .. cookie.path or "") - .. (cookie.secure and "; Secure" or "") - .. (cookie.httponly and "; HttpOnly" or "") - .. (cookie.samesite and "; SameSite=" .. cookie.samesite or "") - .. (cookie.extension and "; " .. cookie.extension or "") - return str -end - -function _M.set(self, cookie) - local cookie_str, err = bake(cookie) - if not cookie_str then - return nil, err - end - - local set_cookie = ngx_header['Set-Cookie'] - local set_cookie_type = type(set_cookie) - local t = self.set_cookie_table - clear_tab(t) - - if set_cookie_type == "string" then - -- only one cookie has been setted - if set_cookie ~= cookie_str then - t[1] = set_cookie - t[2] = cookie_str - ngx_header['Set-Cookie'] = t - end - elseif set_cookie_type == "table" then - -- more than one cookies has been setted - local size = #set_cookie - - -- we can not set cookie like ngx.header['Set-Cookie'][3] = val - -- so create a new table, copy all the values, and then set it back - for i=1, size do - t[i] = ngx_header['Set-Cookie'][i] - if t[i] == cookie_str then - -- new cookie is duplicated - return true - end - end - t[size + 1] = cookie_str - ngx_header['Set-Cookie'] = t - else - -- no cookie has been setted - ngx_header['Set-Cookie'] = cookie_str - end - return true -end - -_M.get_cookie_string = bake - -return _M diff --git a/plugins/openresty/waf/lib/resty/ipmatcher.lua b/plugins/openresty/waf/lib/resty/ipmatcher.lua deleted file mode 100644 index 880d78d0a..000000000 --- a/plugins/openresty/waf/lib/resty/ipmatcher.lua +++ /dev/null @@ -1,407 +0,0 @@ -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 \ No newline at end of file diff --git a/plugins/openresty/waf/lib/resty/libinjection.lua b/plugins/openresty/waf/lib/resty/libinjection.lua deleted file mode 100644 index 587604ae3..000000000 --- a/plugins/openresty/waf/lib/resty/libinjection.lua +++ /dev/null @@ -1,326 +0,0 @@ -local _M = {} - -local bit = require "bit" -local ffi = require "ffi" - -local ffi_new = ffi.new -local ffi_string = ffi.string - --- enum sqli_flags -local FLAG_NONE = 0 -local FLAG_QUOTE_NONE = 1 -local FLAG_QUOTE_SINGLE = 2 -local FLAG_QUOTE_DOUBLE = 4 -local FLAG_SQL_ANSI = 8 -local FLAG_SQL_MYSQL = 16 - --- enum lookup_type -local LOOKUP_FINGERPRINT = 4 - --- enum html5_flags -local DATA_STATE = 0 -local VALUE_NO_QUOTE = 1 -local VALUE_SINGLE_QUOTE = 2 -local VALUE_DOUBLE_QUOTE = 3 -local VALUE_BACK_QUOTE = 4 - --- cached ORs -local QUOTE_NONE_SQL_ANSI = bit.bor(FLAG_QUOTE_NONE, FLAG_SQL_ANSI) -local QUOTE_NONE_SQL_MYSQL = bit.bor(FLAG_QUOTE_NONE, FLAG_SQL_MYSQL) -local QUOTE_SINGLE_SQL_ANSI = bit.bor(FLAG_QUOTE_SINGLE, FLAG_SQL_ANSI) -local QUOTE_SINGLE_SQL_MYSQL = bit.bor(FLAG_QUOTE_SINGLE, FLAG_SQL_MYSQL) -local QUOTE_DOUBLE_SQL_MYSQL = bit.bor(FLAG_QUOTE_DOUBLE, FLAG_SQL_MYSQL) - --- libibjection.so -ffi.cdef [[ -const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state* sql_state, int flags); - -struct libinjection_sqli_token { - char type; - char str_open; - char str_close; - size_t pos; - size_t len; - int count; - char val[32]; -}; - -typedef char (*ptr_lookup_fn)(struct libinjection_sqli_state*, int lookuptype, const char* word, size_t len); - -struct libinjection_sqli_state { - const char *s; - size_t slen; - ptr_lookup_fn lookup; - void* userdata; - int flags; - size_t pos; - struct libinjection_sqli_token tokenvec[8]; - struct libinjection_sqli_token *current; - char fingerprint[8]; - int reason; - int stats_comment_ddw; - int stats_comment_ddx; - int stats_comment_c; - int stats_comment_hash; - int stats_folds; - int stats_tokens; -}; - -void libinjection_sqli_init(struct libinjection_sqli_state * sf, const char *s, size_t len, int flags); -int libinjection_is_sqli(struct libinjection_sqli_state* sql_state); - -int libinjection_sqli(const char* s, size_t slen, char fingerprint[]); - -int libinjection_is_xss(const char* s, size_t len, int flags); -int libinjection_xss(const char* s, size_t slen); -]] - -_M.version = "0.1.1" - -local state_type = ffi.typeof("struct libinjection_sqli_state[1]") -local lib, loaded - --- "borrowed" from CF aho-corasick lib -local function _loadlib() - if (not loaded) then - local path, so_path - local libname = "libinjection.so" - - for k, v in string.gmatch(package.cpath, "[^;]+") do - so_path = string.match(k, "(.*/)") - if so_path then - -- "so_path" could be nil. e.g, the dir path component is "." - so_path = so_path .. libname - - -- Don't get me wrong, the only way to know if a file exist is - -- trying to open it. - local f = io.open(so_path) - if f ~= nil then - io.close(f) - path = so_path - break - end - end - end - - path = "/usr/local/openresty/1pwaf/data/libinjection.so" - - lib = ffi.load(path) - - if (lib) then - loaded = true - return true - else - return false - end - else - return true - end -end - --- this function is not publicly exposed so we need to emulate it here. not great but not a measurable perf hit -local function _reparse_as_mysql(sqli_state) - return sqli_state[0].stats_comment_ddx ~= 0 or sqli_state[0].stats_comment_hash ~= 0 -end - ---[[ -Secondary API: detects SQLi in a string, given a context. Given a string, returns a list of - -* boolean indicating a match -* SQLi fingerprint ---]] -local function _sqli_contextwrapper(string, char, flag1, flag2) - if (char and not string.find(string, char, 1, true)) then - return false, nil - end - - if (not loaded) then - if (not _loadlib()) then - return false, nil - end - end - - local issqli, lookup, sqli_state - - -- allocate a new libinjection_sqli_state struct - sqli_state = ffi_new(state_type) - - -- init the state - lib.libinjection_sqli_init( - sqli_state, - string, - #string, - FLAG_NONE - ) - - -- initial fingerprint - lib.libinjection_sqli_fingerprint( - sqli_state, - flag1 - ) - - -- lookup - lookup = sqli_state[0].lookup( - sqli_state, - LOOKUP_FINGERPRINT, - sqli_state[0].fingerprint, - #ffi.string(sqli_state[0].fingerprint) - ) - - -- match? great, we're done - if (lookup > 0) then - return true, ffi_string(sqli_state[0].fingerprint) - end - - -- no? reparse, fingerprint and lookup again - if (flag2 and _reparse_as_mysql(sqli_state)) then - lib.libinjection_sqli_fingerprint( - sqli_state, - flag2 - ) - - lookup = sqli_state[0].lookup( - sqli_state, - LOOKUP_FINGERPRINT, - sqli_state[0].fingerprint, - #ffi.string(sqli_state[0].fingerprint) - ) - - if (lookup > 0) then - return true, ffi_string(sqli_state[0].fingerprint) - end - end - - return false, nil -end - ---[[ -Wrapper for second-level API with no char context ---]] -function _M.sqli_noquote(string) - return _sqli_contextwrapper( - string, - nil, - QUOTE_NONE_SQL_ANSI, - QUOTE_NONE_SQL_MYSQL - ) -end - ---[[ -Wrapper for second-level API with CHAR_SINGLE context ---]] -function _M.sqli_singlequote(string) - return _sqli_contextwrapper( - string, - "'", - QUOTE_SINGLE_SQL_ANSI, - QUOTE_SINGLE_SQL_MYSQL - ) -end - ---[[ -Wrapper for second-level API with CHAR_DOUBLE context ---]] -function _M.sqli_doublequote(string) - return _sqli_contextwrapper( - string, - '"', - QUOTE_DOUBLE_SQL_MYSQL - ) -end - ---[[ -Simple API. Given a string, returns a list of - -* boolean indicating a match -* SQLi fingerprint ---]] -function _M.sqli(string) - if (not loaded) then - if (not _loadlib()) then - return false, nil - end - end - - local fingerprint = ffi_new("char [8]") - - return lib.libinjection_sqli(string, #string, fingerprint) == 1, ffi_string(fingerprint) -end - ---[[ -Secondary API: detects XSS in a string, given a context. Given a string, returns a boolean denoting if XSS was detected ---]] -local function _xss_contextwrapper(string, flag) - if (not loaded) then - if (not _loadlib()) then - return false - end - end - - return lib.libinjection_is_xss(string, #string, flag) == 1 -end - ---[[ -Wrapper for second-level API with DATA_STATE flag ---]] -function _M.xss_data_state(string) - return _xss_contextwrapper( - string, - DATA_STATE - ) -end - ---[[ -Wrapper for second-level API with VALUE_NO_QUOTE flag ---]] -function _M.xss_noquote(string) - return _xss_contextwrapper( - string, - VALUE_NO_QUOTE - ) -end - ---[[ -Wrapper for second-level API with VALUE_SINGLE_QUOTE flag ---]] -function _M.xss_singlequote(string) - return _xss_contextwrapper( - string, - VALUE_SINGLE_QUOTE - ) -end - ---[[ -Wrapper for second-level API with VALUE_DOUBLE_QUOTE flag ---]] -function _M.xss_doublequote(string) - return _xss_contextwrapper( - string, - VALUE_DOUBLE_QUOTE - ) -end - ---[[ -Wrapper for second-level API with VALUE_BACK_QUOTE flag ---]] -function _M.xss_backquote(string) - return _xss_contextwrapper( - string, - VALUE_BACK_QUOTE - ) -end - ---[[ -ALPHA version of XSS detector. Given a string, returns a boolean denoting if XSS was detected ---]] -function _M.xss(string) - if (not loaded) then - if (not _loadlib()) then - return false - end - end - - return lib.libinjection_xss(string, #string) == 1 -end - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/lib/resty/maxminddb.lua b/plugins/openresty/waf/lib/resty/maxminddb.lua deleted file mode 100644 index ddf82032f..000000000 --- a/plugins/openresty/waf/lib/resty/maxminddb.lua +++ /dev/null @@ -1,372 +0,0 @@ ---[[ - Copyright 2017-now anjia (anjia0532@gmail.com) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - --- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L5-L12 - -local ffi = require('ffi') -local ffi_new = ffi.new -local ffi_str = ffi.string -local ffi_cast = ffi.cast -local ffi_gc = ffi.gc -local C = ffi.C - -local _M = {} -local _D = {} - -_M._VERSION = '1.3.3' -local mt = { __index = _M } - --- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L36-L126 -ffi.cdef [[ - -typedef long int ssize_t; - -typedef unsigned int mmdb_uint128_t __attribute__ ((__mode__(TI))); - -typedef struct MMDB_entry_s { - struct MMDB_s *mmdb; - uint32_t offset; -} MMDB_entry_s; - -typedef struct MMDB_lookup_result_s { - bool found_entry; - MMDB_entry_s entry; - uint16_t netmask; -} MMDB_lookup_result_s; - -typedef struct MMDB_entry_data_s { - bool has_data; - union { - uint32_t pointer; - const char *utf8_string; - double double_value; - const uint8_t *bytes; - uint16_t uint16; - uint32_t uint32; - int32_t int32; - uint64_t uint64; - mmdb_uint128_t uint128; - bool boolean; - float float_value; - }; - - uint32_t offset; - uint32_t offset_to_next; - uint32_t data_size; - uint32_t type; -} MMDB_entry_data_s; - -typedef struct MMDB_entry_data_list_s { - MMDB_entry_data_s entry_data; - struct MMDB_entry_data_list_s *next; -} MMDB_entry_data_list_s; - -typedef struct MMDB_description_s { - const char *language; - const char *description; -} MMDB_description_s; - -typedef struct MMDB_metadata_s { - uint32_t node_count; - uint16_t record_size; - uint16_t ip_version; - const char *database_type; - struct { - size_t count; - const char **names; - } languages; - uint16_t binary_format_major_version; - uint16_t binary_format_minor_version; - uint64_t build_epoch; - struct { - size_t count; - MMDB_description_s **descriptions; - } description; -} MMDB_metadata_s; - -typedef struct MMDB_ipv4_start_node_s { - uint16_t netmask; - uint32_t node_value; -} MMDB_ipv4_start_node_s; - -typedef struct MMDB_s { - uint32_t flags; - const char *filename; - ssize_t file_size; - const uint8_t *file_content; - const uint8_t *data_section; - uint32_t data_section_size; - const uint8_t *metadata_section; - uint32_t metadata_section_size; - uint16_t full_record_byte_size; - uint16_t depth; - MMDB_ipv4_start_node_s ipv4_start_node; - MMDB_metadata_s metadata; -} MMDB_s; - -typedef char * pchar; - -MMDB_lookup_result_s MMDB_lookup_string(MMDB_s *const mmdb, const char *const ipstr, int *const gai_error,int *const mmdb_error); -int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb); -int MMDB_aget_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, const char *const *const path); -char *MMDB_strerror(int error_code); - -int MMDB_get_entry_data_list(MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list); -void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list); -void MMDB_close(MMDB_s *const mmdb); -const char *gai_strerror(int errcode); -]] - --- error codes --- https://github.com/maxmind/libmaxminddb/blob/master/include/maxminddb.h#L66 -local MMDB_SUCCESS = 0 -local MMDB_FILE_OPEN_ERROR = 1 -local MMDB_CORRUPT_SEARCH_TREE_ERROR = 2 -local MMDB_INVALID_METADATA_ERROR = 3 -local MMDB_IO_ERROR = 4 -local MMDB_OUT_OF_MEMORY_ERROR = 5 -local MMDB_UNKNOWN_DATABASE_FORMAT_ERROR = 6 -local MMDB_INVALID_DATA_ERROR = 7 -local MMDB_INVALID_LOOKUP_PATH_ERROR = 8 -local MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR = 9 -local MMDB_INVALID_NODE_NUMBER_ERROR = 10 -local MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR = 11 - --- data type --- https://github.com/maxmind/libmaxminddb/blob/master/include/maxminddb.h#L40 -local MMDB_DATA_TYPE_EXTENDED = 0 -local MMDB_DATA_TYPE_POINTER = 1 -local MMDB_DATA_TYPE_UTF8_STRING = 2 -local MMDB_DATA_TYPE_DOUBLE = 3 -local MMDB_DATA_TYPE_BYTES = 4 -local MMDB_DATA_TYPE_UINT16 = 5 -local MMDB_DATA_TYPE_UINT32 = 6 -local MMDB_DATA_TYPE_MAP = 7 -local MMDB_DATA_TYPE_INT32 = 8 -local MMDB_DATA_TYPE_UINT64 = 9 -local MMDB_DATA_TYPE_UINT128 = 10 -local MMDB_DATA_TYPE_ARRAY = 11 -local MMDB_DATA_TYPE_CONTAINER = 12 -local MMDB_DATA_TYPE_END_MARKER = 13 -local MMDB_DATA_TYPE_BOOLEAN = 14 -local MMDB_DATA_TYPE_FLOAT = 15 - --- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L136-L138 - -local initted = false - -local function mmdb_strerror(profile, rc) - return ffi_str(_D[profile].maxm.MMDB_strerror(rc)) -end - -local function gai_strerror(rc) - return ffi_str(C.gai_strerror(rc)) -end - -function _M.init(profiles) - for profile, location in pairs(profiles) do - _D[profile] = {} - _D[profile].maxm = ffi.load('/usr/local/openresty/1pwaf/data/libmaxminddb.so') - _D[profile].mmdb = ffi_new('MMDB_s') - local maxmind_ready = _D[profile].maxm.MMDB_open(location, 0, _D[profile].mmdb) - if maxmind_ready ~= MMDB_SUCCESS then - return nil, mmdb_strerror(profile, maxmind_ready) - end - ffi_gc(_D[profile].mmdb, _D[profile].maxm.MMDB_close) - end - - --if not initted then - -- local maxmind_ready = maxm.MMDB_open(dbfile, 0, mmdb) - -- - -- if maxmind_ready ~= MMDB_SUCCESS then - -- return nil, mmdb_strerror(maxmind_ready) - -- end - -- - -- - -- - -- ffi_gc(mmdb, maxm.MMDB_close) - --end - initted = true - return initted -end - -function _M.initted() - return initted -end - --- https://github.com/maxmind/libmaxminddb/blob/master/src/maxminddb.c#L1938 --- LOCAL MMDB_entry_data_list_s *dump_entry_data_list( FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent, int *status) -local function _dump_entry_data_list(entry_data_list, status) - - if not entry_data_list then - return nil, MMDB_INVALID_DATA_ERROR - end - - local entry_data_item = entry_data_list[0].entry_data - local data_type = entry_data_item.type - local data_size = entry_data_item.data_size - local result - - if data_type == MMDB_DATA_TYPE_MAP then - result = {} - - local size = entry_data_item.data_size - - entry_data_list = entry_data_list[0].next - - while (size > 0 and entry_data_list) - do - entry_data_item = entry_data_list[0].entry_data - data_type = entry_data_item.type - data_size = entry_data_item.data_size - - if MMDB_DATA_TYPE_UTF8_STRING ~= data_type then - return nil, MMDB_INVALID_DATA_ERROR - end - - local key = ffi_str(entry_data_item.utf8_string, data_size) - - if not key then - return nil, MMDB_OUT_OF_MEMORY_ERROR - end - - local val - entry_data_list = entry_data_list[0].next - entry_data_list, status, val = _dump_entry_data_list(entry_data_list) - - if status ~= MMDB_SUCCESS then - return nil, status - end - - result[key] = val - - size = size - 1 - end - - - elseif entry_data_list[0].entry_data.type == MMDB_DATA_TYPE_ARRAY then - local size = entry_data_list[0].entry_data.data_size - result = {} - - entry_data_list = entry_data_list[0].next - - local i = 1 - while (i <= size and entry_data_list) - do - local val - entry_data_list, status, val = _dump_entry_data_list(entry_data_list) - - if status ~= MMDB_SUCCESS then - return nil, nil, val - end - - result[i] = val - i = i + 1 - end - - - else - entry_data_item = entry_data_list[0].entry_data - data_type = entry_data_item.type - data_size = entry_data_item.data_size - - local val - -- string type "key":"val" - -- other type "key":val - -- default other type - if data_type == MMDB_DATA_TYPE_UTF8_STRING then - val = ffi_str(entry_data_item.utf8_string, data_size) - if not val then - status = MMDB_OUT_OF_MEMORY_ERROR - return nil, status - end - elseif data_type == MMDB_DATA_TYPE_BYTES then - val = ffi_str(ffi_cast('char * ', entry_data_item.bytes), data_size) - if not val then - status = MMDB_OUT_OF_MEMORY_ERROR - return nil, status - end - elseif data_type == MMDB_DATA_TYPE_DOUBLE then - val = entry_data_item.double_value - elseif data_type == MMDB_DATA_TYPE_FLOAT then - val = entry_data_item.float_value - elseif data_type == MMDB_DATA_TYPE_UINT16 then - val = entry_data_item.uint16 - elseif data_type == MMDB_DATA_TYPE_UINT32 then - val = entry_data_item.uint32 - elseif data_type == MMDB_DATA_TYPE_BOOLEAN then - val = entry_data_item.boolean - elseif data_type == MMDB_DATA_TYPE_UINT64 then - val = entry_data_item.uint64 - elseif data_type == MMDB_DATA_TYPE_INT32 then - val = entry_data_item.int32 - else - return nil, MMDB_INVALID_DATA_ERROR - end - - result = val - entry_data_list = entry_data_list[0].next - end - - status = MMDB_SUCCESS - return entry_data_list, status, result -end - -function _M.lookup(profile, ip) - - if not initted then - return nil, "not initialized" - end - - -- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L159-L176 - local gai_error = ffi_new('int[1]') - local mmdb_error = ffi_new('int[1]') - - local result = _D[profile].maxm.MMDB_lookup_string(_D[profile].mmdb, ip, gai_error, mmdb_error) - - if mmdb_error[0] ~= MMDB_SUCCESS then - return nil, 'lookup failed: ' .. mmdb_strerror(profile, mmdb_error[0]) - end - - if gai_error[0] ~= MMDB_SUCCESS then - return nil, 'lookup failed: ' .. gai_strerror(gai_error[0]) - end - - if true ~= result.found_entry then - return nil, 'not found' - end - - local entry_data_list = ffi_cast('MMDB_entry_data_list_s **const', ffi_new("MMDB_entry_data_list_s")) - - local status = _D[profile].maxm.MMDB_get_entry_data_list(result.entry, entry_data_list) - - if status ~= MMDB_SUCCESS then - return nil, 'get entry data failed: ' .. mmdb_strerror(profile, status) - end - - local head = entry_data_list[0] -- Save so this can be passed to free fn. - local _, status, result = _dump_entry_data_list(entry_data_list) - _D[profile].maxm.MMDB_free_entry_data_list(head) - - if status ~= MMDB_SUCCESS then - return nil, 'dump entry data failed: ' .. mmdb_strerror(profile, status) - end - - return result -end - --- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/master/resty/maxminddb.lua#L208 --- https://www.maxmind.com/en/geoip2-databases you should download the mmdb file from maxmind - -return _M; diff --git a/plugins/openresty/waf/lib/resty/mlcache.lua b/plugins/openresty/waf/lib/resty/mlcache.lua deleted file mode 100644 index 0c1e5b2e2..000000000 --- a/plugins/openresty/waf/lib/resty/mlcache.lua +++ /dev/null @@ -1,1441 +0,0 @@ --- vim: ts=4 sts=4 sw=4 et: - -local new_tab = require "table.new" -local lrucache = require "resty.lrucache" -local resty_lock = require "resty.lock" -local tablepool -do - local pok - pok, tablepool = pcall(require, "tablepool") - if not pok then - -- fallback for OpenResty < 1.15.8.1 - tablepool = { - fetch = function(_, narr, nrec) - return new_tab(narr, nrec) - end, - release = function(_, _, _) - -- nop (obj will be subject to GC) - end, - } - end -end -local codec -do - local pok - pok, codec = pcall(require, "string.buffer") - if not pok then - codec = require "cjson" - end -end - - -local now = ngx.now -local min = math.min -local ceil = math.ceil -local fmt = string.format -local sub = string.sub -local find = string.find -local type = type -local pcall = pcall -local xpcall = xpcall -local traceback = debug.traceback -local error = error -local tostring = tostring -local tonumber = tonumber -local encode = codec.encode -local decode = codec.decode -local thread_spawn = ngx.thread.spawn -local thread_wait = ngx.thread.wait -local setmetatable = setmetatable -local shared = ngx.shared -local ngx_log = ngx.log -local WARN = ngx.WARN -local ERR = ngx.ERR - - -local CACHE_MISS_SENTINEL_LRU = {} -local LOCK_KEY_PREFIX = "lua-resty-mlcache:lock:" -local LRU_INSTANCES = setmetatable({}, { __mode = "v" }) -local SHM_SET_DEFAULT_TRIES = 3 -local BULK_DEFAULT_CONCURRENCY = 3 - - -local TYPES_LOOKUP = { - number = 1, - boolean = 2, - string = 3, - table = 4, -} - - -local SHM_FLAGS = { - stale = 0x00000001, -} - - -local marshallers = { - shm_value = function(str_value, value_type, at, ttl) - return fmt("%d:%f:%f:%s", value_type, at, ttl, str_value) - end, - - shm_nil = function(at, ttl) - return fmt("0:%f:%f:", at, ttl) - end, - - [1] = function(number) -- number - return tostring(number) - end, - - [2] = function(bool) -- boolean - return bool and "true" or "false" - end, - - [3] = function(str) -- string - return str - end, - - [4] = function(t) -- table - local pok, str = pcall(encode, t) - if not pok then - return nil, "could not encode table value: " .. str - end - - return str - end, -} - - -local unmarshallers = { - shm_value = function(marshalled) - -- split our shm marshalled value by the hard-coded ":" tokens - -- "type:at:ttl:value" - -- 1:1501831735.052000:0.500000:123 - local ttl_last = find(marshalled, ":", 21, true) - 1 - - local value_type = sub(marshalled, 1, 1) -- n:... - local at = sub(marshalled, 3, 19) -- n:1501831160 - local ttl = sub(marshalled, 21, ttl_last) - local str_value = sub(marshalled, ttl_last + 2) - - return str_value, tonumber(value_type), tonumber(at), tonumber(ttl) - end, - - [0] = function() -- nil - return nil - end, - - [1] = function(str) -- number - return tonumber(str) - end, - - [2] = function(str) -- boolean - return str == "true" - end, - - [3] = function(str) -- string - return str - end, - - [4] = function(str) -- table - local pok, t = pcall(decode, str) - if not pok then - return nil, "could not decode table value: " .. t - end - - return t - end, -} - - -local function rebuild_lru(self) - if self.lru then - if self.lru.flush_all then - self.lru:flush_all() - return - end - - -- fallback for OpenResty < 1.13.6.2 - -- Invalidate the entire LRU by GC-ing it. - LRU_INSTANCES[self.name] = nil - self.lru = nil - end - - -- Several mlcache instances can have the same name and hence, the same - -- lru instance. We need to GC such LRU instance when all mlcache instances - -- using them are GC'ed. We do this with a weak table. - local lru = LRU_INSTANCES[self.name] - if not lru then - lru = lrucache.new(self.lru_size) - LRU_INSTANCES[self.name] = lru - end - - self.lru = lru -end - - -local _M = { - _VERSION = "2.6.1", - _AUTHOR = "Thibault Charbonnier", - _LICENSE = "MIT", - _URL = "https://github.com/thibaultcha/lua-resty-mlcache", -} -local mt = { __index = _M } - - -function _M.new(name, shm, opts) - if type(name) ~= "string" then - error("name must be a string", 2) - end - - if type(shm) ~= "string" then - error("shm must be a string", 2) - end - - if opts ~= nil then - if type(opts) ~= "table" then - error("opts must be a table", 2) - end - - if opts.lru_size ~= nil and type(opts.lru_size) ~= "number" then - error("opts.lru_size must be a number", 2) - end - - if opts.ttl ~= nil then - if type(opts.ttl) ~= "number" then - error("opts.ttl must be a number", 2) - end - - if opts.ttl < 0 then - error("opts.ttl must be >= 0", 2) - end - end - - if opts.neg_ttl ~= nil then - if type(opts.neg_ttl) ~= "number" then - error("opts.neg_ttl must be a number", 2) - end - - if opts.neg_ttl < 0 then - error("opts.neg_ttl must be >= 0", 2) - end - end - - if opts.resurrect_ttl ~= nil then - if type(opts.resurrect_ttl) ~= "number" then - error("opts.resurrect_ttl must be a number", 2) - end - - if opts.resurrect_ttl < 0 then - error("opts.resurrect_ttl must be >= 0", 2) - end - end - - if opts.resty_lock_opts ~= nil - and type(opts.resty_lock_opts) ~= "table" - then - error("opts.resty_lock_opts must be a table", 2) - end - - if opts.ipc_shm ~= nil and type(opts.ipc_shm) ~= "string" then - error("opts.ipc_shm must be a string", 2) - end - - if opts.ipc ~= nil then - if opts.ipc_shm then - error("cannot specify both of opts.ipc_shm and opts.ipc", 2) - end - - if type(opts.ipc) ~= "table" then - error("opts.ipc must be a table", 2) - end - - if type(opts.ipc.register_listeners) ~= "function" then - error("opts.ipc.register_listeners must be a function", 2) - end - - if type(opts.ipc.broadcast) ~= "function" then - error("opts.ipc.broadcast must be a function", 2) - end - - if opts.ipc.poll ~= nil and type(opts.ipc.poll) ~= "function" then - error("opts.ipc.poll must be a function", 2) - end - end - - if opts.l1_serializer ~= nil - and type(opts.l1_serializer) ~= "function" - then - error("opts.l1_serializer must be a function", 2) - end - - if opts.shm_set_tries ~= nil then - if type(opts.shm_set_tries) ~= "number" then - error("opts.shm_set_tries must be a number", 2) - end - - if opts.shm_set_tries < 1 then - error("opts.shm_set_tries must be >= 1", 2) - end - end - - if opts.shm_miss ~= nil and type(opts.shm_miss) ~= "string" then - error("opts.shm_miss must be a string", 2) - end - - if opts.shm_locks ~= nil and type(opts.shm_locks) ~= "string" then - error("opts.shm_locks must be a string", 2) - end - else - opts = {} - end - - local dict = shared[shm] - if not dict then - return nil, "no such lua_shared_dict: " .. shm - end - - local dict_miss - if opts.shm_miss then - dict_miss = shared[opts.shm_miss] - if not dict_miss then - return nil, "no such lua_shared_dict for opts.shm_miss: " - .. opts.shm_miss - end - end - - if opts.shm_locks then - local dict_locks = shared[opts.shm_locks] - if not dict_locks then - return nil, "no such lua_shared_dict for opts.shm_locks: " - .. opts.shm_locks - end - end - - local self = { - name = name, - dict = dict, - shm = shm, - dict_miss = dict_miss, - shm_miss = opts.shm_miss, - shm_locks = opts.shm_locks or shm, - ttl = opts.ttl or 30, - neg_ttl = opts.neg_ttl or 5, - resurrect_ttl = opts.resurrect_ttl, - lru_size = opts.lru_size or 100, - resty_lock_opts = opts.resty_lock_opts, - l1_serializer = opts.l1_serializer, - shm_set_tries = opts.shm_set_tries or SHM_SET_DEFAULT_TRIES, - debug = opts.debug, - } - - if opts.ipc_shm or opts.ipc then - self.events = { - ["invalidation"] = { - channel = fmt("mlcache:invalidations:%s", name), - handler = function(key) - self.lru:delete(key) - end, - }, - ["purge"] = { - channel = fmt("mlcache:purge:%s", name), - handler = function() - rebuild_lru(self) - end, - } - } - - if opts.ipc_shm then - local mlcache_ipc = require "resty.mlcache.ipc" - - local ipc, err = mlcache_ipc.new(opts.ipc_shm, opts.debug) - if not ipc then - return nil, "failed to initialize mlcache IPC " .. - "(could not instantiate mlcache.ipc): " .. err - end - - for _, ev in pairs(self.events) do - ipc:subscribe(ev.channel, ev.handler) - end - - self.broadcast = function(channel, data) - return ipc:broadcast(channel, data) - end - - self.poll = function(timeout) - return ipc:poll(timeout) - end - - self.ipc = ipc - - else - -- opts.ipc - local ok, err = opts.ipc.register_listeners(self.events) - if not ok and err ~= nil then - return nil, "failed to initialize custom IPC " .. - "(opts.ipc.register_listeners returned an error): " - .. err - end - - self.broadcast = opts.ipc.broadcast - self.poll = opts.ipc.poll - - self.ipc = true - end - end - - if opts.lru then - self.lru = opts.lru - - else - rebuild_lru(self) - end - - return setmetatable(self, mt) -end - - -local function l1_serialize(value, l1_serializer) - if value ~= nil and l1_serializer then - local ok, err - ok, value, err = pcall(l1_serializer, value) - if not ok then - return nil, "l1_serializer threw an error: " .. value - end - - if err then - return nil, err - end - - if value == nil then - return nil, "l1_serializer returned a nil value" - end - end - - return value -end - - -local function set_lru(self, key, value, ttl, neg_ttl, l1_serializer) - local value, err = l1_serialize(value, l1_serializer) - if err then - return nil, err - end - - if value == nil then - value = CACHE_MISS_SENTINEL_LRU - ttl = neg_ttl - end - - if ttl == 0 then - -- indefinite ttl for lua-resty-lrucache is 'nil' - ttl = nil - end - - self.lru:set(key, value, ttl) - - return value -end - - -local function marshall_for_shm(value, ttl, neg_ttl) - local at = now() - - if value == nil then - return marshallers.shm_nil(at, neg_ttl), nil, true -- is_nil - end - - -- serialize insertion time + Lua types for shm storage - - local value_type = TYPES_LOOKUP[type(value)] - - if not marshallers[value_type] then - error("cannot cache value of type " .. type(value)) - end - - local str_marshalled, err = marshallers[value_type](value) - if not str_marshalled then - return nil, "could not serialize value for lua_shared_dict insertion: " - .. err - end - - return marshallers.shm_value(str_marshalled, value_type, at, ttl) -end - - -local function unmarshall_from_shm(shm_v) - local str_serialized, value_type, at, ttl = unmarshallers.shm_value(shm_v) - - local value, err = unmarshallers[value_type](str_serialized) - if err then - return nil, err - end - - return value, nil, at, ttl -end - - -local function set_shm(self, shm_key, value, ttl, neg_ttl, flags, shm_set_tries, - throw_no_mem) - local shm_value, err, is_nil = marshall_for_shm(value, ttl, neg_ttl) - if not shm_value then - return nil, err - end - - local shm = self.shm - local dict = self.dict - - if is_nil then - ttl = neg_ttl - - if self.dict_miss then - shm = self.shm_miss - dict = self.dict_miss - end - end - - -- we will call `set()` N times to work around potential shm fragmentation. - -- when the shm is full, it will only evict about 30 to 90 items (via - -- LRU), which could lead to a situation where `set()` still does not - -- have enough memory to store the cached value, in which case we - -- try again to try to trigger more LRU evictions. - - local tries = 0 - local ok, err - - while tries < shm_set_tries do - tries = tries + 1 - - ok, err = dict:set(shm_key, shm_value, ttl, flags or 0) - if ok or err and err ~= "no memory" then - break - end - end - - if not ok then - if err ~= "no memory" or throw_no_mem then - return nil, "could not write to lua_shared_dict '" .. shm - .. "': " .. err - end - - ngx_log(WARN, "could not write to lua_shared_dict '", - shm, "' after ", tries, " tries (no memory), ", - "it is either fragmented or cannot allocate more ", - "memory, consider increasing 'opts.shm_set_tries'") - end - - return true -end - - -local function set_shm_set_lru(self, key, shm_key, value, ttl, neg_ttl, flags, - shm_set_tries, l1_serializer, throw_no_mem) - - local ok, err = set_shm(self, shm_key, value, ttl, neg_ttl, flags, - shm_set_tries, throw_no_mem) - if not ok then - return nil, err - end - - return set_lru(self, key, value, ttl, neg_ttl, l1_serializer) -end - - -local function get_shm_set_lru(self, key, shm_key, l1_serializer) - local v, shmerr, went_stale = self.dict:get_stale(shm_key) - if v == nil and shmerr then - -- shmerr can be 'flags' upon successful get_stale() calls, so we - -- also check v == nil - return nil, "could not read from lua_shared_dict: " .. shmerr - end - - if self.shm_miss and v == nil then - -- if we cache misses in another shm, maybe it is there - v, shmerr, went_stale = self.dict_miss:get_stale(shm_key) - if v == nil and shmerr then - -- shmerr can be 'flags' upon successful get_stale() calls, so we - -- also check v == nil - return nil, "could not read from lua_shared_dict: " .. shmerr - end - end - - if v ~= nil then - local value, err, at, ttl = unmarshall_from_shm(v) - if err then - return nil, "could not deserialize value after lua_shared_dict " .. - "retrieval: " .. err - end - - if went_stale then - value, err = l1_serialize(value, l1_serializer) - if err then - return nil, err - end - - return value, nil, went_stale - end - - -- 'shmerr' is 'flags' on :get_stale() success - local is_stale = shmerr == SHM_FLAGS.stale - - local remaining_ttl - if ttl == 0 then - -- indefinite ttl, keep '0' as it means 'forever' - remaining_ttl = 0 - - else - -- compute elapsed time to get remaining ttl for LRU caching - remaining_ttl = ttl - (now() - at) - - if remaining_ttl <= 0 then - -- value has less than 1ms of lifetime in the shm, avoid - -- setting it in LRU which would be wasteful and could - -- indefinitely cache the value when ttl == 0 - value, err = l1_serialize(value, l1_serializer) - if err then - return nil, err - end - - return value, nil, nil, is_stale - end - end - - value, err = set_lru(self, key, value, remaining_ttl, remaining_ttl, - l1_serializer) - if err then - return nil, err - end - - return value, nil, nil, is_stale - end -end - - -local function check_opts(self, opts) - local ttl - local neg_ttl - local resurrect_ttl - local l1_serializer - local shm_set_tries - local resty_lock_opts - - if opts ~= nil then - if type(opts) ~= "table" then - error("opts must be a table", 3) - end - - ttl = opts.ttl - if ttl ~= nil then - if type(ttl) ~= "number" then - error("opts.ttl must be a number", 3) - end - - if ttl < 0 then - error("opts.ttl must be >= 0", 3) - end - end - - neg_ttl = opts.neg_ttl - if neg_ttl ~= nil then - if type(neg_ttl) ~= "number" then - error("opts.neg_ttl must be a number", 3) - end - - if neg_ttl < 0 then - error("opts.neg_ttl must be >= 0", 3) - end - end - - resurrect_ttl = opts.resurrect_ttl - if resurrect_ttl ~= nil then - if type(resurrect_ttl) ~= "number" then - error("opts.resurrect_ttl must be a number", 3) - end - - if resurrect_ttl < 0 then - error("opts.resurrect_ttl must be >= 0", 3) - end - end - - l1_serializer = opts.l1_serializer - if l1_serializer ~= nil and type(l1_serializer) ~= "function" then - error("opts.l1_serializer must be a function", 3) - end - - shm_set_tries = opts.shm_set_tries - if shm_set_tries ~= nil then - if type(shm_set_tries) ~= "number" then - error("opts.shm_set_tries must be a number", 3) - end - - if shm_set_tries < 1 then - error("opts.shm_set_tries must be >= 1", 3) - end - end - - resty_lock_opts = opts.resty_lock_opts - if resty_lock_opts ~= nil then - if type(resty_lock_opts) ~= "table" then - error("opts.resty_lock_opts must be a table", 3) - end - end - end - - if not ttl then - ttl = self.ttl - end - - if not neg_ttl then - neg_ttl = self.neg_ttl - end - - if not resurrect_ttl then - resurrect_ttl = self.resurrect_ttl - end - - if not l1_serializer then - l1_serializer = self.l1_serializer - end - - if not shm_set_tries then - shm_set_tries = self.shm_set_tries - end - - if not resty_lock_opts then - resty_lock_opts = self.resty_lock_opts - end - - return ttl, neg_ttl, resurrect_ttl, l1_serializer, shm_set_tries, - resty_lock_opts -end - - -local function unlock_and_ret(lock, res, err, hit_lvl) - local ok, lerr = lock:unlock() - if not ok and lerr ~= "unlocked" then - return nil, "could not unlock callback: " .. lerr - end - - return res, err, hit_lvl -end - - -local function run_callback(self, key, shm_key, data, ttl, neg_ttl, - went_stale, l1_serializer, resurrect_ttl, shm_set_tries, rlock_opts, cb, ...) - - local lock, err = resty_lock:new(self.shm_locks, rlock_opts) - if not lock then - return nil, "could not create lock: " .. err - end - - local elapsed, lerr = lock:lock(LOCK_KEY_PREFIX .. shm_key) - if not elapsed and lerr ~= "timeout" then - return nil, "could not acquire callback lock: " .. lerr - end - - do - -- check for another worker's success at running the callback, but - -- do not return data if it is still the same stale value (this is - -- possible if the value was still not evicted between the first - -- get() and this one) - - local data2, err, went_stale2, stale2 = get_shm_set_lru(self, key, - shm_key, - l1_serializer) - if err then - return unlock_and_ret(lock, nil, err) - end - - if data2 ~= nil and not went_stale2 then - -- we got a fresh item from shm: other worker succeeded in running - -- the callback - if data2 == CACHE_MISS_SENTINEL_LRU then - data2 = nil - end - - return unlock_and_ret(lock, data2, nil, stale2 and 4 or 2) - end - end - - -- we are either the 1st worker to hold the lock, or - -- a subsequent worker whose lock has timed out before the 1st one - -- finished to run the callback - - if lerr == "timeout" then - local errmsg = "could not acquire callback lock: timeout" - - -- no stale data nor desire to resurrect it - if not went_stale or not resurrect_ttl then - return nil, errmsg - end - - -- do not resurrect the value here (another worker is running the - -- callback and will either get the new value, or resurrect it for - -- us if the callback fails) - - ngx_log(WARN, errmsg) - - -- went_stale is true, hence the value cannot be set in the LRU - -- cache, and cannot be CACHE_MISS_SENTINEL_LRU - - return data, nil, 4 - end - - -- still not in shm, we are the 1st worker to hold the lock, and thus - -- responsible for running the callback - - local pok, perr, err, new_ttl = xpcall(cb, traceback, ...) - if not pok then - return unlock_and_ret(lock, nil, "callback threw an error: " .. - tostring(perr)) - end - - if err then - -- callback returned nil + err - - -- be resilient in case callbacks return wrong error type - err = tostring(err) - - -- no stale data nor desire to resurrect it - if not went_stale or not resurrect_ttl then - return unlock_and_ret(lock, perr, err) - end - - -- we got 'data' from the shm, even though it is stale - -- 1. log as warn that the callback returned an error - -- 2. resurrect: insert it back into shm if 'resurrect_ttl' - -- 3. signify the staleness with a high hit_lvl of '4' - - ngx_log(WARN, "callback returned an error (", err, ") but stale ", - "value found in shm will be resurrected for ", - resurrect_ttl, "s (resurrect_ttl)") - - local res_data, res_err = set_shm_set_lru(self, key, shm_key, - data, resurrect_ttl, - resurrect_ttl, - SHM_FLAGS.stale, - shm_set_tries, l1_serializer) - if res_err then - ngx_log(WARN, "could not resurrect stale data (", res_err, ")") - end - - if res_data == CACHE_MISS_SENTINEL_LRU then - res_data = nil - end - - return unlock_and_ret(lock, res_data, nil, 4) - end - - -- successful callback run returned 'data, nil, new_ttl?' - - data = perr - - -- override ttl / neg_ttl - - if type(new_ttl) == "number" then - if new_ttl < 0 then - -- bypass cache - return unlock_and_ret(lock, data, nil, 3) - end - - if data == nil then - neg_ttl = new_ttl - - else - ttl = new_ttl - end - end - - data, err = set_shm_set_lru(self, key, shm_key, data, ttl, neg_ttl, nil, - shm_set_tries, l1_serializer) - if err then - return unlock_and_ret(lock, nil, err) - end - - if data == CACHE_MISS_SENTINEL_LRU then - data = nil - end - - -- unlock and return - - return unlock_and_ret(lock, data, nil, 3) -end - - -function _M:get(key, opts, cb, ...) - if type(key) ~= "string" then - error("key must be a string", 2) - end - - if cb ~= nil and type(cb) ~= "function" then - error("callback must be nil or a function", 2) - end - - -- worker LRU cache retrieval - - local data = self.lru:get(key) - if data == CACHE_MISS_SENTINEL_LRU then - return nil, nil, 1 - end - - if data ~= nil then - return data, nil, 1 - end - - -- not in worker's LRU cache, need shm lookup - - -- restrict this key to the current namespace, so we isolate this - -- mlcache instance from potential other instances using the same - -- shm - local namespaced_key = self.name .. key - - -- opts validation - - local ttl, neg_ttl, resurrect_ttl, l1_serializer, shm_set_tries, - rlock_opts = check_opts(self, opts) - - local err, went_stale, is_stale - data, err, went_stale, is_stale = get_shm_set_lru(self, key, namespaced_key, - l1_serializer) - if err then - return nil, err - end - - if data ~= nil and not went_stale then - if data == CACHE_MISS_SENTINEL_LRU then - data = nil - end - - return data, nil, is_stale and 4 or 2 - end - - -- not in shm either - - if cb == nil then - -- no L3 callback, early exit - return nil, nil, -1 - end - - -- L3 callback, single worker to run it - - return run_callback(self, key, namespaced_key, data, ttl, neg_ttl, - went_stale, l1_serializer, resurrect_ttl, - shm_set_tries, rlock_opts, cb, ...) -end - - -do - local function run_thread(self, ops, from, to) - for i = from, to do - local ctx = ops[i] - - ctx.data, ctx.err, ctx.hit_lvl = run_callback(self, ctx.key, - ctx.shm_key, ctx.data, - ctx.ttl, ctx.neg_ttl, - ctx.went_stale, - ctx.l1_serializer, - ctx.resurrect_ttl, - ctx.shm_set_tries, - ctx.rlock_opts, - ctx.cb, ctx.arg) - end - end - - - local bulk_mt = {} - bulk_mt.__index = bulk_mt - - - function _M.new_bulk(n_ops) - local bulk = new_tab((n_ops or 2) * 4, 1) -- 4 slots per op - bulk.n = 0 - - return setmetatable(bulk, bulk_mt) - end - - - function bulk_mt:add(key, opts, cb, arg) - local i = (self.n * 4) + 1 - self[i] = key - self[i + 1] = opts - self[i + 2] = cb - self[i + 3] = arg - self.n = self.n + 1 - end - - - local function bulk_res_iter(res, i) - local idx = i * 3 + 1 - if idx > res.n then - return - end - - i = i + 1 - - local data = res[idx] - local err = res[idx + 1] - local hit_lvl = res[idx + 2] - - return i, data, err, hit_lvl - end - - - function _M.each_bulk_res(res) - if not res.n then - error("res must have res.n field; is this a get_bulk() result?", 2) - end - - return bulk_res_iter, res, 0 - end - - - function _M:get_bulk(bulk, opts) - if type(bulk) ~= "table" then - error("bulk must be a table", 2) - end - - if not bulk.n then - error("bulk must have n field", 2) - end - - if opts then - if type(opts) ~= "table" then - error("opts must be a table", 2) - end - - if opts.concurrency then - if type(opts.concurrency) ~= "number" then - error("opts.concurrency must be a number", 2) - end - - if opts.concurrency <= 0 then - error("opts.concurrency must be > 0", 2) - end - end - end - - local n_bulk = bulk.n * 4 - local res = new_tab(n_bulk - n_bulk / 4, 1) - local res_idx = 1 - - -- only used if running L3 callbacks - local n_cbs = 0 - local cb_ctxs - - -- bulk - -- { "key", opts, cb, arg } - -- - -- res - -- { data, "err", hit_lvl } - - for i = 1, n_bulk, 4 do - local b_key = bulk[i] - local b_opts = bulk[i + 1] - local b_cb = bulk[i + 2] - - if type(b_key) ~= "string" then - error("key at index " .. i .. " must be a string for operation " .. - ceil(i / 4) .. " (got " .. type(b_key) .. ")", 2) - end - - if type(b_cb) ~= "function" then - error("callback at index " .. i + 2 .. " must be a function " .. - "for operation " .. ceil(i / 4) .. " (got " .. type(b_cb) .. - ")", 2) - end - - -- worker LRU cache retrieval - - local data = self.lru:get(b_key) - if data ~= nil then - if data == CACHE_MISS_SENTINEL_LRU then - data = nil - end - - res[res_idx] = data - --res[res_idx + 1] = nil - res[res_idx + 2] = 1 - - else - local pok, ttl, neg_ttl, resurrect_ttl, l1_serializer, - shm_set_tries, rlock_opts = pcall(check_opts, self, b_opts) - if not pok then - -- strip the stacktrace - local err = ttl:match("mlcache%.lua:%d+:%s(.*)") - error("options at index " .. i + 1 .. " for operation " .. - ceil(i / 4) .. " are invalid: " .. err, 2) - end - - -- not in worker's LRU cache, need shm lookup - -- we will prepare a task for each cache miss - local namespaced_key = self.name .. b_key - - local err, went_stale, is_stale - data, err, went_stale, is_stale = get_shm_set_lru(self, b_key, - namespaced_key, - l1_serializer) - if err then - --res[res_idx] = nil - res[res_idx + 1] = err - --res[res_idx + 2] = nil - - elseif data ~= nil and not went_stale then - if data == CACHE_MISS_SENTINEL_LRU then - data = nil - end - - res[res_idx] = data - --res[res_idx + 1] = nil - res[res_idx + 2] = is_stale and 4 or 2 - - else - -- not in shm either, we have to prepare a task to run the - -- L3 callback - - n_cbs = n_cbs + 1 - - if n_cbs == 1 then - cb_ctxs = tablepool.fetch("bulk_cb_ctxs", 1, 0) - end - - local ctx = tablepool.fetch("bulk_cb_ctx", 0, 15) - ctx.res_idx = res_idx - ctx.cb = b_cb - ctx.arg = bulk[i + 3] -- arg - ctx.key = b_key - ctx.shm_key = namespaced_key - ctx.data = data - ctx.ttl = ttl - ctx.neg_ttl = neg_ttl - ctx.went_stale = went_stale - ctx.l1_serializer = l1_serializer - ctx.resurrect_ttl = resurrect_ttl - ctx.shm_set_tries = shm_set_tries - ctx.rlock_opts = rlock_opts - ctx.data = data - ctx.err = nil - ctx.hit_lvl = nil - - cb_ctxs[n_cbs] = ctx - end - end - - res_idx = res_idx + 3 - end - - if n_cbs == 0 then - -- no callback to run, all items were in L1/L2 - res.n = res_idx - 1 - return res - end - - -- some L3 callbacks have to run - -- schedule threads as per our concurrency settings - -- we will use this thread as well - - local concurrency - if opts then - concurrency = opts.concurrency - end - - if not concurrency then - concurrency = BULK_DEFAULT_CONCURRENCY - end - - local threads - local threads_idx = 0 - - do - -- spawn concurrent threads - local thread_size - local n_threads = min(n_cbs, concurrency) - 1 - - if n_threads > 0 then - threads = tablepool.fetch("bulk_threads", n_threads, 0) - thread_size = ceil(n_cbs / concurrency) - end - - if self.debug then - ngx.log(ngx.DEBUG, "spawning ", n_threads, " threads to run ", - n_cbs, " callbacks") - end - - local from = 1 - local rest = n_cbs - - for i = 1, n_threads do - local to - if rest >= thread_size then - rest = rest - thread_size - to = from + thread_size - 1 - else - rest = 0 - to = from - end - - if self.debug then - ngx.log(ngx.DEBUG, "thread ", i, " running callbacks ", from, - " to ", to) - end - - threads_idx = threads_idx + 1 - threads[i] = thread_spawn(run_thread, self, cb_ctxs, from, to) - - from = from + thread_size - - if rest == 0 then - break - end - end - - if rest > 0 then - -- use this thread as one of our concurrent threads - local to = from + rest - 1 - - if self.debug then - ngx.log(ngx.DEBUG, "main thread running callbacks ", from, - " to ", to) - end - - run_thread(self, cb_ctxs, from, to) - end - end - - -- wait for other threads - - for i = 1, threads_idx do - local ok, err = thread_wait(threads[i]) - if not ok then - -- when thread_wait() fails, we don't get res_idx, and thus - -- cannot populate the appropriate res indexes with the - -- error - ngx_log(ERR, "failed to wait for thread number ", i, ": ", err) - end - end - - for i = 1, n_cbs do - local ctx = cb_ctxs[i] - local ctx_res_idx = ctx.res_idx - - res[ctx_res_idx] = ctx.data - res[ctx_res_idx + 1] = ctx.err - res[ctx_res_idx + 2] = ctx.hit_lvl - - tablepool.release("bulk_cb_ctx", ctx, true) -- no clear tab - end - - tablepool.release("bulk_cb_ctxs", cb_ctxs) - - if threads then - tablepool.release("bulk_threads", threads) - end - - res.n = res_idx - 1 - - return res - end - - -end -- get_bulk() - - -function _M:peek(key, stale) - if type(key) ~= "string" then - error("key must be a string", 2) - end - - -- restrict this key to the current namespace, so we isolate this - -- mlcache instance from potential other instances using the same - -- shm - local namespaced_key = self.name .. key - - local v, err, went_stale = self.dict:get_stale(namespaced_key) - if v == nil and err then - -- err can be 'flags' upon successful get_stale() calls, so we - -- also check v == nil - return nil, "could not read from lua_shared_dict: " .. err - end - - -- if we specified shm_miss, it might be a negative hit cached - -- there - if self.dict_miss and v == nil then - v, err, went_stale = self.dict_miss:get_stale(namespaced_key) - if v == nil and err then - -- err can be 'flags' upon successful get_stale() calls, so we - -- also check v == nil - return nil, "could not read from lua_shared_dict: " .. err - end - end - - if went_stale and not stale then - return nil - end - - if v ~= nil then - local value, err, at, ttl = unmarshall_from_shm(v) - if err then - return nil, "could not deserialize value after lua_shared_dict " .. - "retrieval: " .. err - end - - local remaining_ttl = 0 - - if ttl > 0 then - remaining_ttl = ttl - (now() - at) - - if remaining_ttl == 0 then - -- guarantee a non-zero remaining_ttl if ttl is set - remaining_ttl = 0.001 - end - end - - return remaining_ttl, nil, value, went_stale - end -end - - -function _M:set(key, opts, value) - if not self.broadcast then - error("no ipc to propagate update, specify opts.ipc_shm or opts.ipc", 2) - end - - if type(key) ~= "string" then - error("key must be a string", 2) - end - - do - -- restrict this key to the current namespace, so we isolate this - -- mlcache instance from potential other instances using the same - -- shm - local ttl, neg_ttl, _, l1_serializer, shm_set_tries = check_opts(self, - opts) - local namespaced_key = self.name .. key - - if self.dict_miss then - -- since we specified a separate shm for negative caches, we - -- must make sure that we clear any value that may have been - -- set in the other shm - local dict = value == nil and self.dict or self.dict_miss - - -- TODO: there is a potential race-condition here between this - -- :delete() and the subsequent :set() in set_shm() - local ok, err = dict:delete(namespaced_key) - if not ok then - return nil, "could not delete from shm: " .. err - end - end - - local _, err = set_shm_set_lru(self, key, namespaced_key, value, ttl, - neg_ttl, nil, shm_set_tries, - l1_serializer, true) - if err then - return nil, err - end - end - - local _, err = self.broadcast(self.events.invalidation.channel, key) - if err then - return nil, "could not broadcast update: " .. err - end - - return true -end - - -function _M:delete(key) - if not self.broadcast then - error("no ipc to propagate deletion, specify opts.ipc_shm or opts.ipc", - 2) - end - - if type(key) ~= "string" then - error("key must be a string", 2) - end - - -- delete from shm first - do - -- restrict this key to the current namespace, so we isolate this - -- mlcache instance from potential other instances using the same - -- shm - local namespaced_key = self.name .. key - - local ok, err = self.dict:delete(namespaced_key) - if not ok then - return nil, "could not delete from shm: " .. err - end - - -- instance uses shm_miss for negative caches, since we don't know - -- where the cached value is (is it nil or not?), we must remove it - -- from both - if self.dict_miss then - ok, err = self.dict_miss:delete(namespaced_key) - if not ok then - return nil, "could not delete from shm: " .. err - end - end - end - - -- delete from LRU and propagate - self.lru:delete(key) - - local _, err = self.broadcast(self.events.invalidation.channel, key) - if err then - return nil, "could not broadcast deletion: " .. err - end - - return true -end - - -function _M:purge(flush_expired) - if not self.broadcast then - error("no ipc to propagate purge, specify opts.ipc_shm or opts.ipc", 2) - end - - if not self.lru.flush_all and LRU_INSTANCES[self.name] ~= self.lru then - error("cannot purge when using custom LRU cache with " .. - "OpenResty < 1.13.6.2", 2) - end - - -- clear shm first - self.dict:flush_all() - - -- clear negative caches shm if specified - if self.dict_miss then - self.dict_miss:flush_all() - end - - if flush_expired then - self.dict:flush_expired() - - if self.dict_miss then - self.dict_miss:flush_expired() - end - end - - -- clear LRU content and propagate - rebuild_lru(self) - - local _, err = self.broadcast(self.events.purge.channel, "") - if err then - return nil, "could not broadcast purge: " .. err - end - - return true -end - - -function _M:update(timeout) - if not self.poll then - error("no polling configured, specify opts.ipc_shm or opts.ipc.poll", 2) - end - - local _, err = self.poll(timeout) - if err then - return nil, "could not poll ipc events: " .. err - end - - return true -end - - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/lib/resty/mlcache/ipc.lua b/plugins/openresty/waf/lib/resty/mlcache/ipc.lua deleted file mode 100644 index 55cf0113b..000000000 --- a/plugins/openresty/waf/lib/resty/mlcache/ipc.lua +++ /dev/null @@ -1,257 +0,0 @@ --- vim: ts=4 sts=4 sw=4 et: - -local ERR = ngx.ERR -local WARN = ngx.WARN -local INFO = ngx.INFO -local sleep = ngx.sleep -local shared = ngx.shared -local worker_pid = ngx.worker.pid -local ngx_log = ngx.log -local fmt = string.format -local sub = string.sub -local find = string.find -local min = math.min -local type = type -local pcall = pcall -local error = error -local insert = table.insert -local tonumber = tonumber -local setmetatable = setmetatable - - -local INDEX_KEY = "lua-resty-ipc:index" -local FORCIBLE_KEY = "lua-resty-ipc:forcible" -local POLL_SLEEP_RATIO = 2 - - -local function marshall(worker_pid, channel, data) - return fmt("%d:%d:%s%s", worker_pid, #data, channel, data) -end - - -local function unmarshall(str) - local sep_1 = find(str, ":", nil , true) - local sep_2 = find(str, ":", sep_1 + 1, true) - - local pid = tonumber(sub(str, 1 , sep_1 - 1)) - local data_len = tonumber(sub(str, sep_1 + 1, sep_2 - 1)) - - local channel_last_pos = #str - data_len - - local channel = sub(str, sep_2 + 1, channel_last_pos) - local data = sub(str, channel_last_pos + 1) - - return pid, channel, data -end - - -local function log(lvl, ...) - return ngx_log(lvl, "[ipc] ", ...) -end - - -local _M = {} -local mt = { __index = _M } - - -function _M.new(shm, debug) - local dict = shared[shm] - if not dict then - return nil, "no such lua_shared_dict: " .. shm - end - - local self = { - dict = dict, - pid = debug and 0 or worker_pid(), - idx = 0, - callbacks = {}, - } - - return setmetatable(self, mt) -end - - -function _M:subscribe(channel, cb) - if type(channel) ~= "string" then - error("channel must be a string", 2) - end - - if type(cb) ~= "function" then - error("callback must be a function", 2) - end - - if not self.callbacks[channel] then - self.callbacks[channel] = { cb } - - else - insert(self.callbacks[channel], cb) - end -end - - -function _M:broadcast(channel, data) - if type(channel) ~= "string" then - error("channel must be a string", 2) - end - - if type(data) ~= "string" then - error("data must be a string", 2) - end - - local marshalled_event = marshall(worker_pid(), channel, data) - - local idx, err = self.dict:incr(INDEX_KEY, 1, 0) - if not idx then - return nil, "failed to increment index: " .. err - end - - local ok, err, forcible = self.dict:set(idx, marshalled_event) - if not ok then - return nil, "failed to insert event in shm: " .. err - end - - if forcible then - -- take note that eviction has started - -- we repeat this flagging to avoid this key from ever being - -- evicted itself - local ok, err = self.dict:set(FORCIBLE_KEY, true) - if not ok then - return nil, "failed to set forcible flag in shm: " .. err - end - end - - return true -end - - --- Note: if this module were to be used by users (that is, users can implement --- their own pub/sub events and thus, callbacks), this method would then need --- to consider the time spent in callbacks to prevent long running callbacks --- from penalizing the worker. --- Since this module is currently only used by mlcache, whose callback is an --- shm operation, we only worry about the time spent waiting for events --- between the 'incr()' and 'set()' race condition. -function _M:poll(timeout) - if timeout ~= nil and type(timeout) ~= "number" then - error("timeout must be a number", 2) - end - - local shm_idx, err = self.dict:get(INDEX_KEY) - if err then - return nil, "failed to get index: " .. err - end - - if shm_idx == nil then - -- no events to poll yet - return true - end - - if type(shm_idx) ~= "number" then - return nil, "index is not a number, shm tampered with" - end - - if not timeout then - timeout = 0.3 - end - - if self.idx == 0 then - local forcible, err = self.dict:get(FORCIBLE_KEY) - if err then - return nil, "failed to get forcible flag from shm: " .. err - end - - if forcible then - -- shm lru eviction occurred, we are likely a new worker - -- skip indexes that may have been evicted and resume current - -- polling idx - self.idx = shm_idx - 1 - end - - else - -- guard: self.idx <= shm_idx - self.idx = min(self.idx, shm_idx) - end - - local elapsed = 0 - - for _ = self.idx, shm_idx - 1 do - -- fetch event from shm with a retry policy in case - -- we run our :get() in between another worker's - -- :incr() and :set() - - local v - local idx = self.idx + 1 - - do - local perr - local pok = true - local sleep_step = 0.001 - - while elapsed < timeout do - v, err = self.dict:get(idx) - if v ~= nil or err then - break - end - - if pok then - log(INFO, "no event data at index '", idx, "', ", - "retrying in: ", sleep_step, "s") - - -- sleep is not available in all ngx_lua contexts - -- if we fail once, never retry to sleep - pok, perr = pcall(sleep, sleep_step) - if not pok then - log(WARN, "could not sleep before retry: ", perr, - " (note: it is safer to call this function ", - "in contexts that support the ngx.sleep() ", - "API)") - end - end - - elapsed = elapsed + sleep_step - sleep_step = min(sleep_step * POLL_SLEEP_RATIO, - timeout - elapsed) - end - end - - -- fetch next event on next iteration - -- even if we timeout, we might miss 1 event (we return in timeout and - -- we don't retry that event), but it's better than being stuck forever - -- on an event that might have been evicted from the shm. - self.idx = idx - - if elapsed >= timeout then - return nil, "timeout" - end - - if err then - log(ERR, "could not get event at index '", self.idx, "': ", err) - - elseif type(v) ~= "string" then - log(ERR, "event at index '", self.idx, "' is not a string, ", - "shm tampered with") - - else - local pid, channel, data = unmarshall(v) - - if self.pid ~= pid then - -- coming from another worker - local cbs = self.callbacks[channel] - if cbs then - for j = 1, #cbs do - local pok, perr = pcall(cbs[j], data) - if not pok then - log(ERR, "callback for channel '", channel, - "' threw a Lua error: ", perr) - end - end - end - end - end - end - - return true -end - - -return _M \ No newline at end of file diff --git a/plugins/openresty/waf/lib/resty/uuid.lua b/plugins/openresty/waf/lib/resty/uuid.lua deleted file mode 100644 index 2d73b43c2..000000000 --- a/plugins/openresty/waf/lib/resty/uuid.lua +++ /dev/null @@ -1,427 +0,0 @@ --- vim:set ts=4 sts=4 sw=4 et: - ---- jit-uuid --- Fast and dependency-free UUID library for LuaJIT/ngx_lua. --- @module jit-uuid --- @author Thibault Charbonnier --- @license MIT --- @release 0.0.7 - - -local bit = require 'bit' - - -local tohex = bit.tohex -local band = bit.band -local bor = bit.bor - - -local _M = { - _VERSION = '0.0.7' -} - - ----------- --- seeding ----------- - - ---- Seed the random number generator. --- Under the hood, this function calls `math.randomseed`. --- It makes sure to use the most appropriate seeding technique for --- the current environment, guaranteeing a unique seed. --- --- To guarantee unique UUIDs, you must have correctly seeded --- the Lua pseudo-random generator (with `math.randomseed`). --- You are free to seed it any way you want, but this function --- can do it for you if you'd like, with some added guarantees. --- --- @param[type=number] seed (Optional) A seed to use. If none given, will --- generate one trying to use the most appropriate technique. --- @treturn number `seed`: the seed given to `math.randomseed`. --- @usage --- local uuid = require 'resty.jit-uuid' --- uuid.seed() --- --- -- in ngx_lua, seed in the init_worker context: --- init_worker_by_lua { --- local uuid = require 'resty.jit-uuid' --- uuid.seed() --- } -function _M.seed(seed) - if not seed then - if ngx then - seed = ngx.time() + ngx.worker.pid() - - elseif package.loaded['socket'] and package.loaded['socket'].gettime then - seed = package.loaded['socket'].gettime()*10000 - - else - seed = os.time() - end - end - - math.randomseed(seed) - - return seed -end - - -------------- --- validation -------------- - - -do - if ngx and string.find(ngx.config.nginx_configure(),'--with-pcre-jit',nil,true) then - local type = type - local re_find = ngx.re.find - local regex = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' - - - --- Validate a string as a UUID. - -- To be considered valid, a UUID must be given in its canonical - -- form (hexadecimal digits including the hyphen characters). - -- This function validates UUIDs disregarding their generation algorithm, - -- and in a case-insensitive manner, but checks the variant field. - -- - -- Use JIT PCRE if available in OpenResty or fallbacks on Lua patterns. - -- - -- @param[type=string] str String to verify. - -- @treturn boolean `valid`: true if valid UUID, false otherwise. - -- @usage - -- local uuid = require 'resty.jit-uuid' - -- - -- uuid.is_valid 'cbb297c0-a956-486d-ad1d-f9bZZZZZZZZZ' --> false - -- uuid.is_valid 'cbb297c0-a956-486d-dd1d-f9b42df9465a' --> false (invalid variant) - -- uuid.is_valid 'cbb297c0a956486dad1df9b42df9465a' --> false (no dashes) - -- uuid.is_valid 'cbb297c0-a956-486d-ad1d-f9b42df9465a' --> true - function _M.is_valid(str) - -- it has proven itself efficient to first check the length with an - -- evenly distributed set of valid and invalid uuid lengths. - if type(str) ~= 'string' or #str ~= 36 then - return false - end - - return re_find(str, regex, 'ioj') ~= nil - end - - else - local match = string.match - local d = '[0-9a-fA-F]' - local p = '^' .. table.concat({ - d:rep(8), - d:rep(4), - d:rep(4), - '[89ab]' .. d:rep(3), - d:rep(12) - }, '%-') .. '$' - - - function _M.is_valid(str) - if type(str) ~= 'string' or #str ~= 36 then - return false - end - - return match(str, p) ~= nil - end - end -end - - ----------------- --- v4 generation ----------------- - - -do - local fmt = string.format - local random = math.random - - - --- Generate a v4 UUID. - -- v4 UUIDs are created from randomly generated numbers. - -- - -- @treturn string `uuid`: a v4 (randomly generated) UUID. - -- @usage - -- local uuid = require 'resty.jit-uuid' - -- - -- local u1 = uuid() ---> __call metamethod - -- local u2 = uuid.generate_v4() - function _M.generate_v4() - return (fmt('%s%s%s%s-%s%s-%s%s-%s%s-%s%s%s%s%s%s', - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - - tohex(bor(band(random(0, 255), 0x0F), 0x40), 2), - tohex(random(0, 255), 2), - - tohex(bor(band(random(0, 255), 0x3F), 0x80), 2), - tohex(random(0, 255), 2), - - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - tohex(random(0, 255), 2), - tohex(random(0, 255), 2))) - end -end - - ----------------- --- v3/v5 generation ----------------- - - -do - if ngx then - local ffi = require 'ffi' - - - local tonumber = tonumber - local assert = assert - local error = error - local concat = table.concat - local type = type - local char = string.char - local fmt = string.format - local sub = string.sub - local gmatch = ngx.re.gmatch - local sha1_bin = ngx.sha1_bin - local md5 = ngx.md5 - local C = ffi.C - local ffi_new = ffi.new - local ffi_str = ffi.string - local ffi_cast = ffi.cast - local new_tab - do - local ok - ok, new_tab = pcall(require, 'table.new') - if not ok then - new_tab = function(narr, nrec) return {} end - end - end - - - ffi.cdef [[ - typedef unsigned char u_char; - typedef intptr_t ngx_int_t; - - u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len); - ngx_int_t ngx_hextoi(u_char *line, size_t n); - ]] - - - local str_type = ffi.typeof('uint8_t[?]') - local u_char_type = ffi.typeof('u_char *') - - - local function bin_tohex(s) - local slen = #s - local blen = slen * 2 - local buf = ffi_new(str_type, blen) - - C.ngx_hex_dump(buf, s, slen) - - return ffi_str(buf, blen) - end - - - local function hex_to_i(s) - local buf = ffi_cast(u_char_type, s) - - local n = tonumber(C.ngx_hextoi(buf, #s)) - if n == -1 then - error("could not convert hex to number") - end - - return n - end - - - local buf = new_tab(16, 0) - - - local function factory(namespace, hash_fn) - if not _M.is_valid(namespace) then - return nil, 'namespace must be a valid UUID' - end - - local i = 0 - local iter, err = gmatch(namespace, [[([\da-f][\da-f])]]) - if not iter then - return nil, 'could not create iter: ' .. err - end - - while true do - local m, err = iter() - if err then - return nil, err - end - - if not m then - break - end - - i = i + 1 - buf[i] = char(tonumber(m[0], 16)) - end - - assert(i == 16, "invalid binary namespace buffer length") - local ns = concat(buf) - - return function(name) - if type(name) ~= 'string' then - return nil, 'name must be a string' - end - - local hash, ver, var = hash_fn(ns, name) - - return (fmt('%s-%s-%s%s-%s%s-%s', sub(hash, 1, 8), - sub(hash, 9, 12), - ver, - sub(hash, 15, 16), - var, - sub(hash, 19, 20), - sub(hash, 21, 32))) - end - end - - - local function v3_hash(binary, name) - local hash = md5(binary .. name) - - return hash, - tohex(bor(band(hex_to_i(sub(hash, 13, 14)), 0x0F), 0x30), 2), - tohex(bor(band(hex_to_i(sub(hash, 17, 18)), 0x3F), 0x80), 2) - end - - - local function v5_hash(binary, name) - local hash = bin_tohex(sha1_bin(binary .. name)) - - return hash, - tohex(bor(band(hex_to_i(sub(hash, 13, 14)), 0x0F), 0x50), 2), - tohex(bor(band(hex_to_i(sub(hash, 17, 18)), 0x3F), 0x80), 2) - end - - - --- Instanciate a v3 UUID factory. - -- @function factory_v3 - -- Creates a closure generating namespaced v3 UUIDs. - -- @param[type=string] namespace (must be a valid UUID according to `is_valid`) - -- @treturn function `factory`: a v3 UUID generator. - -- @treturn string `err`: a string describing an error - -- @usage - -- local uuid = require 'resty.jit-uuid' - -- - -- local fact = assert(uuid.factory_v3('e6ebd542-06ae-11e6-8e82-bba81706b27d')) - -- - -- local u1 = fact('hello') - -- ---> 3db7a435-8c56-359d-a563-1b69e6802c78 - -- - -- local u2 = fact('foobar') - -- ---> e8d3eeba-7723-3b72-bbc5-8f598afa6773 - function _M.factory_v3(namespace) - return factory(namespace, v3_hash) - end - - - --- Instanciate a v5 UUID factory. - -- @function factory_v5 - -- Creates a closure generating namespaced v5 UUIDs. - -- @param[type=string] namespace (must be a valid UUID according to `is_valid`) - -- @treturn function `factory`: a v5 UUID generator. - -- @treturn string `err`: a string describing an error - -- @usage - -- local uuid = require 'resty.jit-uuid' - -- - -- local fact = assert(uuid.factory_v5('e6ebd542-06ae-11e6-8e82-bba81706b27d')) - -- - -- local u1 = fact('hello') - -- ---> 4850816f-1658-5890-8bfd-1ed14251f1f0 - -- - -- local u2 = fact('foobar') - -- ---> c9be99fc-326b-5066-bdba-dcd31a6d01ab - function _M.factory_v5(namespace) - return factory(namespace, v5_hash) - end - - - --- Generate a v3 UUID. - -- v3 UUIDs are created from a namespace and a name (a UUID and a string). - -- The same name and namespace result in the same UUID. The same name and - -- different namespaces result in different UUIDs, and vice-versa. - -- The resulting UUID is derived using MD5 hashing. - -- - -- This is a sugar function which instanciates a short-lived v3 UUID factory. - -- It is an expensive operation, and intensive generation using the same - -- namespaces should prefer allocating their own long-lived factory with - -- `factory_v3`. - -- - -- @param[type=string] namespace (must be a valid UUID according to `is_valid`) - -- @param[type=string] name - -- @treturn string `uuid`: a v3 (namespaced) UUID. - -- @treturn string `err`: a string describing an error - -- @usage - -- local uuid = require 'resty.jit-uuid' - -- - -- local u = uuid.generate_v3('e6ebd542-06ae-11e6-8e82-bba81706b27d', 'hello') - -- ---> 3db7a435-8c56-359d-a563-1b69e6802c78 - function _M.generate_v3(namespace, name) - local fact, err = _M.factory_v3(namespace) - if not fact then - return nil, err - end - - return fact(name) - end - - - --- Generate a v5 UUID. - -- v5 UUIDs are created from a namespace and a name (a UUID and a string). - -- The same name and namespace result in the same UUID. The same name and - -- different namespaces result in different UUIDs, and vice-versa. - -- The resulting UUID is derived using SHA-1 hashing. - -- - -- This is a sugar function which instanciates a short-lived v5 UUID factory. - -- It is an expensive operation, and intensive generation using the same - -- namespaces should prefer allocating their own long-lived factory with - -- `factory_v5`. - -- - -- @param[type=string] namespace (must be a valid UUID according to `is_valid`) - -- @param[type=string] name - -- @treturn string `uuid`: a v5 (namespaced) UUID. - -- @treturn string `err`: a string describing an error - -- @usage - -- local uuid = require 'resty.jit-uuid' - -- - -- local u = uuid.generate_v5('e6ebd542-06ae-11e6-8e82-bba81706b27d', 'hello') - -- ---> 4850816f-1658-5890-8bfd-1ed14251f1f0 - function _M.generate_v5(namespace, name) - local fact, err = _M.factory_v5(namespace) - if not fact then - return nil, err - end - - return fact(name) - end - - else - function _M.factory_v3() error('v3 UUID generation only supported in ngx_lua', 2) end - function _M.generate_v3() error('v3 UUID generation only supported in ngx_lua', 2) end - function _M.factory_v5() error('v5 UUID generation only supported in ngx_lua', 2) end - function _M.generate_v5() error('v5 UUID generation only supported in ngx_lua', 2) end - end -end - - -return setmetatable(_M, { - __call = _M.generate_v4 -}) \ No newline at end of file diff --git a/plugins/openresty/waf/lib/stringutf8.lua b/plugins/openresty/waf/lib/stringutf8.lua deleted file mode 100644 index 921757392..000000000 --- a/plugins/openresty/waf/lib/stringutf8.lua +++ /dev/null @@ -1,149 +0,0 @@ -local error = error -local str_len = string.len -local new_table = table.new -local concat_table = table.concat -local insert_table = table.insert -local byte_str = string.byte -local sub_str = string.sub -local type = type -local abs = math.abs -local match_str = string.match -local ngx_re_gsub = ngx.re.gsub - -local _M = {} - -local INDEX_OUT_OF_RANGE = "String index out of range: " -local NOT_NUMBER = "number expected, got " -local NOT_STRING = "string expected, got " -local NOT_STRING_NIL = "string expected, got nil" - -function _M.to_char_array(str) - local array - if str then - local length = str_len(str) - array = new_table(length, 0) - - local byteLength = 1 - local i, j = 1, 1 - while i <= length do - local firstByte = byte_str(str, i) - if firstByte >= 0 and firstByte < 128 then - byteLength = 1 - - elseif firstByte > 191 and firstByte < 224 then - byteLength = 2 - - elseif firstByte > 223 and firstByte < 240 then - byteLength = 3 - - elseif firstByte > 239 and firstByte < 248 then - byteLength = 4 - end - - j = i + byteLength - local char = sub_str(str, i, j - 1) - i = j - insert_table(array, char) - end - end - - return array -end - -function _M.sub(str, i, j) - local str_sub - if str then - if i == nil then - i = 1 - end - - if type(i) ~= "number" then - error(NOT_NUMBER .. type(i)) - end - - if i < 1 then - error(INDEX_OUT_OF_RANGE .. i) - end - - if j then - if type(j) ~= "number" then - error(NOT_NUMBER .. type(j)) - end - end - - local array = _M.to_char_array(str) - if array then - local length = #array - local subLen = length - i - if subLen < 0 then - error(INDEX_OUT_OF_RANGE .. subLen) - end - - if not j then - str_sub = concat_table(array, "", i) - else - if abs(j) > length then - error(INDEX_OUT_OF_RANGE .. j) - end - if j < 0 then - j = length + j + 1 - end - str_sub = concat_table(array, "", i, j) - end - end - end - - return str_sub -end - -function _M.trim(str) - if str then - str = ngx_re_gsub(str, "^\\s*|\\s*$", "", "jo") - end - - return str -end - -function _M.len(str) - local str_length = 0 - if str then - if type(str) ~= "string" then - error(NOT_STRING .. type(str)) - end - - local length = str_len(str) - - local i = 1 - while i <= length do - local firstByte = byte_str(str, i) - if firstByte >= 0 and firstByte < 128 then - i = i + 1 - - elseif firstByte > 191 and firstByte < 224 then - i = i + 2 - - elseif firstByte > 223 and firstByte < 240 then - i = i + 3 - - elseif firstByte > 239 and firstByte < 248 then - i = i + 4 - end - - str_length = str_length + 1 - end - else - error(NOT_STRING_NIL) - end - - return str_length -end - -function _M.default_if_blank(str, default_str) - if str == nil or match_str(str, "^%s*$") then - return default_str - end - - return str -end - -return _M diff --git a/plugins/openresty/waf/lib/utils.lua b/plugins/openresty/waf/lib/utils.lua deleted file mode 100644 index b9b8ad251..000000000 --- a/plugins/openresty/waf/lib/utils.lua +++ /dev/null @@ -1,200 +0,0 @@ -local geoip = require "geoip" -local sub_str = string.sub -local pairs = pairs -local insert_table = table.insert -local tonumber = tonumber -local ipairs = ipairs -local type = type -local find_str = string.find -local gmatch_str = string.gmatch -local pcall = pcall -local random = math.random -local unescape_uri = ngx.unescape_uri - -local _M = {} - -function _M.split(input_string, delimiter) - local result = {} - for part in input_string:gmatch("([^" .. delimiter .. "]+)") do - insert_table(result, part) - end - return result -end - -function _M.get_cookie_list(cookie_str) - local cookies = {} - for cookie in cookie_str:gmatch("([^;]+)") do - local key, value = cookie:match("^%s*([^=]+)=(.*)$") - if key and value then - cookies[key] = value - end - end - return cookies -end - -function _M.unescape_uri(str) - local newStr = str - for t = 1, 2 do - local temp = unescape_uri(newStr) - if not temp then - break - end - newStr = temp - end - return newStr -end - -function _M.get_expire_time() - local localtime = ngx.localtime() - local hour = sub_str(localtime, 12, 13) - local expire_time = (24 - tonumber(hour)) * 3600 - return expire_time -end - -function _M.get_date_hour() - local localtime = ngx.localtime() - local hour = sub_str(localtime, 1, 13) - return hour -end - -function _M.getHours() - local hours = {} - local today = ngx.today() - local hour = nil - for i = 0, 23 do - if i < 10 then - hour = today .. ' 0' .. i - else - hour = today .. ' ' .. i - end - hours[i + 1] = hour - end - - return hours -end - -function _M.ipv4_to_int(ip) - local ipInt = 0 - for i, octet in ipairs({ ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") }) do - ipInt = ipInt + tonumber(octet) * 256 ^ (4 - i) - end - return ipInt -end - -function _M.is_ipv6(ip) - if find_str(ip, ':') then - return true - end - return false -end - -function _M.is_ip_in_array(ip, ipStart, ipEnd) - if ip >= ipStart and ip <= ipEnd then - return true - end - return false -end - -function _M.get_real_ip() - local var = ngx.var - local ips = { - var.http_x_forwarded_for, - var.http_proxy_client_ip, - var.http_wl_proxy_client_ip, - var.http_http_client_ip, - var.http_http_x_forwarded_for, - var.remote_addr - } - - for _, ip in pairs(ips) do - if ip and ip ~= "" then - if type(ip) == "table" then - ip = ip[1] - end - return ip - end - end - - return "unknown" -end - -function _M.get_ip_location(ip) - if _M.is_intranet_address(ip) then - return { - country = { ["zh"] = "内网", ["en"] = "Intranet" }, - longitude = 0, - latitude = 0, - iso = "Local" - } - 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) - return geo_res - - end -end - -function _M.get_header(headerKey) - return ngx.req.get_headers(20000)[headerKey] -end - -function _M.get_headers() - return ngx.req.get_headers(20000) -end - -function _M.is_intranet_address(ip_addr) - if not ip_addr then - return false - end - if ip_addr == "unknown" then - return false - end - if find_str(ip_addr, ':') then - return false - end - - local parts = {} - for part in gmatch_str(ip_addr, "%d+") do - insert_table(parts, tonumber(part)) - end - if parts[1] == 10 or - (parts[1] == 192 and parts[2] == 168) or - (parts[1] == 172 and parts[2] >= 16 and parts[2] <= 31) then - return true - else - return false - end -end - -function _M.get_wafdb(waf_db_path) - local ok, sqlite3 = pcall(function() - return require "lsqlite3" - end) - if not ok then - return nil - 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 deleted file mode 100644 index 3d0bca317..000000000 --- a/plugins/openresty/waf/log_and_traffic.lua +++ /dev/null @@ -1,240 +0,0 @@ -local utils = require "utils" -local config = require "config" -local redis_util = require "redis_util" -local action = require "action" -local uuid = require"resty.uuid" - -local upper_str = string.upper -local tonumber = tonumber -local pairs = pairs -local type = type -local concat_table = table.concat - -local function write_req_log(attack) - local wafdb = utils.get_wafdb(config.waf_log_db_path) - if not wafdb then - ngx.log(ngx.ERR, "get log db failed") - return - end - - local real_ip = ngx.ctx.ip - local ip_location = ngx.ctx.ip_location - local country - local province - local longitude = 0.0 - local latitude = 0.0 - local iso = "CN" - if ip_location then - country = ip_location.country or { - ["zh"] = "unknown", - ["en"] = "unknown" - } - province = ip_location.province or { - ["zh"] = "", - ["en"] = "" - } - longitude = ip_location.longitude - latitude = ip_location.latitude - iso = ip_location.iso - end - - local exec_rule = {} - local rule_action = "" - local exec_rule_type = "" - local match_rule_detail = "" - local match_rule_type = "" - local is_attack = 0 - local is_block = 0 - local blocking_time = 0 - - local method = "" - local uri = "" - local ua = "" - local host = "" - local protocol = "" - local website_key = "" - local logs_str = "" - - if attack then - exec_rule = ngx.ctx.exec_rule - rule_action = exec_rule.action - exec_rule_type = exec_rule.type - is_attack = 1 - method = ngx.req.get_method() - uri = ngx.var.request_uri - ua = ngx.ctx.ua - host = ngx.var.server_name - protocol = ngx.var.server_protocol or "" - website_key = ngx.ctx.website_key - - if exec_rule.match_rule then - match_rule_detail = exec_rule.match_rule.rule - match_rule_type = exec_rule.match_rule.type - end - - if ngx.ctx.ip_blocked then - is_block = 1 - blocking_time = tonumber(exec_rule.ipBlockTime) - end - - logs_str = method .. " " .. uri .. " "..protocol.."\n" - local headers = ngx.req.get_headers(20000) - for k, v in pairs(headers) do - local value = "" - if v then - if type(v) == "table" then - value = concat_table(v, ",") - else - value = v - end - end - logs_str = logs_str .. upper_str(k) .. ": " .. value .. "\n" - end - end - - local log_id = uuid() - local insertQuery = [[ - INSERT INTO req_logs ( - id, ip, ip_iso, ip_country_zh, ip_country_en, - ip_province_zh, ip_province_en, ip_longitude, ip_latitude, - localtime, server_name, website_key, host, method, - uri, user_agent, exec_rule, rule_type, match_rule, match_value, - nginx_log, blocking_time, action, is_block,is_attack - ) VALUES ( - :id, :real_ip, :iso, :country_zh, :country_en, - :province_zh, :province_en,:longitude, :latitude, - DATETIME('now'), :server_name,:host, :website_key, :method, - :uri, :ua, :exec_rule, :rule_type, :match_rule, :match_value, - :logs_str, :blocking_time, :action, :is_block, :is_attack - ) - ]] - - wafdb:execute([[BEGIN TRANSACTION]]) - - local stmt = wafdb:prepare(insertQuery) - stmt:bind_names { - id = log_id, - iso = iso, - real_ip = real_ip, - country_zh = country["zh"], - country_en = country["en"], - province_zh = province["zh"], - province_en = province["en"], - longitude = longitude, - latitude = latitude, - host = host, - server_name = host, - website_key = website_key, - method = method, - uri = uri, - ua = ua, - exec_rule = exec_rule_type, - rule_type = match_rule_type, - match_rule = match_rule_detail, - match_value = "", - logs_str = logs_str, - blocking_time = blocking_time or 0, - action = rule_action, - is_block = is_block, - is_attack = is_attack - } - stmt:step() - stmt:finalize() - - local code2 = 101 - if ngx.ctx.ip_blocked then - local insertBlockIp = [[ - INSERT INTO block_ips (ip, is_block, blocking_time, req_log_id,create_date) - VALUES (:ip, :is_block, :blocking_time, :req_log_id, DATETIME('now')) - ]] - stmt = wafdb:prepare(insertBlockIp) - stmt:bind_names { - ip=real_ip, - is_block = is_block, - blocking_time = blocking_time or 0, - req_log_id = log_id - } - code2 = stmt:step() - stmt:finalize() - end - - wafdb:execute([[COMMIT]]) - - --local error_msg = wafdb:errmsg() - --if error_msg then - -- ngx.log(ngx.ERR, "insert attack_log error ", error_msg .. " ") - --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 - -local add_count = function(shared_dict,key) - local count, _ = shared_dict:incr(key, 1) - if not count then - shared_dict:set(key, 1) - end -end - -local function count_req_status(is_attack) - local status = ngx.status - 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") - end - if (status >= 500) then - add_count(req_count, "count_5xx") - end - if is_attack then - add_count(req_count, "attack_count") - end -end - -if config.is_waf_on() then - if ngx.ctx.is_waf_url then - return - end - count_not_found() - local is_attack = ngx.ctx.is_attack - - if not ngx.ctx.ip then - ngx.ctx.ip = utils.get_real_ip() - ngx.ctx.ip_location = utils.get_ip_location(ngx.ctx.ip) - end - - count_req_status(is_attack) - if is_attack then - write_req_log(is_attack) - end -end diff --git a/plugins/openresty/waf/rules/acl.json b/plugins/openresty/waf/rules/acl.json deleted file mode 100644 index 218d999e1..000000000 --- a/plugins/openresty/waf/rules/acl.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "name": "no cookie", - "conditions": [ - { - "field": "URL", - "pattern": "eq", - "rule": "/test/\\d+\\.html" - }, - { - "field": "Cookie", - "pattern": "eq", - "rule": "" - } - ], - "action": "deny", - "code": 403, - "res": "", - "ipBlock": "off", - "ipBlockTime": 60, - "description": "拦截不带Cookie的请求" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/args.json b/plugins/openresty/waf/rules/args.json deleted file mode 100644 index bc44ba4c1..000000000 --- a/plugins/openresty/waf/rules/args.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "name": "sqlInject1", - "rule": "select.+(from|limit)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject2", - "rule": "(?:(union(.*?)select))", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject3", - "rule": "having|rongjitest", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject4", - "rule": "sleep\\((\\s*)(\\d*)(\\s*)\\)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject5", - "rule": "benchmark\\((.*)\\,(.*)\\)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject6", - "rule": "group\\s+by.+\\(", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject7", - "rule": "(?:from\\W+information_schema\\W)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject8", - "rule": "(?:(?:current_)user|database|schema|connection_id)\\s*\\(", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject9", - "rule": "into(\\s+)+(?:dump|out)file\\s*", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject10", - "rule": "\\s+(or|xor|and)\\s+.*(=|<|>|'|\")", - "type": "sqlInject" - }, - { - "state": "on", - "name": "args1", - "rule": "xwork.MethodAccessor", - "type": "args", - "description": "Struts 恶意参数过滤" - }, - { - "state": "on", - "name": "args2", - "rule": "xwork\\.MethodAccessor", - "type": "args", - "description": "Struts 恶意参数过滤" - }, - { - "state": "on", - "name": "oneWordTrojan1", - "rule": "(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(", - "type": "oneWordTrojan" - }, - { - "state": "on", - "name": "oneWordTrojan2", - "rule": "\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[", - "type": "oneWordTrojan" - }, - { - "state": "on", - "name": "protocolFilter1", - "rule": "(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/", - "type": "protocolFilter", - "description": "协议过滤" - }, - { - "state": "on", - "name": "dirFilter1", - "rule": "(?:etc\\/\\W*passwd)", - "type": "dirFilter" - }, - { - "state": "on", - "name": "dirFilter2", - "rule": "java\\.lang", - "type": "dirFilter" - }, - { - "state": "on", - "name": "xss1", - "rule": "\\<(iframe|script|body|img|layer|div|meta|style|base|object|input)", - "type": "xss" - }, - { - "state": "on", - "name": "xss2", - "rule": "(onmouseover|onerror|onload)\\=", - "type": "xss" - }, - { - "state": "on", - "name": "xss3", - "rule": "base64_decode\\(", - "type": "xss" - }, - { - "state": "on", - "name": "webshell1", - "rule": "/shell?cd+/tmp;\\s*rm+-rf\\+\\*;\\s*wget", - "type": "webshell" - }, - { - "state": "on", - "name": "phpExec1", - "rule": "/systembc/password.php", - "type": "phpExec" - }, - { - "state":"on", - "name": "scannerFilter1", - "rule":"(Acunetix-Aspect|Acunetix-Aspect-Password|Acunetix-Aspect-Queries|X-WIPP|X-RequestManager-Memo|X-Request-Memo|X-Scan-Memo)", - "type": "scannerFilter" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/ccurl.json b/plugins/openresty/waf/rules/ccurl.json deleted file mode 100644 index 544b7b4dd..000000000 --- a/plugins/openresty/waf/rules/ccurl.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/cookie.json b/plugins/openresty/waf/rules/cookie.json deleted file mode 100644 index 147a05eec..000000000 --- a/plugins/openresty/waf/rules/cookie.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "name": "dirFilter1", - "rule": "\\.\\./", - "type": "dirFilter" - }, - { - "state": "on", - "name": "dirFilter2", - "rule": "\\:\\$", - "type": "dirFilter" - }, - { - "state": "on", - "name": "dirFilter3", - "rule": "\\$\\{", - "type": "dirFilter" - }, - { - "state": "on", - "name": "dirFilter4", - "rule": "(?:etc\\/\\W*passwd)", - "type": "dirFilter" - }, - { - "state": "on", - "name": "dirFilter5", - "rule": "java\\.lang", - "type": "dirFilter" - }, - { - "state": "on", - "name": "sqlInject1", - "rule": "select.+(from|limit)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject2", - "rule": "(?:(union(.*?)select))", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject3", - "rule": "having|rongjitest", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject4", - "rule": "sleep\\((\\s*)(\\d*)(\\s*)\\)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject5", - "rule": "benchmark\\((.*)\\,(.*)\\)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject6", - "rule": "group\\s+by.+\\(", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject7", - "rule": "(?:from\\W+information_schema\\W)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject8", - "rule": "(?:(?:current_)user|database|schema|connection_id)\\s*\\(", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject9", - "rule": "into(\\s+)+(?:dump|out)file\\s*", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject10", - "rule": "\\s+(or|xor|and)\\s+.*(=|<|>|'|\")", - "type": "sqlInject" - }, - { - "state": "on", - "name": "args1", - "rule": "xwork.MethodAccessor", - "type": "args", - "description": "Struts 恶意参数过滤" - }, - { - "state": "on", - "name": "args2", - "rule": "xwork\\.MethodAccessor", - "type": "args", - "description": "Struts 恶意参数过滤" - }, - { - "state": "on", - "name": "oneWordTrojan1", - "rule": "(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(", - "type": "oneWordTrojan" - }, - { - "state": "on", - "name": "oneWordTrojan2", - "rule": "\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[", - "type": "oneWordTrojan" - }, - { - "state": "on", - "name": "protocolFilter1", - "rule": "(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/", - "type": "protocolFilter" - }, - { - "state":"on", - "name":"scannerFilter1", - "rule":"(CustomCookie|acunetixCookie)", - "type": "scannerFilter" - }, - { - "state": "on", - "name": "xss1", - "rule": "base64_decode\\(", - "type": "xss" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/defaultUaBlack.json b/plugins/openresty/waf/rules/defaultUaBlack.json deleted file mode 100644 index b5b984b8f..000000000 --- a/plugins/openresty/waf/rules/defaultUaBlack.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "name": "uaBlock1", - "rule": "HTTrack|Apache-HttpClient|harvest|audit|dirbuster|pangolin|nmap|sqln|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|zmeu|BabyKrokodil|netsparker|httperf| SF/", - "type": "scannerFilter" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/defaultUrlBlack.json b/plugins/openresty/waf/rules/defaultUrlBlack.json deleted file mode 100644 index 4e189066f..000000000 --- a/plugins/openresty/waf/rules/defaultUrlBlack.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "rule": "\\.(htaccess|mysql_history|bash_history|DS_Store|idea|user\\.ini)", - "name": "dirFilter1", - "type": "dirFilter" - }, - { - "state": "on", - "rule": "\\.(bak|inc|old|mdb|sql|backup|java|class)$", - "name": "dirFilter2", - "type": "dirFilter" - }, - { - "state": "on", - "rule": "^/(vhost|bbs|host|wwwroot|www|site|root|backup|data|ftp|db|admin|website|web).*\\.(rar|sql|zip|tar\\.gz|tar)$", - "name": "dirFilter3", - "type": "dirFilter" - }, - { - "state": "on", - "rule": "java\\.lang", - "name": "dirFilter4", - "type": "dirFilter" - }, - { - "state": "on", - "rule": "/(hack|shell|spy|phpspy)\\.php$", - "name": "phpExec1", - "type": "phpExec" - }, - { - "state": "on", - "rule": "/(attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|inc|forumdata|upload|includes|cache|avatar)/(\\\\w+).(php|jsp)", - "name": "phpExec2", - "type": "phpExec" - }, - { - "state": "on", - "rule": "(?:phpMyAdmin2|phpMyAdmin|phpmyadmin|dbadmin|pma|myadmin|admin|mysql)/scripts/setup%.php", - "name": "phpExec3", - "type": "phpExec" - }, - { - "state": "on", - "rule": "(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(", - "name": "oneWordTrojan1", - "type": "oneWordTrojan" - }, - { - "state": "on", - "rule": "(phpmyadmin|jmx-console|jmxinvokerservlet)", - "name": "appFilter1", - "type": "appFilter" - }, - { - "state": "on", - "rule": "wp-includes/wlwmanifest.xml", - "name": "appFilter2", - "type": "appFilter" - }, - { - "state": "on", - "rule": "die(@md5(HelloThinkCMF))", - "name": "appFilter3", - "type": "appFilter" - }, - { - "state": "on", - "rule": "/boaform/admin/formLogin", - "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))", - "name": "sqlInject1", - "type": "sqlInject" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/fileExt.json b/plugins/openresty/waf/rules/fileExt.json deleted file mode 100644 index c0cc78781..000000000 --- a/plugins/openresty/waf/rules/fileExt.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "rule": "php", - "name": "php", - "type": "fileExt" - }, - { - "state": "on", - "rule": "jsp", - "name": "jsp", - "type": "fileExt" - }, - { - "state": "on", - "rule": "asp", - "name": "asp", - "type": "fileExt" - }, - { - "state": "on", - "rule": "exe", - "name": "exe", - "type": "fileExt" - }, - { - "state": "on", - "rule": "sh", - "name": "sh", - "type": "fileExt" - } - ] -} diff --git a/plugins/openresty/waf/rules/geoRestrict.json b/plugins/openresty/waf/rules/geoRestrict.json deleted file mode 100644 index 7769a81db..000000000 --- a/plugins/openresty/waf/rules/geoRestrict.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "name": "appFilter1", - "rule": "/TomcatBypass/Command/Base64", - "type": "appFilter" - }, - { - "state": "on", - "name": "appFilter2", - "rule": "j\\S*ndi\\S*:\\S*(?:dap|dns)\\S+", - "type": "appFilter" - }, - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/header.json b/plugins/openresty/waf/rules/header.json deleted file mode 100644 index 1c1511c3c..000000000 --- a/plugins/openresty/waf/rules/header.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "name": "appFilter1", - "rule": "/TomcatBypass/Command/Base64", - "type": "appFilter" - }, - { - "state": "on", - "name": "appFilter2", - "rule": "j\\S*ndi\\S*:\\S*(?:dap|dns)\\S+", - "type": "appFilter" - }, - { - "state": "on", - "name": "scannerFilter1", - "rule": "(/acunetix-wvs-test-for-some-inexistent-file|netsparker|acunetix_wvs_security_test|AppScan|XSS@HERE)", - "type": "scannerFilter" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/ipBlack.json b/plugins/openresty/waf/rules/ipBlack.json deleted file mode 100644 index e9fc917d4..000000000 --- a/plugins/openresty/waf/rules/ipBlack.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "rules": [ - { - "name": "拦截IP", - "state": "on", - "type": "ipGroup", - "ipGroup": "test", - "description": "拦截IP" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/ipWhite.json b/plugins/openresty/waf/rules/ipWhite.json deleted file mode 100644 index b7fca6ded..000000000 --- a/plugins/openresty/waf/rules/ipWhite.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rules": [ - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/ip_group/test b/plugins/openresty/waf/rules/ip_group/test deleted file mode 100644 index 3fc5c1775..000000000 --- a/plugins/openresty/waf/rules/ip_group/test +++ /dev/null @@ -1 +0,0 @@ -192.168.1.1 \ No newline at end of file diff --git a/plugins/openresty/waf/rules/methodWhite.json b/plugins/openresty/waf/rules/methodWhite.json deleted file mode 100644 index 93fcad090..000000000 --- a/plugins/openresty/waf/rules/methodWhite.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "rule": "GET", - "name": "GET", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "POST", - "name": "POST", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "PUT", - "name": "PUT", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "DELETE", - "name": "DELETE", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "PATCH", - "name": "PATCH", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "HEAD", - "name": "HEAD", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "OPTIONS", - "name": "OPTIONS", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "TRACE", - "name": "TRACE", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "CONNECT", - "name": "CONNECT", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "PROPFIND", - "name": "PROPFIND", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "PROPPATCH", - "name": "PROPPATCH", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "MKCOL", - "name": "MKCOL", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "COPY", - "name": "COPY", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "MOVE", - "name": "MOVE", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "LOCK", - "name": "LOCK", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "UNLOCK", - "name": "UNLOCK", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "LINK", - "name": "LINK", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "UNLINK", - "name": "UNLINK", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "WRAPPED", - "name": "WRAPPED", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "PROPFIND", - "name": "PROPFIND", - "type": "httpMethod" - }, - { - "state": "on", - "rule": "SRARCH", - "name": "SRARCH", - "type": "httpMethod" - } - ] -} diff --git a/plugins/openresty/waf/rules/post.json b/plugins/openresty/waf/rules/post.json deleted file mode 100644 index 2722b88ff..000000000 --- a/plugins/openresty/waf/rules/post.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "rules": [ - { - "state": "on", - "name": "sqlInject1", - "rule": "select.+(from|limit)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject2", - "rule": "(?:(union(.*?)select))", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject3", - "rule": "having|rongjitest", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject4", - "rule": "sleep\\((\\s*)(\\d*)(\\s*)\\)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject5", - "rule": "benchmark\\((.*)\\,(.*)\\)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject6", - "rule": "group\\s+by.+\\(", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject7", - "rule": "(?:from\\W+information_schema\\W)", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject8", - "rule": "(?:(?:current_)user|database|schema|connection_id)\\s*\\(", - "type": "sqlInject" - }, - { - "state": "on", - "name": "sqlInject9", - "rule": "into(\\s+)+(?:dump|out)file\\s*", - "type": "sqlInject" - }, - { - "state": "on", - "name": "args1", - "rule": "xwork.MethodAccessor", - "type": "args", - "description": "Struts 恶意参数过滤" - }, - { - "state": "on", - "name": "args2", - "rule": "xwork\\.MethodAccessor", - "type": "args", - "description": "Struts 恶意参数过滤" - }, - { - "state": "on", - "name": "oneWordTrojan1", - "rule": "(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(", - "type": "oneWordTrojan" - }, - { - "state": "on", - "name": "protocolFilter1", - "rule": "(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/", - "type": "protocolFilter", - "description": "协议过滤" - }, - { - "state": "on", - "name": "oneWordTrojan2", - "rule": "\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[", - "type": "oneWordTrojan" - }, - { - "state": "on", - "name": "xss1", - "rule": "\\<(iframe|script|body|img|layer|div|meta|style|base|object|input)", - "type": "xss" - }, - { - "state": "on", - "name": "xss2", - "rule": "(onmouseover|onerror|onload)\\=", - "type": "xss" - }, - { - "state": "on", - "name": "xss3", - "rule": "base64_decode\\(", - "type": "xss" - }, - { - "state": "on", - "name": "dirFilter1", - "rule": "(?:etc\\/\\W*passwd)", - "type": "dirFilter" - }, - { - "state": "on", - "name": "dirFilter2", - "rule": "java\\.lang", - "type": "dirFilter" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/uaBlack.json b/plugins/openresty/waf/rules/uaBlack.json deleted file mode 100644 index 45b403002..000000000 --- a/plugins/openresty/waf/rules/uaBlack.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "rules": [ - { - "name": "UaBlack", - "state": "on", - "action": "deny", - "rule": "ua-blacklist", - "description": "测试" - } - ] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/uaWhite.json b/plugins/openresty/waf/rules/uaWhite.json deleted file mode 100644 index f70da9920..000000000 --- a/plugins/openresty/waf/rules/uaWhite.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rules": [] -} diff --git a/plugins/openresty/waf/rules/urlBlack.json b/plugins/openresty/waf/rules/urlBlack.json deleted file mode 100644 index 7d00d1f4a..000000000 --- a/plugins/openresty/waf/rules/urlBlack.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rules": [] -} \ No newline at end of file diff --git a/plugins/openresty/waf/rules/urlWhite.json b/plugins/openresty/waf/rules/urlWhite.json deleted file mode 100644 index 7d00d1f4a..000000000 --- a/plugins/openresty/waf/rules/urlWhite.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rules": [] -} \ No newline at end of file diff --git a/plugins/openresty/waf/test.lua b/plugins/openresty/waf/test.lua deleted file mode 100644 index 3318b9b17..000000000 --- a/plugins/openresty/waf/test.lua +++ /dev/null @@ -1,33 +0,0 @@ -function ip_to_int(ip) - local ip_int = 0 - for i, octet in ipairs({ ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") }) do - ip_int = ip_int + tonumber(octet) * 256 ^ (4 - i) - end - return ip_int -end - ------- 示例 -local ip_address = "222.249.139.98" -local ip_number = ip_to_int(ip_address) -print(ip_number) - ---local geoip = require "lib.resty.maxminddb" ---local cjson = require("cjson") --- ---geoip.init("/Users/wangzhengkun/Downloads/blackIP.mmdb") --- ---local geo = geoip.lookup("165.154.132.251") --- ---print(cjson.encode(geo)) - ---local fileUtils = require "lib.file" ---local read_file2string = fileUtils.read_file2string --- ---local slideHtml = read_file2string("./html/" .. "slide.html") --- ---print(string.format(slideHtml, "1", "2")) - - ---local today = os.date("%Y-%m-%d") ---print(today) - diff --git a/plugins/openresty/waf/waf.json b/plugins/openresty/waf/waf.json deleted file mode 100644 index ab7668f0c..000000000 --- a/plugins/openresty/waf/waf.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "1Panel WAF", - "version": "1.0.0" -} \ No newline at end of file diff --git a/plugins/openresty/waf/waf.lua b/plugins/openresty/waf/waf.lua deleted file mode 100644 index 1b7075498..000000000 --- a/plugins/openresty/waf/waf.lua +++ /dev/null @@ -1,187 +0,0 @@ -local lib = require "lib" -local file_utils = require "file" -local config = require "config" -local cc = require "cc" -local utils = require "utils" -local cjson = require "cjson" - -local ipairs = ipairs -local sub_str = string.sub -local find_str = string.find -local split_str = utils.split -local encode = cjson.encode -local read_file2table = file_utils.read_file2table -local tonumber = tonumber -local date = os.date -local format_str = string.format - -local function get_website_key() - local s_name = ngx.var.server_name - local website_key = ngx.shared.waf:get(s_name) - if website_key then - return website_key - end - local websites = read_file2table(config.config_dir .. '/websites.json') - if not websites then - return s_name - end - for _, v in ipairs(websites) - do - for _, domain in ipairs(v['domains']) - do - if s_name == domain then - ngx.shared.waf:set(s_name, v['key'], 3600) - return v['key'] - end - end - end - if s_name == '_' then - s_name = "unknown" - end - return s_name -end - - - -local function init() - local ip = utils.get_real_ip() - ngx.ctx.ip = ip - local ua = utils.get_header("user-agent") - if not ua then - ua = "" - end - - ngx.ctx.ua = ua - ngx.ctx.ip_location = utils.get_ip_location(ip) - ngx.ctx.website_key = get_website_key() - ngx.ctx.method = ngx.req.get_method() - ngx.ctx.content_type = utils.get_header("content-type") - if ngx.ctx.content_type then - ngx.ctx.content_length = tonumber(utils.get_header("content-length")) - end - ngx.ctx.today = date("%Y-%m-%d") -end - -local function return_js(js_type) - ngx.header.content_type = "text/html;charset=utf8" - ngx.header.Cache_Control = "no-cache" - local host = ngx.var.scheme .. "://" .. ngx.var.host - local set_access_url = host .. "/set_access_token" - local secret = config.get_secret() - local key = ngx.md5(ngx.ctx.ip .. ngx.var.server_name .. ngx.ctx.website_key - .. ngx.ctx.ua .. ngx.ctx.today .. secret) - local value = ngx.md5(ngx.time() .. ngx.ctx.ip) - local js = config.get_html_res(js_type) - ngx.say(format_str(js, set_access_url, key, value)) - ngx.status = 200 - ngx.exit(200) -end - -local function return_json(data) - ngx.header.content_type = "application/json;" - ngx.header.Cache_Control = "no-cache" - ngx.status = 200 - ngx.say(data) - ngx.exit(200) -end - -local function waf_api() - local uri = ngx.var.uri - local prefix = sub_str(uri, 1, 15) - if find_str(prefix, "/set_access_token") then - local kvs = split_str(uri, "-") - if kvs[2] and kvs[3] then - cc.set_access_token(kvs[2], kvs[3]) - else - ngx.exit(444) - end - end - if uri == "/slide_check_" .. ngx.md5(ngx.ctx.ip) .. ".js" then - ngx.ctx.is_waf_url = true - return_js("slide_js") - end - - if uri == "/5s_check_" .. ngx.md5(ngx.ctx.ip) .. ".js" then - ngx.ctx.is_waf_url = true - 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 - ngx.ctx.is_waf_url = true - if uri == '/reload_waf_config' then - config.load_config_file() - ngx.exit(200) - end - if uri == '/get_block_ip' then - --TODO 从 redis 获取黑名单 - 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 - - -if config.is_waf_on() then - init() - waf_api() - - if ngx.ctx.website_key == "unknown" then - ngx.exit(403) - return - end - - if lib.is_white_ip() then - return true - end - lib.black_ip() - lib.default_ip_black() - - if lib.is_white_ua() then - return true - end - lib.black_ua() - lib.default_ua_black() - - --lib.cc_url() - lib.cc() - - if lib.is_white_url() then - return true - end - lib.black_url() - lib.default_url_black() - - lib.allow_location_check() - lib.method_check() - lib.acl() - --lib.bot_check() - lib.args_check() - lib.cookie_check() - lib.post_check() - lib.header_check() -end \ No newline at end of file diff --git a/plugins/openresty/waf/worker.lua b/plugins/openresty/waf/worker.lua deleted file mode 100644 index 96688f8e3..000000000 --- a/plugins/openresty/waf/worker.lua +++ /dev/null @@ -1,64 +0,0 @@ -local uuid = require 'resty.uuid' -local utils = require "utils" -local config = require "config" - -uuid.seed() - -local update_req_count = function() - 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 - req_count:set("count_4xx", 0) - local count_5xx_update = req_count:get("count_5xx") or 0 - req_count:set("count_5xx", 0) - local attack_count_update = req_count:get("attack_count") or 0 - req_count:set("attack_count", 0) - - if req_count_update == 0 and count_4xx_update == 0 and count_5xx_update == 0 and attack_count_update == 0 then - return - end - - local today = ngx.today() - local wafdb = utils.get_wafdb(config.waf_db_path) - if not wafdb then - ngx.log(ngx.ERR, "get log db failed") - return - end - - wafdb:execute([[BEGIN TRANSACTION]]) - - local stmt_exist = wafdb:prepare("SELECT COUNT(*) FROM waf_stat WHERE day = ?") - stmt_exist:bind_values(today) - stmt_exist:step() - local count = stmt_exist:get_uvalues() - stmt_exist:finalize() - - local code = 0 - if count > 0 then - local stmt = wafdb:prepare("UPDATE waf_stat SET req_count = req_count + ?, count4xx = count4xx + ?, count5xx = count5xx + ?, attack_count = attack_count + ? WHERE day = ?") - stmt:bind_values(req_count_update, count_4xx_update, count_5xx_update, attack_count_update, today) - code = stmt:step() - stmt:finalize() - else - local stmt = wafdb:prepare("INSERT INTO waf_stat (day, req_count, count4xx, count5xx, attack_count,create_date) VALUES (?, ?, ?, ?, ?,DATETIME('now'))") - stmt:bind_values(today, req_count_update, count_4xx_update, count_5xx_update, attack_count_update) - code = stmt:step() - stmt:finalize() - end - - wafdb:execute([[COMMIT]]) - - --local error_msg = wafdb:errmsg() - --if error_msg then - -- ngx.log(ngx.ERR, "update waf_stat error ", error_msg .. " ") - --end -end - -if 0 == ngx.worker.id() then - local ok, err = ngx.timer.every(2, update_req_count) - if not ok then - ngx.log(ngx.ERR, "failed to create the timer: ", err) - return - end -end \ No newline at end of file