1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-13 17:24:44 +08:00

feat: 删除 plugins 目录 (#4311)

This commit is contained in:
zhengkunwang 2024-03-26 14:00:07 +08:00 committed by GitHub
parent b4e8c8503c
commit aa93a6d373
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 0 additions and 7299 deletions

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -1,21 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>5s</title>
<style>
#loadingContainer { position: absolute; top: 50%%; left: 50%%; transform: translate(-50%%, -50%%); display: flex; align-items: center; justify-content: center; flex-direction: column; background: #e8e8e8; width: 300px; height: 100px; border: 2px solid #e8e8e8; }
#loadingText { font-size: 18px; margin-top: 10px; }
#loadingSuccess { display: none; font-size: 24px; color: #7ac23c; margin-top: 10px; }
.loadingSpinner { border: 4px solid rgba(0, 0, 0, 0.1); border-top: 4px solid #7ac23c; border-radius: 50%%; width: 20px; height: 20px; animation: spin 1s linear infinite; margin-top: 10px; }
@keyframes spin { 0%% { transform: rotate(0deg); } 100%% { transform: rotate(360deg); } }
</style>
</head>
<body>
<div id="loadingContainer">
<div id="loadingText">正在验证...</div>
<div id="loadingSuccess">验证成功</div>
<div class="loadingSpinner"></div>
</div>
<script type="text/javascript" src="/5s_check_%s.js"></script>
</body>
</html>

View File

@ -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();
}
}

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>访问被拒绝</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: '微软雅黑', sans-serif; background-color: #282c34; color: #fff; text-align: center; padding: 50px; }
.main { max-width: 600px; margin: 10% auto; background-color: #3a3a3a; border-radius: 8px; padding: 20px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); }
.title { background: #ff4d4d; color: #fff; font-size: 24px; height: 60px; line-height: 60px; border-radius: 8px 8px 0 0; }
.content { background-color: #444; border: 1px solid #666; border-radius: 0 0 8px 8px; padding: 20px; margin-top: -1px; }
.t1 { color: #ff9999; font-weight: bold; margin: 0 0 20px; padding-bottom: 18px; }
ol { margin: 0; padding: 0; list-style: none; }
ol li { line-height: 30px; background-color: #555; border-radius: 5px; margin-bottom: 10px; padding: 10px; }
.footer { margin-top: 20px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="main">
<div class="title">无法访问</div>
<div class="content">
<p class="t1">很抱歉,您的 IP 已被禁止访问</p>
<ol>
<li>如被误封,请联系网站管理员解封</li>
</ol>
</div>
<div class="footer">此防护来自 1Panel</div>
</div>
</body>
</html>

View File

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>网站防火墙</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: '微软雅黑', sans-serif; background-color: #282c34; color: #fff; text-align: center; padding: 50px; }
.main { max-width: 600px; margin: 10% auto; background-color: #3a3a3a; border-radius: 8px; padding: 20px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); }
.title { background: #ff4d4d; color: #fff; font-size: 24px; height: 60px; line-height: 60px; border-radius: 8px 8px 0 0; }
.content { background-color: #444; border: 1px solid #666; border-radius: 0 0 8px 8px; padding: 20px; margin-top: -1px; }
.t1 { color: #ff9999; font-weight: bold; margin: 0 0 20px; }
.footer { margin-top: 10px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="main">
<div class="title">网站防火墙</div>
<div class="content">
<p class="t1">您的请求不合法,已被拒绝</p>
</div>
<div class="footer">此网站防护来自 1Panel</div>
</div>
</body>
</html>

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>滑动认证</title>
<style>
#dragContainer {position:absolute;top:50%%;left:50%%;transform:translate(-50%%, -50%%);display:inline-block;background:#e8e8e8;width:300px;height:33px;border:2px solid #e8e8e8;}
#dragBg {position:absolute;background-color:#7ac23c;height:100%%;}
#dragText {position:absolute;width:100%%;height:100%%;text-align:center;line-height:33px;user-select:none;-webkit-user-select:none;}
#dragHandler {position:absolute;width:40px;height:100%%;cursor:pointer;box-sizing:border-box;overflow:hidden;}
#dragHandler.dragHandlerBg {background-color:#c0c0c0;}
#dragHandler.dragHandlerBg::before {content:'»';font-size:24px;position:absolute;top:50%%;left:50%%;transform:translate(-50%%, -50%%);color:#7ac23c;}
.dragHandlerOkBg {position:absolute;border-radius:50%%;background-color:#7ac23c;display:flex;justify-content:center;align-items:center;}
.dragHandlerOkBg::before {content:'\2713';font-size:16px;color:white;}
</style>
</head>
<body>
<div>
<div id="dragContainer">
<div id="dragBg"></div>
<div id="dragText"></div>
<div id="dragHandler" class="dragHandlerBg"></div>
</div>
</div>
<script type="text/javascript" src="/slide_check_%s.js"></script>
</body>
</html>

View File

@ -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()}})()};

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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的请求"
}
]
}

View File

@ -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"
}
]
}

View File

@ -1,3 +0,0 @@
{
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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": "<php>die(@md5(HelloThinkCMF))</php>",
"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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
},
]
}

View File

@ -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"
}
]
}

View File

@ -1,11 +0,0 @@
{
"rules": [
{
"name": "拦截IP",
"state": "on",
"type": "ipGroup",
"ipGroup": "test",
"description": "拦截IP"
}
]
}

View File

@ -1,4 +0,0 @@
{
"rules": [
]
}

View File

@ -1 +0,0 @@
192.168.1.1

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -1,11 +0,0 @@
{
"rules": [
{
"name": "UaBlack",
"state": "on",
"action": "deny",
"rule": "ua-blacklist",
"description": "测试"
}
]
}

View File

@ -1,3 +0,0 @@
{
"rules": []
}

View File

@ -1,3 +0,0 @@
{
"rules": []
}

View File

@ -1,3 +0,0 @@
{
"rules": []
}

View File

@ -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)

View File

@ -1,4 +0,0 @@
{
"name": "1Panel WAF",
"version": "1.0.0"
}

View File

@ -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

View File

@ -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