├── admin
├── conf
│ ├── sites.conf
│ ├── waf.conf
│ ├── waf.conf.default
│ ├── admin.conf
│ └── admin.conf.default
├── ssl-certs
│ └── .gitkeep
├── component
│ ├── pear
│ │ ├── css
│ │ │ ├── module
│ │ │ │ ├── label.css
│ │ │ │ ├── topBar.css
│ │ │ │ ├── link.css
│ │ │ │ ├── fullscreen.css
│ │ │ │ ├── tag.css
│ │ │ │ ├── form.css
│ │ │ │ ├── nprogress.css
│ │ │ │ ├── table.css
│ │ │ │ ├── message.css
│ │ │ │ ├── frame.css
│ │ │ │ ├── button.css
│ │ │ │ └── menu.css
│ │ │ └── pear.css
│ │ ├── font
│ │ │ ├── iconfont.ttf
│ │ │ ├── iconfont.woff
│ │ │ └── iconfont.woff2
│ │ ├── module
│ │ │ ├── topBar.js
│ │ │ ├── context.js
│ │ │ ├── count.js
│ │ │ ├── convert.js
│ │ │ ├── popup.js
│ │ │ ├── button.js
│ │ │ ├── fullscreen.js
│ │ │ ├── common.js
│ │ │ ├── frame.js
│ │ │ ├── tag.js
│ │ │ ├── echartsTheme.js
│ │ │ ├── message.js
│ │ │ └── drawer.js
│ │ └── pear.js
│ └── layui
│ │ └── font
│ │ ├── iconfont.eot
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
├── favicon.ico
├── admin
│ ├── images
│ │ └── logo.png
│ ├── data
│ │ ├── user.json
│ │ └── menu.json
│ └── css
│ │ ├── other
│ │ ├── error.css
│ │ └── login.css
│ │ └── loader.css
├── view
│ ├── error
│ │ ├── 500.html
│ │ └── 404.html
│ ├── system
│ │ └── password.html
│ └── ip-blocking.html
├── js
│ ├── action.js
│ ├── severityLevel.js
│ ├── attackType.js
│ └── validator.js
├── lua
│ ├── lib
│ │ └── pager.lua
│ ├── system.lua
│ ├── website.lua
│ ├── ip_group.lua
│ ├── ip_blocking.lua
│ ├── dashboard.lua
│ ├── events.lua
│ └── cc_defense.lua
├── config
│ └── pear.config.yml
├── login.html
└── index.html
├── conf
├── global_rules
│ ├── ipWhiteList
│ ├── sensitiveWords
│ ├── acl.json
│ ├── whiteUrl.json
│ ├── headers.json
│ ├── cc.json
│ ├── fileExt.json
│ ├── sensitive.json
│ ├── httpMethod.json
│ ├── blackUrl.json
│ ├── post.json
│ ├── cookie.json
│ └── args.json
├── website.json
├── certificate.json
├── ipgroup.json
├── system.json
└── global.json
├── images
├── dashboard.png
└── donate_wechat.png
├── init.lua
├── lib
├── logger_factory.lua
├── aes.lua
├── decoder.lua
├── constants.lua
├── time.lua
├── arrays.lua
├── utils.lua
├── mysql_cli.lua
├── ip_utils.lua
├── logger.lua
├── geoip.lua
├── request.lua
├── stringutf8.lua
├── file_utils.lua
└── action.lua
├── header_filter.lua
├── waf.lua
├── html
└── redirect.html
├── body_filter.lua
├── init_worker.lua
└── install.sh
/admin/conf/sites.conf:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/ssl-certs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conf/global_rules/ipWhiteList:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conf/global_rules/sensitiveWords:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/component/pear/css/module/label.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conf/website.json:
--------------------------------------------------------------------------------
1 | {
2 | "nextId": 1,
3 | "rules": []
4 | }
--------------------------------------------------------------------------------
/conf/certificate.json:
--------------------------------------------------------------------------------
1 | {
2 | "nextId": 1,
3 | "rules": []
4 | }
--------------------------------------------------------------------------------
/admin/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/favicon.ico
--------------------------------------------------------------------------------
/conf/ipgroup.json:
--------------------------------------------------------------------------------
1 | {
2 | "nextId": 1,
3 | "moduleName": "ipgroup",
4 | "rules": []
5 | }
--------------------------------------------------------------------------------
/conf/global_rules/acl.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleName": "ACL",
3 | "nextId": 1,
4 | "rules": []
5 | }
--------------------------------------------------------------------------------
/images/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/images/dashboard.png
--------------------------------------------------------------------------------
/images/donate_wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/images/donate_wechat.png
--------------------------------------------------------------------------------
/admin/admin/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/admin/images/logo.png
--------------------------------------------------------------------------------
/conf/global_rules/whiteUrl.json:
--------------------------------------------------------------------------------
1 | {
2 | "nextId": 1,
3 | "moduleName": "URL白名单检测",
4 | "rules": []
5 | }
--------------------------------------------------------------------------------
/admin/component/layui/font/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/component/layui/font/iconfont.eot
--------------------------------------------------------------------------------
/admin/component/layui/font/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/component/layui/font/iconfont.ttf
--------------------------------------------------------------------------------
/admin/component/pear/font/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/component/pear/font/iconfont.ttf
--------------------------------------------------------------------------------
/admin/component/pear/font/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/component/pear/font/iconfont.woff
--------------------------------------------------------------------------------
/admin/component/layui/font/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/component/layui/font/iconfont.woff
--------------------------------------------------------------------------------
/admin/component/layui/font/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/component/layui/font/iconfont.woff2
--------------------------------------------------------------------------------
/admin/component/pear/font/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bukaleyang/zhongkui-waf/HEAD/admin/component/pear/font/iconfont.woff2
--------------------------------------------------------------------------------
/admin/component/pear/css/module/topBar.css:
--------------------------------------------------------------------------------
1 | .layui-fixbar li {
2 | border-radius: 4px;
3 | background-color: #5FB878;
4 | color: white;
5 | }
6 |
--------------------------------------------------------------------------------
/admin/admin/data/user.json:
--------------------------------------------------------------------------------
1 | {
2 | "username": "admin",
3 | "password": "133F21A9ACCE9D8F36A302F9140F3EAD",
4 | "salt": "XRKUfOvTAJhd8Rk8rvDQ"
5 | }
--------------------------------------------------------------------------------
/admin/component/pear/module/topBar.js:
--------------------------------------------------------------------------------
1 | layui.define(["jquery","element","util"],function(e){"use strict";layui.jquery;var i=layui.util;layui.element;e("topBar",new function(){i.fixbar({})})});
--------------------------------------------------------------------------------
/admin/component/pear/module/context.js:
--------------------------------------------------------------------------------
1 | layui.define(["jquery","element"],function(e){"use strict";layui.jquery,layui.element;e("context",new function(){this.put=function(e,t){localStorage.setItem(e,t)},this.get=function(e){return localStorage.getItem(e)}})});
--------------------------------------------------------------------------------
/admin/component/pear/css/module/link.css:
--------------------------------------------------------------------------------
1 | .pear-link{
2 | font-size: 15px!important;
3 | }
4 |
5 | .pear-link.pear-link-primary{
6 | color : #5FB878 ;
7 | }
8 |
9 | .pear-link.pear-link-success{
10 | color : #5FB878 ;
11 | }
12 |
13 | .pear-link .pear-link-warming{
14 |
15 |
16 | }
17 |
18 | .pear-link .pear-link-danger{
19 |
20 | }
--------------------------------------------------------------------------------
/admin/component/pear/module/count.js:
--------------------------------------------------------------------------------
1 | layui.define(["jquery","element"],function(e){"use strict";layui.jquery,layui.element;e("count",new function(){this.up=function(e,t){t=t||{};var n=document.getElementById(e),i=t.time,u=t.num,r=t.regulator,l=u/(i/r),a=0,c=0,o=setInterval(function(){(a+=l)>=u&&(clearInterval(o),a=u);var e=a.toFixed(t.bit?t.bit:0);e!=c&&(c=e,n.innerHTML=c)},30)}})});
--------------------------------------------------------------------------------
/admin/component/pear/module/convert.js:
--------------------------------------------------------------------------------
1 | layui.define(["jquery","element"],function(e){"use strict";layui.jquery,layui.element;e("convert",new function(){this.imageToBase64=function(e){var t=document.createElement("canvas");t.width=e.width,t.height=e.height,t.getContext("2d").drawImage(e,0,0,e.width,e.height);var i=e.src.substring(e.src.lastIndexOf(".")+1).toLowerCase();return t.toDataURL("image/"+i)}})});
--------------------------------------------------------------------------------
/init.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local config = require "config"
5 |
6 | local script_path = debug.getinfo(1, 'S').source:sub(2)
7 | local script_dir = script_path:match("(.*[/\\])")
8 | config.ZHONGKUI_PATH = script_dir:sub(1, -2) or "/usr/local/openresty/zhongkui-waf"
9 | config.CONF_PATH = config.ZHONGKUI_PATH .. "/conf"
10 |
11 | config.load_config_file()
12 |
--------------------------------------------------------------------------------
/admin/component/pear/module/popup.js:
--------------------------------------------------------------------------------
1 | layui.define(["layer","jquery","element"],function(i){"use strict";layui.jquery;var e=layui.layer;layui.element;i("popup",new function(){this.success=function(i){e.msg(i,{icon:1,time:1e3})},this.failure=function(i){e.msg(i,{icon:2,time:1e3})},this.warning=function(i){e.msg(i,{icon:3,time:1e3})},this.success=function(i,n){e.msg(i,{icon:1,time:1e3},n)},this.failure=function(i,n){e.msg(i,{icon:2,time:1e3},n)},this.warming=function(i,n){e.msg(i,{icon:3,time:1e3},n)}})});
--------------------------------------------------------------------------------
/admin/component/pear/css/module/fullscreen.css:
--------------------------------------------------------------------------------
1 | html:-moz-full-screen {
2 | background: grey;
3 | }
4 | html:-webkit-full-screen {
5 | background: grey;
6 | width: 100%;
7 | height: 100%;
8 | }
9 | html:fullscreen{
10 | background: grey;
11 | width: 100% !important;
12 | height: 100% !important;
13 | }
14 |
15 | :not(:root):fullscreen::backdrop{
16 | background:whitesmoke;
17 | }
18 |
19 | .pear-full-screen {
20 | width: 100% !important;
21 | height: 100% !important;
22 | }
--------------------------------------------------------------------------------
/lib/logger_factory.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local logger = require "logger"
5 |
6 | local loggers = {}
7 |
8 | local _M = {}
9 |
10 | function _M.get_logger(logPath, host, rolling)
11 | local host_logger = loggers[host]
12 | if not host_logger then
13 | host_logger = logger:new(logPath, host, rolling)
14 | loggers[host] = host_logger
15 | end
16 | return host_logger
17 | end
18 |
19 | return _M
--------------------------------------------------------------------------------
/conf/global_rules/headers.json:
--------------------------------------------------------------------------------
1 | {
2 | "nextId": 3,
3 | "moduleName": "Headers检测",
4 | "rules": [
5 | {
6 | "id": 1,
7 | "state": "on",
8 | "action": "deny",
9 | "rule": "/TomcatBypass/Command/Base64",
10 | "attackType": "rce",
11 | "severityLevel": "high"
12 | },
13 | {
14 | "id": 2,
15 | "state": "on",
16 | "action": "deny",
17 | "rule": "j\\S*ndi\\S*:\\S*(?:dap|dns)\\S+",
18 | "attackType": "rce",
19 | "severityLevel": "high"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/admin/component/pear/module/button.js:
--------------------------------------------------------------------------------
1 | layui.define(["jquery"],function(t){"use strict";var e=layui.jquery,i=function(t){this.option=t};i.prototype.load=function(t){var o={elem:t.elem,time:!!t.time&&t.time,done:t.done?t.done:function(){}},n=e(o.elem).html();e(o.elem).html(""),e(o.elem).attr("disabled","disabled");var l=e(o.elem);return""==o.time&&0==o.time||setTimeout(function(){e(o.elem).attr("disabled",!1),l.html(n),o.done()},o.time),o.text=n,new i(o)},i.prototype.stop=function(t){e(this.option.elem).attr("disabled",!1),e(this.option.elem).html(this.option.text),t&&t()},t("button",new i)});
--------------------------------------------------------------------------------
/admin/view/error/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |

12 |
13 |
500
14 |
抱歉,服务器出错了
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/admin/view/error/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |

12 |
13 |
404
14 |
抱歉,你访问的页面不存在或仍在开发中
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/admin/component/pear/css/pear.css:
--------------------------------------------------------------------------------
1 | @import url("../../layui/css/layui.css");
2 | @import url("../font/iconfont.css");
3 |
4 | @import url("module/nprogress.css");
5 | @import url("module/message.css");
6 | @import url("module/loading.css");
7 | @import url("module/topBar.css");
8 | @import url("module/layout.css");
9 | @import url("module/button.css");
10 | @import url("module/frame.css");
11 | @import url("module/layer.css");
12 | @import url("module/toast.css");
13 | @import url("module/menu.css");
14 | @import url("module/link.css");
15 | @import url("module/tab.css");
16 | @import url("module/tag.css");
17 | @import url("module/fullscreen.css");
18 | @import url("module/popover.min.css");
--------------------------------------------------------------------------------
/header_filter.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local config = require "config"
5 |
6 | local get_site_config = config.get_site_config
7 | local is_site_option_on = config.is_site_option_on
8 |
9 | if is_site_option_on("waf") and get_site_config("waf").mode == "protection" then
10 | if ngx.status ~= 403 then
11 | if is_site_option_on("sensitiveDataFilter") or (is_site_option_on("bot") and get_site_config("bot").trap.state == "on") then
12 | ngx.header.content_length = nil
13 | end
14 | else
15 | ngx.header.server = "ZhongKui WAF"
16 | end
17 | end
--------------------------------------------------------------------------------
/admin/admin/css/other/error.css:
--------------------------------------------------------------------------------
1 | * {padding: 0;margin: 0;font-size: 0.38rem;}ul {list-style: none;}a {text-decoration: none;-webkit-tap-highlight-color: transparent }.clearfix:after {content: '';width: 0;height: 0;display: block;clear: both;}html {height: 100%;width: 100%;}body {font-size: 0.28rem;height: 100%;width: 100%;display: flex;flex-direction: column;position: relative;background-color: white !important;}.content {position: absolute;top: 50%;transform: translateY(-50%);width: 100%;text-align: center;}.content>img {height: 300px;max-width: 370px;margin-right: 180px;}.content>* {display: inline-block;}.content-r {vertical-align: top;}.content-r>h1 {font-size: 72px;color: #434e59;margin-bottom: 24px;font-weight: 600;}.content-r>p {font-size: 20px;color: rgba(0, 0, 0, .45);margin-bottom: 16px;}button {margin-top: 20px;}
--------------------------------------------------------------------------------
/lib/aes.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local aes = require "resty.aes"
5 | local str = require "resty.string"
6 |
7 | local tonumber = tonumber
8 | local gsub = string.gsub
9 | local char = string.char
10 |
11 |
12 | local _M = {}
13 |
14 | local function from_hex(s)
15 | return (gsub(s, '..', function(cc)
16 | return char(tonumber(cc, 16))
17 | end))
18 | end
19 |
20 | function _M.encrypt(key, msg, salt)
21 | local aes_128_cbc_md5 = aes:new(key, salt)
22 | local encrypted = aes_128_cbc_md5:encrypt(msg)
23 | return str.to_hex(encrypted)
24 | end
25 |
26 | function _M.decrypt(key, msg, salt)
27 | local aes_128_cbc_md5 = aes:new(key, salt)
28 | local encrypted = from_hex(msg)
29 | return aes_128_cbc_md5:decrypt(encrypted)
30 | end
31 |
32 | return _M
33 |
--------------------------------------------------------------------------------
/admin/js/action.js:
--------------------------------------------------------------------------------
1 | let action = {
2 | "deny": "拒绝访问",
3 | "allow": "允许访问",
4 | "redirect": "拒绝访问并返回拦截页面",
5 | "captcha": "人机验证",
6 | "coding": "打码"
7 | }
8 |
9 | function initActionSelect(id, exclude, success) {
10 | var mySelect = document.getElementById(id);
11 |
12 | for (let key in action) {
13 | var option = document.createElement('option');
14 | if (exclude && exclude === key) {
15 | continue;
16 | }
17 | option.text = action[key];
18 | option.value = key;
19 |
20 | mySelect.appendChild(option);
21 | }
22 |
23 | if (success) {
24 | success();
25 | }
26 | }
27 |
28 | function getActionText(actionType) {
29 | actionType = actionType.toLowerCase();
30 | for (let key in action) {
31 | if (actionType === key) {
32 | return action[key];
33 | }
34 | }
35 | return;
36 | }
--------------------------------------------------------------------------------
/lib/decoder.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local _M = {}
5 |
6 | function _M.decode_base64(str)
7 | local str_new = str
8 | for t = 1, 2 do
9 | local temp = ngx.decode_base64(str_new)
10 | if not temp then
11 | break
12 | end
13 | str_new = temp
14 | end
15 | return str_new
16 | end
17 |
18 | function _M.unescape_uri(str)
19 | local str_new = str
20 | for t = 1, 2 do
21 | local temp = ngx.unescape_uri(str_new)
22 | if not temp then
23 | break
24 | end
25 | str_new = temp
26 | end
27 | return str_new
28 | end
29 |
30 | function _M.remove_comment(str)
31 | if str == nil then return nil end
32 | local str_new, n, err = ngx.re.gsub(str, "/\\*[\\s\\S]*\\*/", " ", "ijo")
33 | return str_new
34 | end
35 |
36 |
37 | return _M
38 |
--------------------------------------------------------------------------------
/admin/conf/waf.conf:
--------------------------------------------------------------------------------
1 | lua_shared_dict dict_cclimit 10m;
2 | lua_shared_dict dict_accesstoken 5m;
3 | lua_shared_dict dict_blackip 10m;
4 | lua_shared_dict dict_locks 100k;
5 | lua_shared_dict dict_config 100k;
6 | lua_shared_dict dict_config_rules_hits 100k;
7 | lua_shared_dict dict_req_count 5m;
8 | lua_shared_dict dict_req_count_citys 10m;
9 | lua_shared_dict dict_sql_queue 10m;
10 |
11 | lua_package_path "/usr/local/openresty/zhongkui-waf/?.lua;/usr/local/openresty/zhongkui-waf/lib/?.lua;/usr/local/openresty/zhongkui-waf/admin/lua/?.lua;;";
12 | init_by_lua_file /usr/local/openresty/zhongkui-waf/init.lua;
13 | init_worker_by_lua_file /usr/local/openresty/zhongkui-waf/init_worker.lua;
14 | access_by_lua_file /usr/local/openresty/zhongkui-waf/waf.lua;
15 | body_filter_by_lua_file /usr/local/openresty/zhongkui-waf/body_filter.lua;
16 | header_filter_by_lua_file /usr/local/openresty/zhongkui-waf/header_filter.lua;
17 | log_by_lua_file /usr/local/openresty/zhongkui-waf/log_and_traffic.lua;
--------------------------------------------------------------------------------
/conf/system.json:
--------------------------------------------------------------------------------
1 | {
2 | "attackLog": {
3 | "state": "off",
4 | "logPath": "/usr/local/openresty/nginx/logs/hack/",
5 | "jsonFormat": "off"
6 | },
7 | "geoip": {
8 | "file": "/usr/local/share/GeoIP/GeoLite2-City.mmdb",
9 | "language": "zh-CN"
10 | },
11 | "html": "",
12 | "secret": "2215D605B798A5CCEB6D5C900EE3585B",
13 | "mysql": {
14 | "state": "off",
15 | "host": "127.0.0.1",
16 | "port": "3306",
17 | "user": "",
18 | "password": "",
19 | "database": "zhongkui_waf",
20 | "poolSize": "10",
21 | "timeout": 5000
22 | },
23 | "redis": {
24 | "state": "off",
25 | "host": "127.0.0.1",
26 | "password": "",
27 | "timeouts": "1000,1000,1000",
28 | "poolSize": 10,
29 | "ssl": "off",
30 | "port": 6379
31 | },
32 | "rulesSort": {
33 | "state": "off",
34 | "period": 10
35 | }
36 | }
--------------------------------------------------------------------------------
/admin/conf/waf.conf.default:
--------------------------------------------------------------------------------
1 | lua_shared_dict dict_cclimit 10m;
2 | lua_shared_dict dict_accesstoken 5m;
3 | lua_shared_dict dict_blackip 10m;
4 | lua_shared_dict dict_locks 100k;
5 | lua_shared_dict dict_config 100k;
6 | lua_shared_dict dict_config_rules_hits 100k;
7 | lua_shared_dict dict_req_count 5m;
8 | lua_shared_dict dict_req_count_citys 10m;
9 | lua_shared_dict dict_sql_queue 10m;
10 |
11 | lua_package_path "/usr/local/openresty/zhongkui-waf/?.lua;/usr/local/openresty/zhongkui-waf/lib/?.lua;/usr/local/openresty/zhongkui-waf/admin/lua/?.lua;;";
12 | init_by_lua_file /usr/local/openresty/zhongkui-waf/init.lua;
13 | init_worker_by_lua_file /usr/local/openresty/zhongkui-waf/init_worker.lua;
14 | access_by_lua_file /usr/local/openresty/zhongkui-waf/waf.lua;
15 | body_filter_by_lua_file /usr/local/openresty/zhongkui-waf/body_filter.lua;
16 | header_filter_by_lua_file /usr/local/openresty/zhongkui-waf/header_filter.lua;
17 | log_by_lua_file /usr/local/openresty/zhongkui-waf/log_and_traffic.lua;
--------------------------------------------------------------------------------
/lib/constants.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2024 bukale bukale2022@163.com
3 |
4 | local _M = {}
5 |
6 | _M.KEY_HTTP_4XX = 'http4x'
7 | _M.KEY_HTTP_5XX = 'http5x'
8 | _M.KEY_REQUEST_TIMES = 'request_times'
9 | _M.KEY_ATTACK_TIMES = 'attack_times'
10 | _M.KEY_BLOCK_TIMES_ATTACK = 'block_times_attack'
11 | _M.KEY_BLOCK_TIMES_CAPTCHA = 'block_times_captcha'
12 | _M.KEY_BLOCK_TIMES_CC = 'block_times_cc'
13 | _M.KEY_CAPTCHA_PASS_TIMES = 'captcha_pass_times'
14 | _M.KEY_ATTACK_PREFIX = 'attack_'
15 | _M.KEY_ATTACK_TYPE_PREFIX = 'attack_type_'
16 | _M.KEY_BLOCKED_PREFIX = 'blocked_'
17 | _M.KEY_ATTACK_LOG = 'attack_log'
18 | _M.KEY_IP_BLOCK_LOG = 'ip_block_log'
19 | _M.KEY_BLACKIP_PREFIX = 'black_ip:'
20 | _M.KEY_IP_GROUPS_WHITELIST = 'ipWhiteList'
21 | _M.KEY_IP_GROUPS_BLACKLIST = 'ipBlackList'
22 | _M.KEY_CAPTCHA_PREFIX = 'captcha:'
23 | _M.KEY_CAPTCHA_ACCESSTOKEN_REDIS_PREFIX = 'captcha_accesstoken:'
24 |
25 | return _M
26 |
--------------------------------------------------------------------------------
/conf/global_rules/cc.json:
--------------------------------------------------------------------------------
1 | {
2 | "nextId": 3,
3 | "moduleName": "CC检测",
4 | "rules": [
5 | {
6 | "id": 1,
7 | "attackType": "cc-url",
8 | "rule": "cc-url",
9 | "state": "off",
10 | "severityLevel": "medium",
11 | "countType": "url",
12 | "pattern": "",
13 | "autoIpBlock": "on",
14 | "ipBlockExpireInSeconds": 600,
15 | "action": "captcha",
16 | "duration": 60,
17 | "threshold": 1000,
18 | "description": "统计单个IP访问单个URL次数"
19 | },
20 | {
21 | "id": 2,
22 | "attackType": "cc-ip",
23 | "rule": "cc-ip",
24 | "state": "off",
25 | "severityLevel": "medium",
26 | "countType": "ip",
27 | "pattern": "",
28 | "autoIpBlock": "on",
29 | "ipBlockExpireInSeconds": 600,
30 | "action": "captcha",
31 | "duration": 60,
32 | "threshold": 1000,
33 | "description": "统计单个IP访问次数"
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/admin/js/severityLevel.js:
--------------------------------------------------------------------------------
1 | let severityLevelArray = [
2 | { "type": "low", "name_en": "low", "name_cn": "低危" },
3 | { "type": "medium", "name_en": "medium", "name_cn": "中危" },
4 | { "type": "high", "name_en": "high", "name_cn": "高危" },
5 | { "type": "critical", "name_en": "critical", "name_cn": "严重" }
6 | ]
7 |
8 | function initSeverityLevelSelect(id, success) {
9 | var mySelect = document.getElementById(id);
10 |
11 | for (let i in severityLevelArray) {
12 | let obj = severityLevelArray[i];
13 | var option = document.createElement('option');
14 | option.text = obj.name_cn;
15 | option.value = obj.type;
16 |
17 | mySelect.appendChild(option);
18 | }
19 |
20 | if (success) {
21 | success();
22 | }
23 | }
24 |
25 | function getSeverityLevelText(severityLevel) {
26 | severityLevel = severityLevel.toLowerCase();
27 | for (let i in severityLevelArray) {
28 | let obj = severityLevelArray[i];
29 | if (severityLevel === obj.type) {
30 | return obj.name_cn;
31 | }
32 | }
33 | return severityLevel;
34 | }
--------------------------------------------------------------------------------
/lib/time.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local ngxmatch = ngx.re.match
5 | local sub = string.sub
6 | local tonumber = tonumber
7 |
8 | local _M = {}
9 |
10 | function _M.calculate_seconds_to_next_midnight()
11 | local localtime = ngx.localtime()
12 |
13 | local m, err = ngxmatch(localtime, "(\\d+):(\\d+):(\\d+)", "jo")
14 | if not m then
15 | ngx.log(ngx.ERR, "failed to calculate ttl ", err)
16 | return nil
17 | end
18 |
19 | return 86400 - tonumber(m[1]) * 3600 - tonumber(m[2]) * 60 - tonumber(m[3])
20 | end
21 |
22 | function _M.get_date_hour()
23 | local localtime = ngx.localtime()
24 | local hour = sub(localtime, 1, 13)
25 | return hour
26 | end
27 |
28 | function _M.get_hours()
29 | local hours = {}
30 | local today = ngx.today()
31 | local hour = nil
32 | for i = 0, 23 do
33 | if i < 10 then
34 | hour = today .. ' 0' .. i
35 | else
36 | hour = today .. ' ' .. i
37 | end
38 | hours[i + 1] = hour
39 | end
40 |
41 | return hours
42 | end
43 |
44 | return _M
--------------------------------------------------------------------------------
/admin/component/pear/module/fullscreen.js:
--------------------------------------------------------------------------------
1 | layui.define(["message","table","jquery","element","yaml","form","tab","menu","frame","theme","convert"],function(e){"use strict";var n=layui.jquery.Deferred();e("fullscreen",new function(){this.func=null,this.onFullchange=function(e){this.func=e;for(var n=["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"],l=0;l`;t("#"+e.elem).html(""+n+'
\n\t\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t
')}(i),t("#"+i.elem).width(i.width),t("#"+i.elem).height(i.height),new n(i)},n.prototype.changePage=function(e,n){var a=t("#"+this.option.elem).find(".pear-frame-loading"),r=t("#"+this.option.elem+" iframe");r.attr("src",e),i(r,a,n)},n.prototype.changePageByElement=function(e,n,a,r){var o=t("#"+e).find(".pear-frame-loading"),l=t("#"+e+" iframe");l.attr("src",n),t("#"+e+" .title").html(a),i(l,o,r)},n.prototype.refresh=function(e){var n=t("#"+this.option.elem).find(".pear-frame-loading"),a=t("#"+this.option.elem).find("iframe");a.attr("src",a.attr("src")),i(a,n,e)},e("frame",new n)});
--------------------------------------------------------------------------------
/admin/admin/css/loader.css:
--------------------------------------------------------------------------------
1 | .loader-main{position: fixed;width: 100%;height: 100%;background-color: whitesmoke;z-index: 9999999;}.loader {width: 50px;height: 50px;margin: 30px auto 40px;margin-top: 20%;position: relative;z-index: 999999;background-color: whitesmoke;}.loader:before {content: "";width: 50px;height: 7px;border-radius: 50%;background: #000;opacity: 0.1;position: absolute;top: 59px;left: 0;animation: shadow .5s linear infinite;}.loader:after {content: "";width: 50px;height: 50px;border-radius: 3px;background-color: #5FB878;position: absolute;top: 0;left: 0;animation: loading .5s linear infinite;}@-webkit-keyframes loading {17% {border-bottom-right-radius: 3px;}25% {transform: translateY(9px) rotate(22.5deg);}50% {transform: translateY(18px) scale(1, 0.9) rotate(45deg);border-bottom-right-radius: 40px;}75% {transform: translateY(9px) rotate(67.5deg);}100% {transform: translateY(0) rotate(90deg);}}@keyframes loading {17% {border-bottom-right-radius: 3px;}25% {transform: translateY(9px) rotate(22.5deg);}50% {transform: translateY(18px) scale(1, 0.9) rotate(45deg);border-bottom-right-radius: 40px;}75% {transform: translateY(9px) rotate(67.5deg);}100% {transform: translateY(0) rotate(90deg);}}@-webkit-keyframes shadow {0%, 100% {transform: scale(1, 1);}50% {transform: scale(1.2, 1);}}@keyframes shadow {0%, 100% {transform: scale(1, 1);}50% {transform: scale(1.2, 1);}}
--------------------------------------------------------------------------------
/lib/arrays.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local bit = require "bit"
5 | local nkeys = require "table.nkeys"
6 | local isempty = require "table.isempty"
7 |
8 | local rshift = bit.rshift
9 |
10 | local _M = {}
11 |
12 | local INDEX_OUT_OF_RANGE = "String index out of range: "
13 |
14 | function _M.binary_search(array, from_index, ro_index, item)
15 | if isempty(array) then
16 | return -1
17 | end
18 |
19 | if from_index > ro_index then
20 | error("out of range: " .. from_index .. "," .. ro_index)
21 | end
22 |
23 | if from_index < 1 then
24 | error(INDEX_OUT_OF_RANGE .. from_index)
25 | end
26 |
27 | local array_length = nkeys(array)
28 | if ro_index > array_length then
29 | error(INDEX_OUT_OF_RANGE .. ro_index)
30 | end
31 |
32 | local low = from_index
33 | local high = ro_index
34 |
35 | while low <= high do
36 | local mid = rshift(low + high, 1)
37 | --local mid = math.ceil((low + high) / 2)
38 |
39 | local midval = array[mid]
40 | if midval < item then
41 | low = mid + 1
42 | elseif midval > item then
43 | high = mid - 1
44 | else
45 | return mid
46 | end
47 | end
48 |
49 | return -low
50 | end
51 |
52 | return _M
--------------------------------------------------------------------------------
/waf.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local geoip = require "geoip"
5 | local config = require "config"
6 | local lib = require "lib"
7 | local ip_utils = require "ip_utils"
8 | local request = require "request"
9 | local stringutf8 = require "stringutf8"
10 |
11 | local default_if_blank = stringutf8.default_if_blank
12 | local generate_id = request.generate_id
13 | local is_site_option_on = config.is_site_option_on
14 | local get_client_ip = ip_utils.get_client_ip
15 |
16 | local function init()
17 | local ctx = ngx.ctx
18 |
19 | local ip = get_client_ip()
20 | ctx.ip = ip
21 |
22 | ctx.ua = default_if_blank(ngx.var.http_user_agent, '')
23 |
24 | ctx.geoip = geoip.lookup(ip)
25 |
26 | ctx.request_id = generate_id()
27 |
28 | ctx.server_name = default_if_blank(ngx.var.server_name, 'unknown')
29 | end
30 |
31 | if is_site_option_on("waf") then
32 |
33 | init()
34 |
35 | lib.is_white_ip()
36 |
37 | lib.is_black_ip()
38 |
39 | lib.is_unsafe_http_method()
40 |
41 | lib.is_bot()
42 |
43 | lib.is_acl()
44 |
45 | lib.is_cc()
46 |
47 | lib.is_white_url()
48 |
49 | lib.is_black_url()
50 |
51 | lib.is_evil_args()
52 |
53 | lib.is_evil_headers()
54 |
55 | lib.is_evil_cookies()
56 |
57 | lib.is_evil_request_body()
58 |
59 | end
60 |
--------------------------------------------------------------------------------
/admin/component/pear/pear.js:
--------------------------------------------------------------------------------
1 | window.rootPath = (function (src) {
2 | src = document.currentScript
3 | ? document.currentScript.src
4 | : document.scripts[document.scripts.length - 1].src;
5 | return src.substring(0, src.lastIndexOf("/") + 1);
6 | })();
7 |
8 | layui.config({
9 | base: rootPath + "module/",
10 | version: "3.40.0"
11 | }).extend({
12 | admin: "admin", // 框架布局组件
13 | common: "common", // 公共方法封装
14 | menu: "menu", // 数据菜单组件
15 | frame: "frame", // 内容页面组件
16 | tab: "tab", // 多选项卡组件
17 | echarts: "echarts", // 数据图表组件
18 | echartsTheme: "echartsTheme",// 数据图表主题
19 | drawer: "drawer", // 抽屉弹层组件
20 | tag:"tag", // 多标签页组件
21 | popup:"popup", // 弹层封装
22 | count:"count", // 数字滚动
23 | topBar: "topBar", // 置顶组件
24 | button: "button", // 加载按钮
25 | loading: "loading", // 加载组件
26 | convert:"convert", // 数据转换
27 | yaml:"yaml", // yaml 解析组件
28 | context: "context", // 上下文组件
29 | theme: "theme", // 主题转换
30 | message: "message", // 通知组件
31 | toast: "toast", // 消息通知
32 | fullscreen:"fullscreen" //全屏组件
33 | }).use(['layer', 'theme', 'jquery'], function () {
34 | layui.theme.changeTheme(window, false);
35 | var $ = layui.$;
36 | $.ajaxSetup({
37 | complete(xhr, status) {
38 | if (xhr.status == 401) {
39 | window.parent.location.href = '/login.html';
40 | }
41 | }});
42 | });
--------------------------------------------------------------------------------
/admin/component/pear/css/module/tag.css:
--------------------------------------------------------------------------------
1 | .input-new-tag {
2 | width: 90px;
3 | }
4 |
5 | .input-new-tag input {
6 | height: 100%!important;
7 | border: none;
8 | padding-left: 0px;
9 | }
10 |
11 | .tag .layui-btn .tag-close:hover {
12 | border-radius: 2px;
13 | color: #fff;
14 | }
15 |
16 | .tag .layui-btn .tag-close {
17 | margin-left: 8px;
18 | transition: all .2s;
19 | -webkit-transition: all .2s;
20 | }
21 | .tag-item {
22 | background-color: #5FB878;
23 | color: white;
24 | border: none;
25 | }
26 |
27 | .tag-item:hover {
28 |
29 | color: white;
30 |
31 | }
32 | .tag-item-normal {
33 | background-color: #5FB878;
34 | color: white;
35 | border: none;
36 | }
37 |
38 | .tag-item-warm {
39 | background-color: #f6ad55;
40 | color: white;
41 | border: none;
42 | }
43 |
44 | .tag-item-danger {
45 | background-color: #f56c6c;
46 | color: white;
47 | border: none;
48 | }
49 |
50 | .tag-item-dark {
51 | background-color: #525252;
52 | color: white;
53 | border: none;
54 | }
55 |
56 | .tag-item-primary {
57 | background-color: white !important;
58 | color: dimgray;
59 | border: 1px solid dimgray;
60 | }
61 |
62 | .tag-item-normal:hover {
63 |
64 | color: white !important;
65 | }
66 |
67 | .tag-item-warm:hover {
68 |
69 | color: white;
70 | }
71 |
72 | .tag-item-danger:hover {
73 |
74 | color: white;
75 | }
76 |
77 | .tag-item-dark:hover {
78 |
79 | color: white;
80 | }
81 |
82 | .tag-item-primary:hover {
83 | color: dimgray;
84 | border: 1px solid dimgray;
85 | }
--------------------------------------------------------------------------------
/lib/utils.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2024 bukale bukale2022@163.com
3 |
4 | local timerat = ngx.timer.at
5 | local every = ngx.timer.every
6 |
7 | local _M = {}
8 |
9 | function _M.start_timer(delay, callback, ...)
10 | local ok, err = timerat(delay, callback, ...)
11 | if not ok then
12 | ngx.log(ngx.ERR, "failed to create timer: ", err)
13 | return
14 | end
15 |
16 | return ok, err
17 | end
18 |
19 | function _M.start_timer_every(delay, callback, ...)
20 | local ok, err = every(delay, callback, ...)
21 | if not ok then
22 | ngx.log(ngx.ERR, "failed to create the timer: ", err)
23 | return
24 | end
25 |
26 | return ok, err
27 | end
28 |
29 | function _M.dict_incr(dict, key, ttl)
30 | local newval, err = dict:incr(key, 1)
31 | if not newval then
32 | if ttl then
33 | local t = type(ttl)
34 | if t == 'number' then
35 | dict:set(key, 1, ttl)
36 | elseif t == 'function' then
37 | dict:set(key, 1, ttl())
38 | end
39 | else
40 | dict:set(key, 1)
41 | end
42 |
43 | return 1
44 | end
45 |
46 | return newval, err
47 | end
48 |
49 | function _M.dict_set(dict, key, value, ttl)
50 | return dict:set(key, value, ttl)
51 | end
52 |
53 | function _M.dict_get(dict, key)
54 | return dict:get(key)
55 | end
56 |
57 | return _M
58 |
--------------------------------------------------------------------------------
/admin/admin/css/other/login.css:
--------------------------------------------------------------------------------
1 | .layui-form {width: 320px !important;margin: auto !important;margin-top: 160px !important;}.layui-form button {width: 100% !important;height: 44px !important;line-height: 44px !important;font-size: 16px !important;background-color: #5FB878 !important;font-weight: 550 !important;}.layui-form-checked[lay-skin=primary] i {border-color: #5FB878 !important;background-color: #5FB878 !important;color: #fff !important;}.layui-tab-content {margin-top: 15px !important;padding-left: 0px !important;padding-right: 0px !important;}.layui-form-item {margin-top: 20px !important;}.layui-input:focus {box-shadow: 0px 0px 2px 1px #5FB878 !important;}.layui-form-danger:focus{box-shadow: 0px 0px 2px 1px #f56c6c !important;}.logo {width: 60px !important;border-radius: 12px;margin-top: 10px !important;margin-left: 8px !important;}.title {font-size: 30px !important;font-weight: 550 !important;margin-left: 20px !important;color: #5FB878 !important;display: inline-block !important;height: 60px !important;line-height: 60px !important;margin-top: 10px !important;position: absolute !important;}.desc {width: 100% !important;text-align: center !important;color: gray !important;height: 50px !important;line-height: 50px !important;}body {background-repeat:no-repeat;background-color: whitesmoke;background-size: 100%;height: 100%;}.code {float: left;margin-right: 13px;margin: 0px !important;border: #e6e6e6 1px solid;display: inline-block!important;}.codeImage {float: right;height: 42px;border: #e6e6e6 1px solid;cursor: pointer;}@media (max-width:768px){body{background-position:center;}}
--------------------------------------------------------------------------------
/admin/component/pear/css/module/form.css:
--------------------------------------------------------------------------------
1 | input::-webkit-input-placeholder,
2 | textarea::-webkit-input-placeholder {
3 | color: #ccc;
4 | }
5 |
6 | .layui-input:hover,
7 | .layui-textarea:hover,
8 | .layui-input:focus,
9 | .layui-textarea:focus {
10 | border-color: #eee;
11 | }
12 |
13 | .layui-input:focus,
14 | .layui-textarea:focus {
15 | border-color: #5FB878 !important;
16 | box-shadow: 0 0 0 3px #f0f9eb !important;
17 | }
18 |
19 | .layui-input[success] {
20 | box-shadow: 0px 0px 0px 3px #f0f9eb !important;
21 | border: #5FB878 1px solid!important;
22 | }
23 |
24 | .layui-input[failure],
25 | .layui-form-item .layui-form-danger:focus {
26 | box-shadow: 0px 0px 0px 3px #fef0f0 !important;
27 | border: #F56C6C 1px solid!important;
28 | }
29 |
30 | .layui-input,
31 | .layui-select,
32 | .layui-textarea {
33 | border-radius: 4px;
34 | border-color: #eee;
35 | transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
36 | }
37 |
38 | .layui-form-select dl::-webkit-scrollbar {
39 | width: 0px;
40 | height: 0px;
41 | }
42 |
43 | .layui-form-select dl::-webkit-scrollbar {
44 | width: 6px;
45 | height: 6px;
46 | }
47 |
48 | .layui-form-select dl::-webkit-scrollbar-track {
49 | background: white;
50 | border-radius: 3px;
51 | }
52 |
53 | .layui-form-select dl::-webkit-scrollbar-thumb {
54 | background: #E6E6E6;
55 | border-radius: 3px;
56 | }
57 |
58 | .layui-form-select dl::-webkit-scrollbar-thumb:hover {
59 | background: #E6E6E6;
60 | }
61 |
62 | .layui-form-select dl::-webkit-scrollbar-corner {
63 | background: #f6f6f6;
64 | }
65 |
66 | /* layui 2.6.9 样式变化 */
67 | .layui-form-select dl dd.layui-this{
68 | background-color: #F6F6F6;
69 | font-weight: 700;
70 | }
71 |
--------------------------------------------------------------------------------
/admin/lua/lib/pager.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local tonumber = tonumber
5 |
6 | local _M = {}
7 |
8 | local mt = { __index = _M }
9 |
10 | function _M:new(page, limit)
11 | page = tonumber(page) or 1 -- 第几页
12 | if page < 1 then
13 | page = 1
14 | end
15 |
16 | local t = {
17 | page = tonumber(page) or 1, -- 第几页
18 | limit = tonumber(limit) or 10, -- 每页大小
19 | totalPages = 0, -- 总页数
20 | totalSize = 0 -- 总记录数
21 | }
22 |
23 | setmetatable(t, mt)
24 | return t
25 | end
26 |
27 | local function get_page(page)
28 | page = tonumber(page) or 1
29 | if page < 1 then
30 | page = 1
31 | end
32 | return page
33 | end
34 |
35 | local function get_limit(limit)
36 | limit = tonumber(limit) or 10
37 | if limit < 1 then
38 | limit = 1
39 | end
40 | return limit
41 | end
42 |
43 | -- 获取起始下标,从0开始
44 | function _M.get_begin(page, limit)
45 | page = get_page(page)
46 | limit = get_limit(limit)
47 | return (page - 1) * limit
48 | end
49 |
50 | -- 获取截止下标,从0开始
51 | function _M.get_end(page, limit)
52 | page = get_page(page)
53 | limit = get_limit(limit)
54 | return (page - 1) * limit + limit - 1
55 | end
56 |
57 | -- 获取起始下标,从1开始
58 | function _M.get_lua_begin(page, limit)
59 | page = get_page(page)
60 | limit = get_limit(limit)
61 | return (page - 1) * limit + 1
62 | end
63 |
64 | -- 获取截止下标,从1开始
65 | function _M.get_lua_end(page, limit)
66 | page = get_page(page)
67 | limit = get_limit(limit)
68 | return (page - 1) * limit + limit
69 | end
70 |
71 | return _M
72 |
--------------------------------------------------------------------------------
/html/redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ZhongKui WAF
10 |
47 |
48 |
49 |
50 |
403
51 |
Your request has been blocked by ZhongKui WAF!
52 |
53 |
请求ID: $request_id
54 |
拦截时间: $blocked_time
55 |
客户端IP: $remote_addr
56 |
客户端标识: $user_agent
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/admin/component/pear/css/module/nprogress.css:
--------------------------------------------------------------------------------
1 | /* Make clicks pass-through */
2 | #nprogress {
3 | pointer-events: none;
4 | }
5 |
6 | #nprogress .bar {
7 | background: #29d;
8 |
9 | position: fixed;
10 | z-index: 999999;
11 | top: 0;
12 | left: 0;
13 |
14 | width: 100%;
15 | height: 2px;
16 | }
17 |
18 | /* Fancy blur effect */
19 | #nprogress .peg {
20 | display: block;
21 | position: absolute;
22 | right: 0px;
23 | width: 100px;
24 | height: 100%;
25 | box-shadow: 0 0 10px #29d, 0 0 5px #29d;
26 | opacity: 1.0;
27 |
28 | -webkit-transform: rotate(3deg) translate(0px, -4px);
29 | -ms-transform: rotate(3deg) translate(0px, -4px);
30 | transform: rotate(3deg) translate(0px, -4px);
31 | }
32 |
33 | /* Remove these to get rid of the spinner */
34 | #nprogress .spinner {
35 | display: block;
36 | position: fixed;
37 | z-index: 1031;
38 | top: 15px;
39 | right: 15px;
40 | }
41 |
42 | #nprogress .spinner-icon {
43 | width: 18px;
44 | height: 18px;
45 | box-sizing: border-box;
46 |
47 | border: solid 2px transparent;
48 | border-top-color: #29d;
49 | border-left-color: #29d;
50 | border-radius: 50%;
51 |
52 | -webkit-animation: nprogress-spinner 400ms linear infinite;
53 | animation: nprogress-spinner 400ms linear infinite;
54 | }
55 |
56 | .nprogress-custom-parent {
57 | overflow: hidden;
58 | position: relative;
59 | }
60 |
61 | .nprogress-custom-parent #nprogress .spinner,
62 | .nprogress-custom-parent #nprogress .bar {
63 | position: absolute;
64 | }
65 |
66 | @-webkit-keyframes nprogress-spinner {
67 | 0% { -webkit-transform: rotate(0deg); }
68 | 100% { -webkit-transform: rotate(360deg); }
69 | }
70 | @keyframes nprogress-spinner {
71 | 0% { transform: rotate(0deg); }
72 | 100% { transform: rotate(360deg); }
73 | }
--------------------------------------------------------------------------------
/admin/config/pear.config.yml:
--------------------------------------------------------------------------------
1 | ## 网站配置
2 | logo:
3 | ## 网站名称
4 | title: "ZhongKui WAF"
5 | ## 网站图标
6 | image: "admin/images/logo.png"
7 | ## 菜单配置
8 | menu:
9 | ## 菜单数据来源
10 | data: "admin/data/menu.json"
11 | ## 菜单接口的请求方式 GET / POST
12 | method: "GET"
13 | ## 是否同时只打开一个菜单目录
14 | accordion: true
15 | ## 侧边默认折叠状态
16 | collapse: false
17 | ## 是否开启多系统菜单模式
18 | control: false
19 | ## 顶部菜单宽度 PX
20 | controlWidth: 500
21 | ## 默认选中的菜单项
22 | select: "1"
23 | ## 是否开启异步菜单,false 时 data 属性设置为静态数据,true 时为后端接口
24 | async: true
25 | ## 视图内容配置
26 | tab:
27 | ## 是否开启多选项卡
28 | enable: false
29 | ## 保持视图状态
30 | keepState: true
31 | ## 开启选项卡记忆
32 | session: true
33 | ## 浏览器刷新时是否预加载非激活标签页
34 | preload: true
35 | ## 可打开的数量, false 不限制极值
36 | max: "30"
37 | ## 首页
38 | index:
39 | id: "1" ## 标识 ID , 建议与菜单项中的 ID 一致
40 | href: "view/dashboard.html" ## 页面地址
41 | title: "数据统计" ## 标题
42 | ## 主题配置
43 | theme:
44 | ## 默认主题色,对应 colors 配置中的 ID 标识
45 | defaultColor: "2"
46 | ## 默认的菜单主题 dark-theme 黑 / light-theme 白
47 | defaultMenu: "dark-theme"
48 | ## 默认的顶部主题 dark-theme 黑 / light-theme 白
49 | defaultHeader: "light-theme"
50 | ## 是否允许用户切换主题,false 时关闭自定义主题面板
51 | allowCustom: true
52 | ## 通栏配置
53 | banner: false
54 | ## 主题色配置列表
55 | colors:
56 | - id: "1"
57 | color: "#2d8cf0"
58 | second: "#ecf5ff"
59 | - id: "2"
60 | color: "#36b368"
61 | second: "#f0f9eb"
62 | - id: "3"
63 | color: "#f6ad55"
64 | second: "#fdf6ec"
65 | - id: "4"
66 | color: "#f56c6c"
67 | second: "#fef0f0"
68 | - id: "5"
69 | color: "#3963bc"
70 | second: "#ecf5ff"
71 | ## 其他配置
72 | other:
73 | ## 主页动画时长
74 | keepLoad: "120"
75 | ## 布局顶部主题
76 | autoHead: false
77 | ## 页脚
78 | footer: true
79 | ## 头部配置
80 | header:
81 | ## 站内消息,通过 false 设置关闭
82 | message: false
--------------------------------------------------------------------------------
/conf/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "waf": {
3 | "state": "on",
4 | "mode": "protection"
5 | },
6 | "args": {
7 | "state": "on"
8 | },
9 | "acl": {
10 | "state": "off"
11 | },
12 | "blackIP": {
13 | "state": "off"
14 | },
15 | "blackUrl": {
16 | "state": "on"
17 | },
18 | "bot": {
19 | "state": "off",
20 | "trap": {
21 | "state": "off",
22 | "action": "deny",
23 | "autoIpBlock": "off",
24 | "ipBlockExpireInSeconds": "600",
25 | "uri": "/zhongkuiwaf/honey/trap"
26 | },
27 | "captcha": {
28 | "state": "off",
29 | "action": "captcha",
30 | "verifyInSeconds": 60,
31 | "maxFailTimes": 3,
32 | "autoIpBlock": "off",
33 | "ipBlockExpireInSeconds": 600,
34 | "expireInSeconds": 1800,
35 | "type": "js_challenge"
36 | }
37 | },
38 | "cc": {
39 | "state": "off"
40 | },
41 | "cookie": {
42 | "state": "on"
43 | },
44 | "fileExt": {
45 | "state": "on"
46 | },
47 | "fileContentCheck": {
48 | "state": "on"
49 | },
50 | "geoip": {
51 | "disallowCountrys": [],
52 | "language": "zh-CN"
53 | },
54 | "headers": {
55 | "state": "on"
56 | },
57 | "httpMethod": {
58 | "state": "off"
59 | },
60 | "post": {
61 | "state": "on"
62 | },
63 | "sensitiveDataFilter": {
64 | "state": "off"
65 | },
66 | "sqli": {
67 | "state": "on"
68 | },
69 | "whiteIP": {
70 | "state": "off"
71 | },
72 | "whiteUrl": {
73 | "state": "off"
74 | },
75 | "xss": {
76 | "state": "on"
77 | }
78 | }
--------------------------------------------------------------------------------
/admin/js/attackType.js:
--------------------------------------------------------------------------------
1 | let attackTypeArray = [
2 | {"type":"sqli","name_en":"SQL Injection","name_cn":"SQL注入"},
3 | {"type":"xss","name_en":"XSS","name_cn":"XSS"},
4 | {"type":"acl","name_en":"ACL","name_cn":"ACL(访问控制列表)"},
5 | {"type":"file_ext","name_en":"file ext","name_cn":"上传文件类型黑名单"},
6 | {"type":"blackurl","name_en":"URL Blacklist","name_cn":"URL黑名单"},
7 | {"type":"blackip","name_en":"IP Blacklist","name_cn":"IP黑名单"},
8 | {"type":"unsafe_method","name_en":"unsafe http method","name_cn":"不允许的HTTP方法"},
9 | {"type":"bot","name_en":"Bot","name_cn":"Bot"},
10 | {"type":"bot_trap","name_en":"bot trap","name_cn":"Bot陷阱"},
11 | {"type":"directory_traversal","name_en":"Directory Traversal","name_cn":"目录穿越"},
12 | {"type":"commandi","name_en":"Command Injection","name_cn":"命令注入"},
13 | {"type":"rce","name_en":"Remote Code Exec","name_cn":"代码执行"},
14 | {"type":"codei","name_en":"Code Injection","name_cn":"代码注入"},
15 | {"type":"backdoor","name_en":"backdoor","name_cn":"后门"},
16 | {"type":"data_leak","name_en":"Data Leak","name_cn":"信息泄露"},
17 | {"type":"read_file","name_en":"Read File","name_cn":"文件读取"},
18 | {"type":"unknown","name_en":"unknown","name_cn":"未知"}
19 | ]
20 |
21 | function initAttackTypeSelect(id, success) {
22 | var mySelect = document.getElementById(id);
23 |
24 | for (let i in attackTypeArray) {
25 | let obj = attackTypeArray[i];
26 | var option = document.createElement('option');
27 | option.text = obj.name_cn;
28 | option.value = obj.type;
29 |
30 | mySelect.appendChild(option);
31 | }
32 |
33 | if (success) {
34 | success();
35 | }
36 | }
37 |
38 | function getAttackTypeText(attackType) {
39 | attackType = attackType.toLowerCase();
40 | for (let i in attackTypeArray) {
41 | let obj = attackTypeArray[i];
42 | if (attackType === obj.type) {
43 | return obj.name_cn;
44 | }
45 | }
46 | return attackType;
47 | }
--------------------------------------------------------------------------------
/lib/mysql_cli.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local mysql = require "resty.mysql"
5 | local config = require "config"
6 |
7 | local _M = {}
8 |
9 | local mysql_config = config.get_system_config("mysql")
10 | local host = mysql_config.host
11 | local port = mysql_config.port
12 | local user = mysql_config.user
13 | local password = mysql_config.password
14 | local database = mysql_config.database
15 | local poolSize = mysql_config.poolSize
16 | local timeout = mysql_config.timeout or 1000
17 |
18 | function _M.get_connection()
19 | local db, err = mysql:new()
20 | if not db then
21 | ngx.log(ngx.ERR, "failed to instantiate mysql: ", err)
22 | return nil, err
23 | end
24 |
25 | db:set_timeout(timeout)
26 |
27 | local ok, err, errcode, sql_state = db:connect{
28 | host = host,
29 | port = port or 3306,
30 | database = database,
31 | user = user,
32 | password = password,
33 | charset = "utf8mb4",
34 | max_packet_size = 1024 * 1024,
35 | pool_size = poolSize or 10
36 | }
37 |
38 | if not ok then
39 | ngx.log(ngx.ERR, "failed to connect: ", err, ": ", errcode, " ", sql_state)
40 | return nil, err
41 | end
42 |
43 | return db, err
44 | end
45 |
46 | function _M.query(sql, rows)
47 | local res, err, errcode, sql_state
48 | local db = _M.get_connection()
49 | if db then
50 | res, err, errcode, sql_state = db:query(sql, rows)
51 | if not res then
52 | ngx.log(ngx.ERR, "bad result: ", err, ": ", errcode, ": ", sql_state, ".")
53 | return
54 | end
55 |
56 | _M.close_connection(db)
57 | end
58 |
59 | return res
60 | end
61 |
62 |
63 | function _M.close_connection(db)
64 | -- put it into the connection pool of size 100,
65 | -- with 10 seconds max idle timeout
66 | local ok, err = db:set_keepalive(10000, poolSize or 10)
67 | if not ok then
68 | ngx.log(ngx.ERR, "failed to set keepalive: ", err)
69 | end
70 |
71 | return ok, err
72 | end
73 |
74 |
75 | return _M
76 |
--------------------------------------------------------------------------------
/conf/global_rules/httpMethod.json:
--------------------------------------------------------------------------------
1 | {
2 | "nextId": 10,
3 | "moduleName": "HTTP方法检测",
4 | "rules": [
5 | {
6 | "id": 1,
7 | "state": "off",
8 | "action": "DENY",
9 | "rule": "GET",
10 | "attackType": "unsafe_method",
11 | "severityLevel": "high"
12 | },
13 | {
14 | "id": 2,
15 | "state": "off",
16 | "action": "DENY",
17 | "rule": "POST",
18 | "attackType": "unsafe_method",
19 | "severityLevel": "high"
20 | },
21 | {
22 | "id": 3,
23 | "state": "off",
24 | "action": "DENY",
25 | "rule": "HEAD",
26 | "attackType": "unsafe_method",
27 | "severityLevel": "high"
28 | },
29 | {
30 | "id": 4,
31 | "state": "off",
32 | "action": "DENY",
33 | "rule": "PUT",
34 | "attackType": "unsafe_method",
35 | "severityLevel": "high"
36 | },
37 | {
38 | "id": 5,
39 | "state": "off",
40 | "action": "DENY",
41 | "rule": "DELETE",
42 | "attackType": "unsafe_method",
43 | "severityLevel": "high"
44 | },
45 | {
46 | "id": 6,
47 | "state": "off",
48 | "action": "DENY",
49 | "rule": "CONNECT",
50 | "attackType": "unsafe_method",
51 | "severityLevel": "high"
52 | },
53 | {
54 | "id": 7,
55 | "state": "off",
56 | "action": "DENY",
57 | "rule": "OPTIONS",
58 | "attackType": "unsafe_method",
59 | "severityLevel": "high"
60 | },
61 | {
62 | "id": 8,
63 | "state": "off",
64 | "action": "DENY",
65 | "rule": "TRACE",
66 | "attackType": "unsafe_method",
67 | "severityLevel": "high"
68 | },
69 | {
70 | "id": 9,
71 | "state": "off",
72 | "action": "DENY",
73 | "rule": "PATCH",
74 | "attackType": "unsafe_method",
75 | "severityLevel": "high"
76 | }
77 | ]
78 | }
--------------------------------------------------------------------------------
/lib/ip_utils.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local _M = {}
5 |
6 | local stringutf8 = require "stringutf8"
7 |
8 | local pairs = pairs
9 | local ipairs = ipairs
10 | local tonumber = tonumber
11 | local find = string.find
12 | local sub = string.sub
13 | local trim = stringutf8.trim
14 | local ngxmatch = ngx.re.match
15 |
16 | local insert = table.insert
17 |
18 | function _M.get_client_ip()
19 | local var = ngx.var
20 | local ips = {
21 | var.http_x_forwarded_for,
22 | var.http_proxy_client_ip,
23 | var.http_wl_proxy_client_ip,
24 | var.http_http_client_ip,
25 | var.http_http_x_forwarded_for,
26 | var.remote_addr
27 | }
28 |
29 | for _, ip in pairs(ips) do
30 | if ip and #ip > 0 then
31 | local idx = find(ip, ",")
32 | if idx and idx > 0 then
33 | ip = sub(ip, 1, idx - 1)
34 | end
35 |
36 | return trim(ip)
37 | end
38 | end
39 |
40 | return "unknown"
41 | end
42 |
43 | -- 是否内网IP
44 | function _M.is_private_ip(ip)
45 | if not ip then
46 | return false
47 | end
48 |
49 | if ip == '127.0.0.1' then
50 | return true
51 | end
52 |
53 | local m, err = ngxmatch(ip, '(\\d{1,3})\\.(\\d{1,3})\\.(?:\\d{1,3})\\.(?:\\d{1,3})', 'isjo')
54 | if m then
55 | local a, b = tonumber(m[1]), tonumber(m[2])
56 | if a == 10 then
57 | return true
58 | elseif a == 172 and b >= 16 and b <= 31 then
59 | return true
60 | elseif a == 192 and b == 168 then
61 | return true
62 | end
63 | else
64 | if err then
65 | ngx.log(ngx.ERR, "error: ", err)
66 | return
67 | end
68 | end
69 |
70 | return false
71 | end
72 |
73 | -- 把配置中混合在一起的单ip和ip网段区分开,{ip网段table},{ips}
74 | function _M.filter_ip_list(ips)
75 | local t1, t2 = {}, {}
76 |
77 | if ips and #ips > 0 then
78 | for _, v in ipairs(ips) do
79 | if find(v, '/') then
80 | insert(t1, v)
81 | else
82 | insert(t2, v)
83 | end
84 | end
85 | end
86 |
87 | return t1, t2
88 | end
89 |
90 | return _M
91 |
--------------------------------------------------------------------------------
/admin/component/pear/module/tag.js:
--------------------------------------------------------------------------------
1 | layui.define("jquery",function(t){"use strict";var e=layui.$,n="layui-btn layui-btn-primary layui-btn-sm";(i=function(){this.config={skin:n,tagText:"+ New Tag"},this.configs={}}).prototype.set=function(t){return e.extend(!0,this.config,t),i.render(),this},i.prototype.on=function(t,e){return layui.onevent.call(this,"tag",t,e)},i.prototype.add=function(t,n){var i=e(".tag[lay-filter="+t+"]");return a.add(null,i,n),a.tagAuto(t),this},i.prototype.delete=function(t,n){var i=e(".tag[lay-filter="+t+"]").find('>.tag-item[lay-id="'+n+'"]');return a.delete(null,i),this};var a={tagClick:function(t,n,a,i){i=i||{};var l=a||e(this),o=(n=n||l.index(l),l.parents(".tag").eq(0)),r=o.attr("lay-filter");layui.event.call(this,"tag","click("+r+")",{elem:o,index:n})},add:function(t,e,n){var a=e.attr("lay-filter"),i=e.children(".button-new-tag"),l=i.index(),o='";!1!==layui.event.call(this,"tag","add("+a+")",{elem:e,index:l,othis:o})&&(i[0]?i.before(o):e.append(o))},input:function(t,n){var l=n||e(this),o=l.parents(".tag").eq(0),r=o.attr("lay-filter"),u=i.configs[r]=e.extend({},i.config,i.configs[r]||{},u),s=e('');s.addClass(u.skin),l.after(s).remove(),s.children(".layui-input").on("blur",function(){if(this.value){var t={text:this.value};a.add(null,o,t)}s.remove(),a.tagAuto(r)}).focus()},delete:function(t,n){var a=n||e(this).parent(),i=a.index(),l=a.parents(".tag").eq(0),o=l.attr("lay-filter");!1!==layui.event.call(this,"tag","delete("+o+")",{elem:l,index:i})&&a.remove()},tagAuto:function(t){var l=(t=t||"")&&i.configs[t]||i.config;e(".tag"+(t?'[lay-filter="'+t+'"]':"")).each(function(){var t=e(this),i=t.children(".tag-item"),o=t.children(".button-new-tag");i.removeClass(n).addClass(l.skin),t.attr("lay-allowClose")&&i.length&&i.each(function(){var t=e(this);if(!t.find(".tag-close")[0]){var n=e('ဆ');n.on("click",a.delete),t.append(n)}}),t.attr("lay-newTag")&&0===o.length&&((o=e('')).on("click",a.input),t.append(o)),o.html(l.tagText),o.removeClass(n).addClass(l.skin)})}};i.prototype.init=function(t,n){return t&&(i.configs[t]=e.extend({},i.config,i.configs[t]||{},n)),a.tagAuto.call(this,t)},i.prototype.render=i.prototype.init;var i=new i,l=e(document);i.render(),l.on("click",".tag-item",a.tagClick),t("tag",i)});
--------------------------------------------------------------------------------
/body_filter.lua:
--------------------------------------------------------------------------------
1 | -- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
2 | -- Copyright (c) 2023 bukale bukale2022@163.com
3 |
4 | local sensitive = require "sensitive_data_filter"
5 | local config = require "config"
6 | local stringutf8 = require "stringutf8"
7 |
8 | local ngxfind = ngx.re.find
9 | local gsub = string.gsub
10 | local format = string.format
11 | local default_if_blank = stringutf8.default_if_blank
12 |
13 | local get_site_config = config.get_site_config
14 | local is_site_option_on = config.is_site_option_on
15 |
16 | local CONTENT_TYPE_REGEX = "^(?:text/html|text/plain|text/xml|application/json|application/xml|application/xhtml\\+xml)"
17 | local HTML_CONTENT_TYPE_REGEX = "^(?:text/html|application/xhtml\\+xml)"
18 | local TRAP_HTML = 'come-here