diff --git a/plugins/op_waf/index.py b/plugins/op_waf/index.py index ea2b4d191..15b83859a 100755 --- a/plugins/op_waf/index.py +++ b/plugins/op_waf/index.py @@ -222,6 +222,10 @@ def initDreplace(): if not os.path.exists(logs_path): mw.execShell('mkdir -p ' + logs_path) + debug_log = path + '/debug.log' + if not os.path.exists(debug_log): + mw.execShell('echo "" > ' + debug_log) + config = path + '/waf/config.json' content = mw.readFile(config) content = json.loads(content) @@ -235,6 +239,11 @@ def initDreplace(): content = contentReplace(content) mw.writeFile(config, content) + config_common = path + "/waf/lua/common.lua" + content = mw.readFile(config_common) + content = contentReplace(content) + mw.writeFile(config_common, content) + waf_conf = mw.getServerDir() + "/openresty/nginx/conf/luawaf.conf" waf_tpl = getPluginDir() + "/conf/luawaf.conf" content = mw.readFile(waf_tpl) diff --git a/plugins/op_waf/install.sh b/plugins/op_waf/install.sh index 481fbd07c..35a36768a 100755 --- a/plugins/op_waf/install.sh +++ b/plugins/op_waf/install.sh @@ -19,12 +19,12 @@ Install_of(){ echo '0.1' > $serverPath/op_waf/version.pl echo 'install ok' > $install_tmp - cd ${rootPath} && python3 plugins/op_waf/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py start } Uninstall_of(){ - cd ${rootPath} && python3 plugins/op_waf/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py stop rm -rf $serverPath/op_waf } diff --git a/plugins/op_waf/js/op_waf.js b/plugins/op_waf/js/op_waf.js index fff5729d9..150fda923 100755 --- a/plugins/op_waf/js/op_waf.js +++ b/plugins/op_waf/js/op_waf.js @@ -28,7 +28,7 @@ function setRequestCode(ruleName, statusCode){ title: "设置响应代码【" + ruleName + "】", area: '300px', shift: 5, - closeBtn: 2, + closeBtn: 1, shadeClose: true, content: '
\
\ @@ -144,7 +144,7 @@ function setCcRule(cycle, limit, endtime, siteName, increase){ type: 1, title: "设置CC规则", area: '540px', - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\
\ @@ -184,7 +184,7 @@ function setRetry(retry_cycle, retry, retry_time, siteName) { type: 1, title: "设置恶意容忍规则", area: '500px', - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '\
\ @@ -339,7 +339,7 @@ function setObjConf(ruleName, type) { type: 1, title: "编辑规则【" + ruleName + "】", area: ['700px', '530px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\
\ @@ -405,7 +405,7 @@ function scanRule() { type: 1, title: "常用扫描器过滤规则", area: '650px', - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '\
\ @@ -571,7 +571,7 @@ function ipWhite(type) { type: 1, title: "管理IP白名单", area: ['500px', '500px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\
\ @@ -714,7 +714,7 @@ function ipBlack(type) { type: 1, title: "管理IP黑名单", area: ['500px', '500px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
IPv4黑名单
IPv6黑名单
\
\ @@ -824,7 +824,7 @@ function wafScreen(){
CC攻击'+rdata.rules.cc+'
\
恶意User-Agent'+rdata.rules.user_agent+'
\
Cookie渗透'+rdata.rules.cookie+'
\ -
恶意扫描0
\ +
恶意扫描'+rdata.rules.scan+'
\
恶意HEAD请求0
\
URI自定义拦截'+rdata.rules.args+'
\
URI保护'+rdata.rules.args+'
\ @@ -975,7 +975,7 @@ function siteWafLog(siteName) { type: 1, title: "日志【" + siteName + "】", area: ['880px', '500px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\
\ @@ -1071,7 +1071,7 @@ function siteLogCon(siteName, day, page) { type: 1, title: time + "详情", area: '600px', - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\ \ @@ -1190,7 +1190,7 @@ function siteRuleAdmin(siteName, ruleName, type) { type: 1, title: "管理网站过滤规则【" + title + "】", area: ['500px', '500px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\
\ @@ -1237,7 +1237,7 @@ function cdnHeader(siteName, type) { type: 1, title: "管理网站【" + siteName + "】CDN-Headers", area: ['500px', '500px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\
\ @@ -1342,7 +1342,7 @@ function setSiteObjConf(siteName, ruleName, type) { type: 1, title: "编辑网站【" + siteName + "】规则【" + ruleName + "】", area: ['700px', '530px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
\
\ @@ -1394,7 +1394,7 @@ function siteWafConfig(siteName, type) { type: 1, title: "网站配置【" + siteName + "】", area: ['700px', '500px'], - closeBtn: 2, + closeBtn: 1, shadeClose: false, content: '
' }); diff --git a/plugins/op_waf/t/index.py b/plugins/op_waf/t/index.py index 65db38e92..59654236b 100644 --- a/plugins/op_waf/t/index.py +++ b/plugins/op_waf/t/index.py @@ -22,7 +22,7 @@ from random import Random TEST_URL = "http://t1.cn/" -def httpGet(url, timeout): +def httpGet(url, timeout=10): import urllib.request try: @@ -34,6 +34,19 @@ def httpGet(url, timeout): return str(e) +def httpGet__UA(url, ua, timeout=10): + import urllib.request + headers = {'user-agent': ua} + try: + req = urllib.request.Request(url, headers=headers) + response = urllib.request.urlopen(req) + result = response.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + def httpPost(url, data, timeout=10): """ 发送POST请求 @@ -74,15 +87,57 @@ def httpPost(url, data, timeout=10): def test_Dir(): + ''' + 目录保存 + ''' url = TEST_URL + '?t=../etc/passwd' print("args test start") - httpGet(url, 10) + url_val = httpGet(url, 10) + # print(url_val) print("args test end") +def test_UA(): + ''' + user-agent 过滤 + ''' + url = TEST_URL + print("user-agent test start") + url_val = httpGet__UA(url, 'ApacheBench') + print(url_val) + print("user-agent test end") + + +def test_POST(): + ''' + user-agent 过滤 + ''' + url = TEST_URL + print("POST test start") + url_val = httpPost(url, {'data': "substr($mmsss,0,1)"}) + # url_val = httpPost(url, {'data': "123123"}) + print(url_val) + print("POST test end") + + +def test_scan(): + ''' + 目录保存 + ''' + url = TEST_URL + '/acunetix_wvs_security_test?t=1' + print("scan test start") + url_val = httpGet(url, 10) + print(url_val) + print("scan test end") + + def test_start(): test_Dir() + test_UA() + test_POST() + test_scan() if __name__ == "__main__": + os.system('cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/op_waf && sh install.sh uninstall 0.1 && sh install.sh install 0.1') test_start() diff --git a/plugins/op_waf/waf/lua/common.lua b/plugins/op_waf/waf/lua/common.lua index f04c1686b..22c6838ef 100644 --- a/plugins/op_waf/waf/lua/common.lua +++ b/plugins/op_waf/waf/lua/common.lua @@ -1,26 +1,87 @@ local setmetatable = setmetatable -local _M = { _VERSION = '0.01' } +local _M = { _VERSION = '0.02' } local mt = { __index = _M } + local json = require "cjson" local ngx_match = ngx.re.find +local debug_mode = false + +local waf_root = "{$WAF_ROOT}" +local cpath = waf_root.."/waf/" +local logdir = waf_root.."/logs/" +local rpath = cpath.."/rule/" + +function _M.new(self) -function _M.new(self, cpath, rpath, logdir) - -- ngx.log(ngx.ERR,"read:"..cpath..",rpath:"..rpath) - local opt = { - cpath = cpath, - rpath = rpath, - logdir = logdir, - config = '', - site_config = '', - params = nil + + local self = { + waf_root = waf_root, + cpath = cpath, + rpath = rpath, + logdir = logdir, + config = '', + site_config = '', + params = nil } - local p = setmetatable(opt, mt) - return p + return setmetatable(self, mt) end +function _M.setDebug(self, mode) + debug_mode = mode +end + + +-- 调试方式 +function _M.D(self, msg) + + if not debug_mode then return true end + + local _msg = '' + if type(msg) == 'table' then + for key, val in pairs(msg) do + _msg = tostring( key)..':'.."\n" + end + elseif type(msg) == 'string' then + _msg = msg + elseif type(msg) == 'nil' then + _msg = 'nil' + else + _msg = msg + end + + + local fp = io.open(waf_root.."/debug.log", "ab") + if fp == nil then + return nil + end + + local localtime = os.date("%Y-%m-%d %H:%M:%S") + if server_name then + fp:write(tostring(_msg) .. "\n") + else + fp:write(localtime..":"..tostring(_msg) .. "\n") + end + + fp:flush() + fp:close() + return true +end + + +local function write_file_clear(filename, body) + fp = io.open(filename,'w') + if fp == nil then + return nil + end + fp:write(body) + fp:flush() + fp:close() + return true +end + function _M.setConfData( self, config, site_config ) self.config = config self.site_config = site_config @@ -94,6 +155,10 @@ function _M.compare_ip(self,ips) end +function _M.to_json(self, msg) + return json.encode(msg) +end + function _M.return_message(self, status, msg) ngx.header.content_type = "application/json;" ngx.status = status @@ -136,15 +201,9 @@ function _M.write_file(self, filename, body) return true end + function _M.write_file_clear(self, filename, body) - fp = io.open(filename,'w') - if fp == nil then - return nil - end - fp:write(body) - fp:flush() - fp:close() - return true + return write_file_clear(filename, body) end @@ -234,11 +293,21 @@ function _M.read_file(self, name) end function _M.read_file_table( self, name ) - return self:select_rule(self:read_file('args')) + return self:select_rule(self:read_file(name)) end +local function timer_at_inc_log(premature) + local total_path = cpath .. 'total.json' + local tbody = ngx.shared.limit:get(total_path) + if not tbody then + return false + end + return write_file_clear(total_path,tbody) +end + function _M.inc_log(self, name, rule) + local server_name = self.params['server_name'] local total_path = self.cpath .. 'total.json' local tbody = ngx.shared.limit:get(total_path) @@ -246,7 +315,10 @@ function _M.inc_log(self, name, rule) tbody = self:read_file_body(total_path) if not tbody then return false end end + local total = json.decode(tbody) + + -- 开始计算 if not total['sites'] then total['sites'] = {} end if not total['sites'][server_name] then total['sites'][server_name] = {} end if not total['sites'][server_name][name] then total['sites'][server_name][name] = 0 end @@ -256,13 +328,15 @@ function _M.inc_log(self, name, rule) total['total'] = total['total'] + 1 total['sites'][server_name][name] = total['sites'][server_name][name] + 1 total['rules'][name] = total['rules'][name] + 1 + local total_log = json.encode(total) if not total_log then return false end + ngx.shared.limit:set(total_path,total_log) - if not ngx.shared.limit:get('mw_waf_timeout') then - self:write_file_clear(total_path,total_log) - ngx.shared.limit:set('mw_waf_timeout',1,5) - end + + -- 异步执行 + ngx.timer.at(1, timer_at_inc_log) + end @@ -294,6 +368,54 @@ end -- end +function _M.is_ngx_match_orgin(self,rule,match, sign) + if ngx_match(ngx.unescape_uri(match), rule,"isjo") then + error_rule = rule .. ' >> ' .. sign .. ':' .. match + return true + end + return false +end + + +function _M.ngx_match_string(self, rules, content,sign) + local t = self:is_ngx_match_orgin(rules, content, sign) + if t then + return true + end + + return false +end + +function _M.is_ngx_match_ua(self, rules, content) + -- ngx.header.content_type = "text/html" + for i,rule in ipairs(rules) + do + -- 开启的规则,才匹配。 + if rule[1] == 1 then + local t = self:is_ngx_match_orgin(rule[2], content, rule[3]) + if t then + return true + end + end + end + return false +end + +function _M.is_ngx_match_post(self, rules, content) + for i,rule in ipairs(rules) + do + -- 开启的规则,才匹配。 + if rule[1] == 1 then + local t = self:is_ngx_match_orgin(rule[2],content, rule[3]) + if t then + return true + end + end + end + return false +end + + function _M.is_ngx_match(self, rules, sbody, rule_name) if rules == nil or sbody == nil then return false end if type(sbody) == "string" then @@ -341,6 +463,7 @@ end function _M.write_log(self, name, rule) + local ip = self.params['ip'] local retry = self.config['retry']['retry'] local retry_time = self.config['retry']['retry_time'] diff --git a/plugins/op_waf/waf/lua/init.lua b/plugins/op_waf/waf/lua/init.lua index 6539aec1f..983a90cef 100644 --- a/plugins/op_waf/waf/lua/init.lua +++ b/plugins/op_waf/waf/lua/init.lua @@ -1,47 +1,27 @@ - -local cpath = "{$WAF_PATH}/" -local rpath = "{$WAF_PATH}/rule/" -local logdir = "{$WAF_ROOT}/logs/" local json = require "cjson" local ngx_match = ngx.re.find -local _C = require "common" -local C = _C:new(cpath, rpath, logdir) +local __C = require "common" +local C = __C:new() + +local waf_root = "{$WAF_ROOT}" -config = C:read_file_body_decode(cpath .. 'config.json') -local site_config = C:read_file_body_decode(cpath .. 'site.json') +config = C:read_file_body_decode(waf_root.."/waf/"..'config.json') +local site_config = C:read_file_body_decode(waf_root.."/waf/"..'site.json') C:setConfData(config, site_config) --- D func -local function D(msg) - local _msg = '' - if type(msg) == 'table' then - for key, val in pairs(msg) do - _msg = key..':'..val.."\n" - end - elseif type(msg) == 'string' then - _msg = msg - elseif type(msg) == 'nil' then - _msg = 'nil' - else - _msg = msg - end - if not debug_mode then return true end - local fp = io.open(cpath..'debug.log', 'ab') - if fp == nil then - return nil - end - local localtime = os.date("%Y-%m-%d %H:%M:%S") - if server_name then - fp:write(tostring(_msg) .. "\n") - else - fp:write(localtime..":"..tostring(_msg) .. "\n") - end - fp:flush() - fp:close() - return true -end + +local get_html = C:read_file_body(config["reqfile_path"] .. '/' .. config["get"]["reqfile"]) +local post_html = C:read_file_body(config["reqfile_path"] .. '/' .. config["post"]["reqfile"]) +local user_agent_html = C:read_file_body(config["reqfile_path"] .. '/' .. config["user-agent"]["reqfile"]) +local args_rules = C:read_file_table('args') +local ip_white_rules = C:read_file('ip_white') +local ip_black_rules = C:read_file('ip_black') +local scan_black_rules = C:read_file('scan_black') +local user_agent_rules = C:read_file('user_agent') +local post_rules = C:read_file('post') + function initParams() local data = {} @@ -53,13 +33,14 @@ function initParams() data['uri_request_args'] = ngx.req.get_uri_args() data['method'] = ngx.req.get_method() data['request_uri'] = ngx.var.request_uri + data['cookie'] = ngx.var.http_cookie return data end local params = initParams() C:setParams(params) - +C:setDebug(true) function get_return_state(rstate,rmsg) result = {} @@ -102,7 +83,7 @@ function save_ip_on(data) end end -function remove_btwaf_drop_ip() +function remove_waf_drop_ip() if not uri_request_args['ip'] or not C:is_ipaddr(uri_request_args['ip']) then return get_return_state(true,'格式错误') end if ngx.shared.btwaf:get(cpath2 .. 'stop_ip') then ret=ngx.shared.btwaf:get(cpath2 .. 'stop_ip') @@ -125,7 +106,7 @@ function remove_btwaf_drop_ip() return get_return_state(true,uri_request_args['ip'] .. '已解封') end -function clean_btwaf_drop_ip() +function clean_waf_drop_ip() if ngx.shared.btwaf:get(cpath2 .. 'stop_ip') then ret2=ngx.shared.btwaf:get(cpath2 .. 'stop_ip') ip_data2=json.decode(ret2) @@ -155,14 +136,6 @@ function min_route() end end -local get_html = C:read_file_body(config["reqfile_path"] .. '/' .. config["get"]["reqfile"]) -local post_html = C:read_file_body(config["reqfile_path"] .. '/' .. config["post"]["reqfile"]) -local user_agent_html = C:read_file_body(config["reqfile_path"] .. '/' .. config["user-agent"]["reqfile"]) -local args_rules = C:read_file_table('args') -local ip_white_rules = C:read_file('ip_white') -local ip_black_rules = C:read_file('ip_black') -local scan_black_rules = C:read_file('scan_black') - function waf_args() if not config['get']['open'] or not C:is_site_config('get') then return false end if C:is_ngx_match(args_rules, params['uri_request_args'],'args') then @@ -208,9 +181,9 @@ end function waf_user_agent() - D("12312") - if not config['user-agent']['open'] or not C:is_site_config('user-agent') then return false end - if C:is_ngx_match(user_agent_rules,params['request_header']['user-agent'],'user_agent') then + -- user_agent 过滤 + if not config['user-agent']['open'] or not C:is_site_config('user-agent') then return false end + if C:is_ngx_match_ua(user_agent_rules,params['request_header']['user-agent']) then C:write_log('user_agent','regular') C:return_html(config['user-agent']['status'],user_agent_html) return true @@ -288,20 +261,25 @@ end function waf_scan_black() + -- 扫描软件禁止 if not config['scan']['open'] or not C:is_site_config('scan') then return false end - if C:is_ngx_match(scan_black_rules['cookie'],params["request_header"]["cookie"],false) then - C:write_log('scan','regular') - ngx.exit(config['scan']['status']) - return true + if not params["cookie"] then + if C:ngx_match_string(scan_black_rules['cookie'], tostring(params["cookie"]),'scan') then + C:write_log('scan','regular') + ngx.exit(config['scan']['status']) + return true + end end - if C:is_ngx_match(scan_black_rules['args'],params["request_uri"],false) then + + if C:ngx_match_string(scan_black_rules['args'], params["request_uri"], 'scan') then C:write_log('scan','regular') ngx.exit(config['scan']['status']) return true end + for key,value in pairs(params["request_header"]) do - if C:is_ngx_match(scan_black_rules['header'], key, false) then + if C:ngx_match_string(scan_black_rules['header'], key, 'scan') then C:write_log('scan','regular') ngx.exit(config['scan']['status']) return true @@ -334,7 +312,19 @@ function waf_post() return false end - if C:is_ngx_match(post_rules,request_args,'post') then + for key, val in pairs(request_args) do + if type(val) == "table" then + if type(val[1]) == "boolean" then + return false + end + data = table.concat(val, ", ") + else + data = val + end + + end + + if C:is_ngx_match_post(post_rules,data) then C:write_log('post','regular') C:return_html(config['post']['status'],post_html) return true @@ -367,10 +357,11 @@ function post_data_chekc() end if not list_list then return false end - aaa=nil + + aaa = nil for k,v in pairs(request_args) do - aaa=v + aaa = v end if not aaa then return false end @@ -382,7 +373,7 @@ function post_data_chekc() if not data_len then return false end if arrlen(data_len) ==0 then return false end - if C:is_ngx_match(post_rules,data_len,'post') then + if C:is_ngx_match_post(post_rules , data_len) then C:write_log('post','regular') C:return_html(config['post']['status'],post_html) return true @@ -601,6 +592,8 @@ function waf() end waf_args() + + -- 扫描软件禁止 waf_scan_black() waf_post() diff --git a/plugins/op_waf/waf/rule/user_agent.json b/plugins/op_waf/waf/rule/user_agent.json index 57d5d72e9..0f5c5318c 100755 --- a/plugins/op_waf/waf/rule/user_agent.json +++ b/plugins/op_waf/waf/rule/user_agent.json @@ -1 +1 @@ -[[1,"(HTTrack|Apache-HttpClient|harvest|audit|dirbuster|pangolin|nmap|sqln|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|zmeu|BabyKrokodil|netsparker|httperf| SF/)","关键词过滤1",0],[2,"(ApacheBench)","AB测试",0]] \ No newline at end of file +[[1,"(HTTrack|Apache-HttpClient|harvest|audit|dirbuster|pangolin|nmap|sqln|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|zmeu|BabyKrokodil|netsparker|httperf| SF/)","关键词过滤1",0],[1,"(ApacheBench)","AB测试",0]] \ No newline at end of file