--[[ 免费WAF 功能比较少 ]]-- local cpath = "/www/server/free_waf/" package.cpath = cpath .. "/?.so;" .. cpath .. "/?.lua;" .. package.cpath local resolver = require "dns" local jpath = cpath .. "rule/" local json = require "cjson" local ngx_match = ngx.re.find local multipart = require "multipart" error_rule = nil function read_file(name) fbody = read_file_body(jpath .. name .. '.json') if fbody == nil then return {} end return json.decode(fbody) end function read_file_body(filename) fp = io.open(filename,'r') if fp == nil then return nil end fbody = fp:read("*a") fp:close() if fbody == '' then return nil end return fbody end function write_file(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 logs(data) write_file2('/dev/shm/btwaf.json',"\n"..data.."\n") end function write_file2(filename,body) fp = io.open(filename,'a+') if fp == nil then return nil end fp:write(body) fp:flush() fp:close() return true end local config = json.decode(read_file_body(cpath .. 'config.json')) local site_config = json.decode(read_file_body(cpath .. 'site.json')) function is_ipaddr(client_ip) local cipn = split(client_ip,'.') if arrlen(cipn) < 4 then return false end for _,v in ipairs({1,2,3,4}) do local ipv = tonumber(cipn[v]) if ipv == nil then return false end if ipv > 255 or ipv < 0 then return false end end return true end function http_log() data='' data=method..' ' ..request_uri.. ' '.. 'HTTP/1.1\n' if not ngx.req.get_headers(100000) then return data end for key,valu in pairs(ngx.req.get_headers(100000)) do if type(valu)=='string' then data=data..key..':'..valu..'\n' end if type(valu) =='table' then for key2,val2 in pairs(valu) do data=data..key..':'..val2..'\n' end end end data=data..'\n' if method ~='GET' then ngx.req.read_body() if get_boundary() then if ngx.req.get_body_data() then data =data ..ngx.req.get_body_data() else if config['http_open'] then request_args2=ngx.req.get_body_file() request_args2=read_file_body(request_args2) data =data ..request_args2 else data =data ..'\n拦截非法恶意上传文件或者非法from-data传值,该数据包较大系统默认不存储,如需要开启,请在[Nginx防火墙-->全局配置-->HTTP包]' end end return data else request_args = ngx.req.get_post_args(1000000) --json 记录 if ngx.req.get_headers(100000)["content-type"] and ngx.re.find(ngx.req.get_headers(100000)["content-type"], '^application/json',"oij") then local ok ,request_args = pcall(function() return json.decode(ngx.req.get_body_data()) end) if not ok then local check_html = [[json格式错误
请传递正确的json参数
]] ngx.header.content_type = "text/html;charset=utf8" ngx.say(check_html) ngx.exit(200) end if type(request_args)~='table' then return data end return data..json.encode(request_args) else --x-www-form-urlencoded 传值 coun=0 if not request_args then return data end for i,v in pairs(request_args) do if type(v) =='table' then for i2,v2 in pairs(v) do if type(v2)=='string' then if coun ==0 then data=data..i..'='..v2 else data=data..'&'..i..'='..v2 end coun=coun+1 end end elseif type(v)=='string' then if coun ==0 then data=data..i..'='..v else data=data..'&'..i..'='..v end coun=coun+1 end end return data end end else return data end end function compare_ip_block(ips) if not ips then return false end if string.find(ips,':') then return false end ips = arrip(ips) if not is_max(ips,arrip("127.0.0.255")) then return false end if not is_min(ips,arrip("127.0.0.1")) then return false end return true end function de_dict (l_key,l_data) if type(l_data) ~= "table" then return l_data end if arrlen(l_data) == 0 then return l_data end if not l_data then return false end local r_data = {} if arrlen(l_data) >= 500 then lan_ip('args','非法请求') return true end for li,lv in pairs(l_data) do r_data[l_key..tostring(li)] = lv end return r_data end function get_client_ip() local client_ip = "unknown" if site_config[server_name] then if site_config[server_name]['cdn'] then for _,v in ipairs(site_config[server_name]['cdn_header']) do if request_header[v] ~= nil and request_header[v] ~= "" then local header_tmp = request_header[v] if type(header_tmp) == "table" then header_tmp = header_tmp[1] end tmpe=split_bylog(header_tmp,',') if arrlen(tmpe)>=1 then if site_config[server_name]['cdn_baidu'] ~=nil and site_config[server_name]['cdn_baidu'] then client_ip=tmpe[1] client_ip=string.gsub(client_ip," ","") else client_ip=tmpe[arrlen(tmpe)] client_ip=string.gsub(client_ip," ","") end if request_header['remote-host'] and request_header['remote-host']~=nil then if request_header['remote-host']==client_ip then client_ip=tmpe[1] client_ip=string.gsub(client_ip," ","") end end end if compare_ip_block(client_ip) then if tostring(ngx.var.remote_addr) == tostring(client_ip) then client_ip = ngx.var.remote_addr else client_ip = ngx.var.remote_addr end end break; end end end end if type(client_ip) == 'table' then client_ip = "" end if (string.match(client_ip,"^%d+%.%d+%.%d+%.%d+$") == nil and string.match(client_ip,"^[%w:]+$") == nil) or client_ip == 'unknown' then client_ip = ngx.var.remote_addr if client_ip == nil then client_ip = "unknown" end end return client_ip end function split_bylog( str,reps ) local resultStrList = {} string.gsub(str,'[^'..reps..']+',function(w) table.insert(resultStrList,w) end) return resultStrList end function split( str,reps ) local resultStrList = {} string.gsub(str,'[^'..reps..']+',function(w) table.insert(resultStrList,w) end) return resultStrList end function arrip(ipstr) if string.find(ipstr,':') then return ipstr end if ipstr == 'unknown' then return {0,0,0,0} end iparr = split(ipstr,'.') iparr[1] = tonumber(iparr[1]) iparr[2] = tonumber(iparr[2]) iparr[3] = tonumber(iparr[3]) iparr[4] = tonumber(iparr[4]) return iparr end function join(arr,e) result = '' length = arrlen(arr) for k,v in ipairs(arr) do if length == k then e = '' end result = result .. v .. e end return result end function arrlen(arr) if not arr then return 0 end count = 0 for _,v in ipairs(arr) do count = count + 1 end return count end function select_rule(rules) if not rules then return {} end new_rules = {} for i,v in ipairs(rules) do if v[1] == 1 then table.insert(new_rules,v[2]) end end return new_rules end function return_error(int_age) error_rule='http包非法,并且被封锁IP,如果自定义了from-data可能会导致误报。如果大量出现当前问题。可以选择在全局设置中关闭From-data协议'..int_age write_log('post','regular') return_html(config['post']['status'],read_file_body('/www/server/free_waf/html/post.html')) end function is_site_config(cname) if site_config[server_name] ~= nil then if cname == 'cc' then return site_config[server_name][cname]['open'] else return site_config[server_name][cname] end end return true end function get_boundary() local header = request_header["content-type"] if not header then return nil end if type(header) == "table" then return return_message(200,'content-type ERROR') end if header then if ngx.re.find(header,[[multipart]],'ijo') then if not ngx.re.match(header,'^multipart/form-data; boundary=') then return return_message(200,'content-type ERROR') end multipart_data=ngx.re.match(header,'^multipart/form-data; boundary=.+') if not multipart_data then return return_message(200,"Btwaf Boundary Error") end if ngx.re.match(multipart_data[0],'""') then return return_message(200,"Btwaf Boundary Double Quotation Mark Error") end check_file=ngx.re.gmatch(multipart_data[0],[[=]],'ijo') ret={} while true do local m, err = check_file() if m then table.insert(ret,m) else break end end if type(ret)~='table' then return false end if(arrlen(ret)>=2) then return return_message(200,'multipart/form-data ERROR') end return header else return false end end end local get_html = read_file_body(config["reqfile_path"] .. '/' .. config["get"]["reqfile"]) local post_html = read_file_body('/www/server/free_waf/html/post.html') local cookie_html = read_file_body(config["reqfile_path"] .. '/' .. config["cookie"]["reqfile"]) local user_agent_html = read_file_body(config["reqfile_path"] .. '/' .. config["user-agent"]["reqfile"]) local other_html = read_file_body(config["reqfile_path"] .. '/' .. config["other"]["reqfile"]) local cnlist = json.decode(read_file_body(cpath .. '/rule/cn.json')) local scan_black_rules = read_file('scan_black') local ip_black_rules = read_file('ip_black') local ip_white_rules = read_file('ip_white') local url_white_rules = read_file('url_white') local url_black_rules = read_file('url_black') local user_agent_rules = select_rule(read_file('user_agent')) local post_rules = select_rule(read_file('post')) local cookie_rules = select_rule(read_file('cookie')) local args_rules = select_rule(read_file('args')) local url_rules = select_rule(read_file('url')) local head_white_rules = read_file('head_white') function is_min(ip1,ip2) n = 0 for _,v in ipairs({1,2,3,4}) do if ip1[v] == ip2[v] then n = n + 1 elseif ip1[v] > ip2[v] then break else return false end end return true end function is_max(ip1,ip2) n = 0 for _,v in ipairs({1,2,3,4}) do if ip1[v] == ip2[v] then n = n + 1 elseif ip1[v] < ip2[v] then break else return false end end return true end function compare_ip(ips) if ip == 'unknown' then return true end if string.find(ip,':') then return false end if not is_max(ipn,ips[2]) then return false end if not is_min(ipn,ips[1]) then return false end return true end function write_log(name,rule) local count,_ = ngx.shared.free_waf_drop_ip:get(ip) if count then ngx.shared.free_waf_drop_ip:incr(ip,1) else ngx.shared.free_waf_drop_ip:set(ip,1,retry_cycle) end if config['log'] ~= true or is_site_config('log') ~= true then return false end local method = ngx.req.get_method() if error_rule then rule = error_rule error_rule = nil end local logtmp = {ngx.localtime(),ip,method,request_uri,ngx.var.http_user_agent,name,rule,http_log()} local logstr = json.encode(logtmp) .. "\n" local count,_ = ngx.shared.free_waf_drop_ip:get(ip) if count > retry and name ~= 'cc' then local safe_count,_ = ngx.shared.free_waf_drop_sum:get(ip) if not safe_count then ngx.shared.free_waf_drop_sum:set(ip,1,86400) safe_count = 1 else ngx.shared.free_waf_drop_sum:incr(ip,1) end local lock_time = retry_time * safe_count if lock_time > 86400 then lock_time = 86400 end logtmp = {ngx.localtime(),ip,method,request_uri,ngx.var.http_user_agent,name,retry_cycle .. '秒以内累计超过'..retry..'次以上非法请求,封锁'.. lock_time ..'秒',http_log()} logstr = logstr .. json.encode(logtmp) .. "\n" ngx.shared.free_waf_drop_ip:set(ip,retry+1,lock_time) write_drop_ip('inc',lock_time) end write_to_file(logstr) inc_log(name,rule) end function write_drop_ip(is_drop,drop_time) local filename = cpath .. 'drop_ip.log' local fp = io.open(filename,'ab') if fp == nil then return false end local logtmp = {os.time(),ip,server_name,request_uri,drop_time,is_drop} local logstr = json.encode(logtmp) .. "\n" fp:write(logstr) fp:flush() fp:close() return true end function inc_log(name,rule) local total_path = cpath .. 'total.json' local tbody = read_file_body(total_path) if not tbody then return false 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 if not total['rules'] then total['rules'] = {} end if not total['rules'][name] then total['rules'][name] = 0 end if not total['total'] then total['total'] = 0 end 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 write_file(total_path,total_log) end function write_to_file(logstr) local filename = config["logs_path"] .. '/' .. server_name .. '_' .. ngx.today() .. '.log' local fp = io.open(filename,'ab') if fp == nil then return false end fp:write(logstr) fp:flush() fp:close() return true end function is_ssl() if(ngx.re.match(request_uri,'^/.well-known/pki-validation/')) then return true end if(ngx.re.match(request_uri,'^/.well-known/acme-challenge/')) then return true end end function drop_abroad() if ip == 'unknown' then return false end if not config['drop_abroad']['open'] or not is_site_config('drop_abroad') then return false end if string.find(ip,':') then return false end if is_ssl() then return false end if ip=='91.199.212.132' or ip=='91.199.212.133' or ip=='91.199.212.148' or ip=='91.199.212.151' or ip=='91.199.212.176' then return false end for _,v in ipairs(cnlist) do if compare_ip(v) then return false end end ngx.exit(config['drop_abroad']['status']) return true end function drop() local count,_ = ngx.shared.free_waf_drop_ip:get(ip) if not count then return false end if count > retry then ngx.exit(config['cc']['status']) return true end return false end function cc() if not config['cc']['open'] or not site_cc then return false end local token = ngx.md5(ip .. '_' .. request_uri) local count,_ = ngx.shared.free_waf:get(token) if count then if count > limit then local safe_count,_ = ngx.shared.free_waf_drop_sum:get(ip) if not safe_count then ngx.shared.free_waf_drop_sum:set(ip,1,86400) safe_count = 1 else ngx.shared.free_waf_drop_sum:incr(ip,1) end local lock_time = (endtime * safe_count) if lock_time > 86400 then lock_time = 86400 end ngx.shared.free_waf_drop_ip:set(ip,retry+1,lock_time) write_log('cc',cycle..'秒内累计超过'..limit..'次请求,封锁' .. lock_time .. '秒') write_drop_ip('cc',lock_time) ngx.exit(config['cc']['status']) return true else ngx.shared.free_waf:incr(token,1) end else ngx.shared.free_waf:set(token,1,cycle) end return false end function cc2() if not config['cc']['open'] or not site_cc then return false end if not site_config[server_name] then return false end if not site_config[server_name]['cc']['increase'] then return false end if ngx_match(uri,"\\.(jpg|png|gif|css|js|swf|ts)$","isjo") then return false end sv,_ = ngx.shared.free_waf:get(ip) if sv == 666 then return false end local token2 = ngx.md5(method .. server_name .. tostring(request_header['user-agent']) .. tostring(request_header['host']) .. tostring(request_header['accept-language']) .. tostring(request_header['connection']) .. tostring(request_header['accept']) .. tostring(request_header['accept-encoding']) .. tostring(request_header['upgrade-insecure-requests']) .. tostring(request_header['cache-control'])) .. '_' .. 'cc2' local cc2_limit = limit * 3 local count,_ = ngx.shared.free_waf:get(token2) if count then if count > cc2_limit then local safe_count,_ = ngx.shared.free_waf_drop_sum:get(ip) if not safe_count then ngx.shared.free_waf_drop_sum:set(ip,1,86400) safe_count = 1 else ngx.shared.free_waf_drop_sum:incr(ip,1) end if safe_count > retry then local lock_time = (endtime * safe_count) if lock_time > 86400 then lock_time = 86400 end ngx.shared.free_waf_drop_ip:set(ip,retry+1,lock_time) write_log('cc',cycle..'秒内累计超过'..limit..'次请求,封锁' .. lock_time .. '秒') write_drop_ip('cc',lock_time) ngx.exit(config['cc']['status']) return true end security_verification() else ngx.shared.free_waf:incr(token2,1) end else ngx.shared.free_waf:safe_set(token2,1,cycle) end return false end function security_verification() if uri_request_args['btwaf'] then vn3,_ = ngx.shared.free_waf:get(ip) if tostring(vn3) == uri_request_args['btwaf'] then ngx.shared.free_waf:delete(ip) ngx.shared.free_waf:delete(ngx.md5(ip .. '_' .. uri)) ngx.shared.free_waf:set(ip,666,3600) return false end end math.randomseed(tostring(os.time()):reverse():sub(1, 6)) local n1 = math.random(0,20) local n2 = math.random(0,20) local n3 = n1 + n2 ngx.shared.free_waf:set(ip,n3,300) local vargs = '&btwaf=' sargs = string.gsub(request_uri,'.?btwaf=.*','') if not string.find(sargs,'?',1,true) then vargs = '?btwaf=' end ngx.header.charset = 'utf-8' local jsbody = string.format([[ ]],tostring(n1) .. ' + ' .. tostring(n2),sargs .. vargs) ngx.header.content_type = "text/html;charset=utf8" ngx.say(jsbody) ngx.exit(403) end function scan_black() if not config['scan']['open'] or not is_site_config('scan') then return false end if is_ngx_match(scan_black_rules['cookie'],request_header['cookie'],false) then write_log('scan','regular') ngx.exit(config['scan']['status']) return true end if is_ngx_match(scan_black_rules['args'],request_uri,false) then write_log('scan','regular') ngx.exit(config['scan']['status']) return true end for key,value in pairs(request_header) do if is_ngx_match(scan_black_rules['header'],key,false) then write_log('scan','regular') ngx.exit(config['scan']['status']) return true end end return false end function ip_black() for _,rule in ipairs(ip_black_rules) do if compare_ip(rule) then ngx.exit(config['cc']['status']) return true end end return false end function ip_white() for _,rule in ipairs(ip_white_rules) do if compare_ip(rule) then return true end end return false end function split2(input, delimiter) input = tostring(input) delimiter = tostring(delimiter) if (delimiter=='') then return false end local pos,arr = 0, {} for st,sp in function() return string.find(input, delimiter, pos, true) end do table.insert(arr, string.sub(input, pos, st - 1)) pos = sp + 1 end table.insert(arr, string.sub(input, pos)) return arr end function url_white() if ngx.var.document_root=='/www/server/phpmyadmin' then return true end if is_ngx_match(url_white_rules,request_uri,false) then url_data=split2(request_uri,'?') if not url_data then url_data=request_uri end if not url_data[1] then url_data=request_uri else url_data=url_data[1] end if ngx.re.match(url_data,'/\\.\\./') then return false end return true end if site_config[server_name] ~= nil then if is_ngx_match(site_config[server_name]['url_white'],request_uri,false) then url_data=split2(request_uri,'?') if not url_data then url_data=request_uri end if not url_data[1] then url_data=request_uri else url_data=url_data[1] end if ngx.re.match(url_data,'/\\.\\./') then return false end return true end end return false end function url_black() if is_ngx_match(url_black_rules,request_uri,false) then ngx.exit(config['get']['status']) return true end return false end function head() if method ~= 'HEAD' then return false end for _,v in ipairs(head_white_rules) do if ngx_match(uri,v,"isjo") then return false end end spiders = {'spider','bot'} for _,v in ipairs(spiders) do if ngx_match(request_header['user-agent'],v,"isjo") then return false end end write_log('head','禁止HEAD请求') ngx.shared.free_waf:set(ip,retry,endtime) write_drop_ip('head',endtime) ngx.exit(444) end function user_agent() if not config['user-agent']['open'] or not is_site_config('user-agent') then return false end if is_ngx_match(user_agent_rules,request_header['user-agent'],'user_agent') then write_log('user_agent','regular') return_html(config['user-agent']['status'],user_agent_html) return true end return false end local function _process_json_args(json_args,t) if type(json_args)~='table' then return {} end local t = t or {} for k,v in pairs(json_args) do if type(v) == 'table' then for _k,_v in pairs(v) do if type(_v) == "table" then t = _process_json_args(_v,t) else if type(t[k]) == "table" then table.insert(t[k],_v) elseif type(t[k]) == "string" then local tmp = {} table.insert(tmp,t[k]) table.insert(tmp,_v) t[k] = tmp else t[k] = _v end end end else if type(t[k]) == "table" then table.insert(t[k],v) elseif type(t[k]) == "string" then local tmp = {} table.insert(tmp,t[k]) table.insert(tmp,v) t[k] = tmp else t[k] = v end end end return t end function split2(input, delimiter) input = tostring(input) delimiter = tostring(delimiter) if (delimiter=='') then return false end local pos,arr = 0, {} for st,sp in function() return string.find(input, delimiter, pos, true) end do table.insert(arr, string.sub(input, pos, st - 1)) pos = sp + 1 end table.insert(arr, string.sub(input, pos)) return arr end function post() if not config['post']['open'] or not is_site_config('post') then return false end if method == "GET" then return false end content_length=tonumber(request_header['content-length']) if content_length == nil then return false end local content_type = ngx.req.get_headers(100000)["Content-type"] if not content_type then return false end if type(content_type)~='string' then return_error(1) end if content_type and ngx.re.find(content_type, 'multipart',"oij") then return false end ngx.req.read_body() request_args = ngx.req.get_post_args(1000000) if not request_args then if content_length >10000 then request_uri22=split2(request_uri,'?') request_uri22=request_uri22[1] local check_html = [[Nginx缓冲区溢出
宝塔免费WAF提醒您,Nginx缓冲区溢出,传递的参数超过接受参数的大小,出现异常,
第一种解决方案:把当前url-->]]..'^'..request_uri22..[[加入到URL白名单中
第二种解决方案:面板-->nginx管理->性能调整-->client_body_buffer_size的值调整为10240K 或者5024K(PS:可能会一直请求失败建议加入白名单)
]] ngx.header.content_type = "text/html;charset=utf8" ngx.say(check_html) ngx.exit(403) end return true end list_data={} if type(request_args)=='table' then for k,v in pairs(request_args) do if type(v)=='table' then table.insert(list_data,de_dict(k,v)) end if type(v)=='string' then if not string.find(v,'^data:.+/.+;base64,') then if (#v) >=200000 then write_log('post',k..' 参数值长度超过20w已被系统拦截') return_html(config['post']['status'],read_file_body('/www/server/free_waf/html/post.html')) return true end end end end end if content_type and ngx.re.find(content_type, '^application/json',"oij") and ngx.req.get_headers(100000)["Content-Length"] and tonumber(ngx.req.get_headers(10000)["Content-Length"]) ~= 0 then local ok ,request_args = pcall(function() return json.decode(ngx.req.get_body_data()) end) if not ok then local check_html = [[json格式错误
请传递正确的json参数
]] ngx.header.content_type = "text/html;charset=utf8" ngx.say(check_html) ngx.exit(403) end if type(request_args)~='table' then return false end request_args=_process_json_args(request_args) if is_ngx_match(post_rules,request_args,'post') then write_log('post','regular') return_html(config['post']['status'],read_file_body('/www/server/free_waf/html/post.html')) return true end else if list_data then if arrlen(list_data)>=1 then for i2,v2 in ipairs(list_data) do request_args=_process_json_args(v2,request_args) end else request_args=_process_json_args(list_data,request_args) end else request_args =_process_json_args(request_args) end if count_sieze(request_args)>=800 then error_rule = '参数太多POST传递的参数数量超过800,拒绝访问,如有误报请点击误报' write_log('post','参数太多POST传递的参数数量超过800,拒绝访问,如有误报请点击误报') local check_html = [[参数太多
宝塔免费WAF提醒您,POST传递的参数数量超过800,拒绝访问,如有误报请点击误报
]] ngx.header.content_type = "text/html;charset=utf8" ngx.say(check_html) ngx.exit(403) end if list_data then for i2,v2 in ipairs(list_data) do if is_ngx_match(post_rules,v2,'post') then write_log('args','regular') return_html(config['get']['status'],get_html) return true end end end if is_ngx_match(post_rules,request_args,'post') then write_log('post','regular') return_html(config['post']['status'],read_file_body('/www/server/free_waf/html/post.html')) return true end end return false end function disable_upload_ext(ext) if not ext then return false end if is_key(site_config[server_name]['disable_upload_ext'],ext) then write_log('upload_ext','上传扩展名黑名单') return_html(config['other']['status'],other_html) return true end end function disable_upload_ext2(ext) if not ext then return false end if type(ext)~='table' then return false end for i,k in pairs(ext) do for i2,k2 in pairs(k) do check_file=ngx.re.gmatch(k2,[[filename=]],'ijo') ret={} while true do local m, err = check_file() if m then table.insert(ret,m) else break end end if arrlen(ret)>1 then return_error(2) end if not ngx.re.match(k2,[[filename=""]],'ijo') and not ngx.re.match(k2,[[filename=".+"]],'ijo') then return_error(3) else k2 = string.lower(k2) if site_config[server_name] ==nil then return false end disa=site_config[server_name]['disable_upload_ext'] if is_ngx_match(disa,k2,'post') then lan_ip('disable_upload_ext','上传非法PHP文件被系统拦截,并且被封锁IP2') return true end end end end end function from_data(data,data2,data3) if arrlen(data) ==0 then return false end local count=0 for k,v in pairs(data) do if ngx.re.match(v[0],'filename=') then if not ngx.re.match(v[0],'Content-Disposition: form-data; name="[^"]+"; filename=""\r*$','ijo') then if not ngx.re.match(v[0],'Content-Disposition: form-data; name="[^"]+"; filename="[^"]+"\r*$','ijo') then return_error2(1) end end count=count+1 disable_upload_ext(v[0]) end if config['from_data'] then if not ngx.re.match(v[0],'filename=') and not ngx.re.match(v[0],'Content-Disposition: form-data; name="[^"]+"\r*$','ijo') then return_error2(2) end end end len_count=arrlen(data2)+arrlen(data3) if count ~=len_count then return_error2(3) end end function return_error2(int_age) error_rule = 'from-data 请求异常,拒绝访问,如有误报请点击误报 return_error2'..int_age write_log('post','from-data 请求异常,拒绝访问,如有误报请点击误报') local check_html = [[from-data请求error
宝塔免费WAF提醒您,from-data 请求异常,拒绝访问,如有误报请点击误报
]] ngx.header.content_type = "text/html;charset=utf8" ngx.say(check_html) ngx.exit(200) end function lan_ip(type,name) local safe_count,_ = ngx.shared.free_waf_drop_sum:get(ip) if not safe_count then ngx.shared.free_waf_drop_sum:set(ip,1,86400) safe_count = 1 else ngx.shared.free_waf_drop_sum:incr(ip,1) end local lock_time = (endtime * safe_count) if lock_time > 86400 then lock_time = 86400 end ngx.shared.free_waf_drop_ip:set(ip,retry+1,lock_time) --write_log(type,name) local method = ngx.req.get_method() if error_rule then rule = error_rule error_rule = nil end local logtmp = {ngx.localtime(),ip,method,request_uri,ngx.var.http_user_agent,type,name,http_log()} local logstr = json.encode(logtmp) .. "\n" write_to_file(logstr) inc_log(type,rule) if type =='args' or type=='post' or type =='inc' then write_drop_ip('inc',lock_time) else write_drop_ip(type,lock_time) end ngx.exit(config['cc']['status']) end function gusb_string(table) ret={"-","]","@","#","&","_","{","}"} ret2={} if arrlen(table)==0 then return table end for _,v in pairs(table) do for _,v2 in pairs(ret) do if ngx.re.find(v[0],v2) then v[0]=ngx.re.gsub(v[0],v2,'baota') end end v[0]=string.gsub(v[0],'%[','baota') v[0]=string.gsub(v[0],'%(','baota') v[0]=string.gsub(v[0],'%)','baota') v[0]=string.gsub(v[0],'%+','baota') v[0]=string.gsub(v[0],'%$','baota') v[0]=string.gsub(v[0],'%?','baota') end return table end function disable_upload_ext3(ext,check) if not ext then return false end if type(ext)~='table' then return false end for i2,k2 in pairs(ext) do check_file=ngx.re.gmatch(k2,[[filename=| filename=|filename="|filename=']],'ijo') ret={} while true do local m, err = check_file() if m then table.insert(ret,m) else break end end if arrlen(ret)>1 then return_error(4) end if check==1 then if arrlen(ret)==0 then if not k2 then return false end if ngx.re.match(k2,[[Content-Disposition: form-data; name=".+\\"\r]]) then return return_error2(3.6) end kkkkk=ngx.re.match(k2,[[Content-Disposition:.{200}]],'ijo') if not kkkkk then if not ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\r]],'ijom') or ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\r;name=]],'ijo') or ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\r;\s*\r*\n*n\s*\r*\n*a\s*\r*\n*m\s*\r*\n*e\s*\r*\n*=]],'ijo') or ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\s*;]],'ijo') then k2=string.gsub(k2,'\r','') if ngx.re.match(k2,[[filename=]],'ijo') then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP1') end return return_error2(4) end else k2=kkkkk[0] if not ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\r]],'ijom') or ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\r;name=]],'ijo') or ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\r;\s*\r*\n*n\s*\r*\n*a\s*\r*\n*m\s*\r*\n*e\s*\r*\n*=]],'ijo') or ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"\r\s*;]],'ijo') then k2=string.gsub(k2,'\r','') if ngx.re.match(k2,[[filename=]],'ijo') then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP2') end return return_error2(5) end end if k2 then k2=string.gsub(k2,'\r','') if ngx.re.match(k2,[[filename=]],'ijo') then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP3') end end if ngx.re.match(k2,[[Content-Disposition: form-data; name="(.+)"\r]],'ijos') then tttt=ngx.re.match(k2,[[Content-Disposition: form-data; name="(.+)"\r\s]],'ijos') if tttt==nil then return false end if #tttt[0] >200 then return false end if tttt[1] ==nil then return false end tttt[1]=string.gsub(tttt[1],'\n','') tttt[1]=string.gsub(tttt[1],'\t','') tttt[1]=string.gsub(tttt[1],'\r','') if ngx.re.match(tttt[1],'name=','ijo') then return return_error2(6) end end if ngx.re.match(k2,[[\r\r(.+)\r\r]],'ijos') then tttt=ngx.re.match(k2,[[\r\r(.+)\r\r]],'ijos') if tttt==nil then return false end if #tttt[0] >200 then return false end if tttt[1] ==nil then return false end tttt[1]=string.gsub(tttt[1],'\n','') tttt[1]=string.gsub(tttt[1],'\t','') tttt[1]=string.gsub(tttt[1],'\r','') if ngx.re.match(tttt[1],'name=','ijo') then return return_error2(7) end end else if not k2 then return false end k2=string.gsub(k2,'\r','') kkkkk=ngx.re.match(k2,[[Content-Disposition:.{200}]],'ijo') if not kkkkk then k3=ngx.re.match(k2,[[Content-Disposition:.+Content-Type:]],'ijo') if not k3 then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP4') end if not ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"; filename=""Content-Type:]],'ijo') and not ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"; filename=".+"Content-Type:]],'ijo') then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP5') end else k3=ngx.re.match(kkkkk[0],[[Content-Disposition:.+Content-Type:]],'ijo') if not k3 then return false end if not ngx.re.match(k3[0],[[Content-Disposition: form-data; name=".+"; filename=""Content-Type:]],'ijo') and not ngx.re.match(k3[0],[[Content-Disposition: form-data; name=".+"; filename=".+"Content-Type:]],'ijo') then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP7') end end if site_config[server_name] ==nil then return false end disa=site_config[server_name]['disable_upload_ext'] if is_ngx_match(disa,k3,'post') then lan_ip('disable_upload_ext','上传非法PHP文件被系统拦截,并且被封锁IP') end if #k3[0] >200 then ret10={} local tmp10 = ngx.re.gmatch(k3[0],'form-data') while true do local m, err = tmp10() if m then table.insert(ret10,m) else break end end if tonumber(arrlen(ret10)) >1 then return false end if ngx.re.match(k3[0],'--$') then return false end return return_message(200,'error1->The upload file name is too long') end local tmp8 = ngx.re.gmatch(k3[0],'\"') local tmp9 = ngx.re.gmatch(k3[0],'=') local tmp10 = ngx.re.gmatch(k3[0],';') ret8={} ret9={} ret10={} while true do local m, err = tmp8() if m then table.insert(ret8,m) else break end end while true do local m, err = tmp9() if m then table.insert(ret9,m) else break end end while true do local m, err = tmp10() if m then table.insert(ret10,m) else break end end if tonumber(arrlen(ret9))~=2 and tonumber(arrlen(ret8))~=4 and tonumber(arrlen(ret10))~=2 then return return_error2(11) end --error_rule = '非法文件上传请求。已经被系统拦截' --write_log('post','非法文件上传请求。已经被系统拦截') --local check_html = [[非法请求
宝塔WAF提醒您,文件上传参数错误。文件名或者参数中不能存在分号、等号、和双引号。如有误报请点击误报
]] --ngx.header.content_type = "text/html;charset=utf8" --ngx.say(check_html) --ngx.exit(200) end else if arrlen(ret)==0 then return false else kkkkk=ngx.re.match(k2,[[Content-Disposition:.{500}]],'ijo') if not kkkkk then if ngx.re.match(k2,[[Content-Disposition: form-data; name=".+\\"]]) then return return_error2(4.6) end k3=ngx.re.match(k2,[[Content-Disposition:.+Content-Type:]],'ijo') if not k3 then return return_error(5) end if not ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"; filename=""Content-Type:]],'ijo') and not ngx.re.match(k2,[[Content-Disposition: form-data; name=".+"; filename=".+"Content-Type:]],'ijo') then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP5') end else if ngx.re.match(kkkkk[0],[[Content-Disposition: form-data; name=".+\\"]]) then return return_error2(4.6) end k3=ngx.re.match(kkkkk[0],[[Content-Disposition:.+Content-Type:]],'ijo') if not k3 then return false end if not ngx.re.match(k3[0],[[Content-Disposition: form-data; name=".+"; filename=""Content-Type:]],'ijo') and not ngx.re.match(k3[0],[[Content-Disposition: form-data; name=".+"; filename=".+"Content-Type:]],'ijo') then return lan_ip('disable_upload_ext','非法上传请求已被系统拦截,并且被封锁IP7') end end k3=k3[0] if not ngx.re.match(k3,[[filename=""Content-Type]],'ijo') and not ngx.re.match(k3,[[filename=".+"Content-Type]],'ijo') then return_error(6) else check_filename=ngx.re.match(k3,[[filename="(.+)"Content-Type]],'ijo') if check_filename then if check_filename[1] then if ngx.re.match(check_filename[1],'name=','ijo') then return return_error(7) end if ngx.re.match(check_filename[1],'php','ijo') then return return_error(8) end if ngx.re.match(check_filename[1],'jsp','ijo') then return return_error(9) end end end if #k3 >=500 then write_log('post','上传的文件名太长了,被系统拦截') return return_message(200,k3) end k3 = string.lower(k2) if site_config[server_name] ==nil then return false end disa=site_config[server_name]['disable_upload_ext'] if is_ngx_match(disa,k3,'post') then lan_ip('disable_upload_ext','上传非法PHP文件被系统拦截,并且被封锁IP'..' >> '..k3) return true end end end end end end function table_key(tbl, key) if tbl == nil then return false end for k, v in pairs(tbl) do if k == key then return true end end return false end local function chsize(char) if not char then print("not char") return 0 elseif char > 240 then return 4 elseif char > 225 then return 3 elseif char > 192 then return 2 else return 1 end end function ReadFileHelper(str) if type(str)~='string' then return str end res = string.gsub(str, "\r", "") res = string.gsub(res, "\n", "") return res end function utf8sub(str, startChar, numChars) local startIndex = 1 while startChar > 1 do local char = string.byte(str, startIndex) startIndex = startIndex + chsize(char) startChar = startChar - 1 end local currentIndex = startIndex while numChars > 0 and currentIndex <= #str do local char = string.byte(str, currentIndex) currentIndex = currentIndex + chsize(char) numChars = numChars -1 end return str:sub(startIndex, currentIndex - 1) end function return_post_data2() if method ~= "POST" then return false end content_length=tonumber(request_header['content-length']) if not content_length then return false end local boundary = get_boundary() if boundary then ngx.req.read_body() local data = ngx.req.get_body_data() if not data then data=ngx.req.get_body_file() data=read_file_body(data) end if not data then return false end local tmp2 = ngx.re.gmatch(data,[[Content-Disposition.+filename=]],'ijo') ret={} while true do local m, err = tmp2() if m then table.insert(ret,m) else break end end ret=gusb_string(ret) if arrlen(ret)>=1 then for _,v in pairs(ret) do if not ngx.re.match(v[0],'ContentbaotaDisposition: formbaotadata; name=".+"; filename=','ijo') and not ngx.re.match(v[0],'ContentbaotaDisposition: formbaotadata; name=”.+”; filename=','ijo') then return_error(10) end end end if arrlen(ret)==1 then return 1 else return 2 end end return 3 end function is_substitution(data) data=ngx.re.sub(data,"\\+",'\\+') return data end function post_data_chekc() if not config['post']['open'] or not is_site_config('post') then return false end content_length=tonumber(request_header['content-length']) if not content_length then return false end if content_length >108246867 then return false end if method =="POST" then return_post_data=return_post_data2() if not return_post_data then return false end if return_post_data==3 then return false end ngx.req.read_body() request_args2=ngx.req.get_body_data() if not request_args2 then request_args2=ngx.req.get_body_file() request_args2=read_file_body(request_args2) end if not request_args2 then return false end if not request_header['Content-Type'] then return false end if type(request_header['Content-Type']) ~= "string" then if type(request_header['Content-Type']) ~= "string" then return_error(11) end end local p, err = multipart.new(request_args2, ngx.var.http_content_type) if not p then return false end if not ngx.re.match(ReadFileHelper(p['body']),is_substitution(ReadFileHelper(p['boundary2']))..'--$','ijo') then lan_ip('post','http包非法,header头部中Content-Type: multipart/form-data; boundary=存在特殊字符.拦截此文件上传的包不。不允许出现【@#%^&*()=\\/】') end site_count=0 local array = {} while true do local part_body, name, mime, filename,is_filename,header_data = p:parse_part() if header_data then header_data_check=ngx.re.gmatch(header_data,[[Content-Disposition: form-data]],'ijo') ret={} while true do local m, err = header_data_check() if m then table.insert(ret,m) else break end end if arrlen(ret)>1 then return return_message(200,"btwaf is from-data error2") end end if not is_filename then break end if is_filename then filename_data=ngx.re.match(is_filename,'filename.+','ijo') if filename_data then if ngx.re.match(filename_data[0],'php','ijo') then return return_error(22) end if ngx.re.match(filename_data[0],'jsp','ijo') then return return_error(23) end if not ngx.re.match(is_filename,'^Content-Disposition: form-data; name=".+"; filename=".+"Content-Type:','ijo') and not ngx.re.match(is_filename,'^Content-Disposition: form-data; name=".+"; filename=""Content-Type:','ijo') and not ngx.re.match(is_filename,'^Content-Disposition: form-data; name="filename"','ijo') then return_error(23) end end if (#is_filename)>=1000 then return_error(22) end end site_count=site_count+1 if filename ~=nil then if ngx.re.match(filename,'name=','ijo') then return return_error(13) end if ngx.re.match(filename,'php','ijo') then return return_error(20) end if ngx.re.match(filename,'jsp','ijo') then return return_error(21) end if (#filename)>=1000 then return_error(22) end end if name ==nil then if part_body then if #part_body>30 then array[utf8sub(part_body,1,30)]=part_body else array[part_body]=part_body end end else if #name >300 then return_error(14) end if filename ==nil then if table_key(array,name) then for i=1, 1000 do if not table_key(array,name..'_'..i) then if #name>30 then array[utf8sub(name,1,30)..'_'..i]=part_body else array[name..'_'..i]=part_body end break end end else if #name >30 then array[utf8sub(name,1,30)]=part_body else array[name]=part_body end end if type(part_body)=='string' then if (#part_body) >=200000 then write_log('post',name..' 参数值长度超过20w已被系统拦截') return_html(config['post']['status'],read_file_body('/www/server/free_waf/html/post.html')) return true end end else if type(part_body) =='string' and part_body ~=nil then if ngx.re.find(part_body,[[phpinfo\(]],'ijo') or ngx.re.find(part_body,[[\$_SERVER]],'ijo') or ngx.re.find(part_body,[[<\?php]],'ijo') or ngx.re.find(part_body,[[fputs]],'ijo') or ngx.re.find(part_body,[[file_put_contents]],'ijo') or ngx.re.find(part_body,[[file_get_contents]],'ijo') or ngx.re.find(part_body,[[eval\(]],'ijo') or ngx.re.find(part_body,[[\$_POST]],'ijo') or ngx.re.find(part_body,[[\$_GET]],'ijo') or ngx.re.find(part_body,[[base64_decode\(]],'ijo') or ngx.re.find(part_body,[[\$_REQUEST]],'ijo') or ngx.re.find(part_body,[[assert\(]],'ijo') or ngx.re.find(part_body,[[copy\(]],'ijo') or ngx.re.find(part_body,[[create_function\(]],'ijo') or ngx.re.find(part_body,[[preg_replace\(]],'ijo') or ngx.re.find(part_body,[[preg_filter\(]],'ijo') or ngx.re.find(part_body,[[system\(]],'ijo') or ngx.re.find(part_body,[[header_register_callback\(]],'ijo') or ngx.re.find(part_body,[[curl_init\(]],'ijo') or ngx.re.find(part_body,[[curl_error\(]],'ijo') or ngx.re.find(part_body,[[fopen\(]],'ijo') or ngx.re.find(part_body,[[stream_context_create\(]],'ijo') or ngx.re.find(part_body,[[fsockopen\(]],'ijo') then lan_ip('disable_upload_ext','webshell防御.拦截木马上传,并被封锁IP') end end end end end if site_count==0 then if config['from_data'] then return return_error2(8) end end if count_sieze(array)>=1000 then error_rule = '参数太多POST传递的参数数量超过1000,拒绝访问,如有误报请点击误报' write_log('post','参数太多POST传递的参数数量超过1000,拒绝访问,如有误报请点击误报') local check_html = [[参数太多
宝塔免费WAF提醒您,multipart/from-data传递的参数数量超过1000,拒绝访问,如有误报请点击误报
]] ngx.header.content_type = "text/html;charset=utf8" ngx.say(check_html) ngx.exit(403) end if array['_method'] and array['method'] and array['server[REQUEST_METHOD]'] then lan_ip('post','拦截ThinkPHP 5.x RCE 攻击') end if array['_method'] and array['method'] and array['server[]'] and array['get[]'] then lan_ip('post','拦截ThinkPHP 5.x RCE 攻击,并且被封锁IP') end if array['_method'] and ngx.re.match(array['_method'],'construct','ijo') then lan_ip('post','拦截ThinkPHP 5.x RCE 攻击,并且被封锁IP') end if is_ngx_match(post_rules,array,'post') then write_log('post','regular') return_html(config['post']['status'],read_file_body('/www/server/free_waf/html/post.html')) return true end end end function data_in_php(data) if not data then return false else if ngx.re.find(data,[[<\?php]],'ijo') then error_rule="非法上传php文件被系统拦截" write_log('post','regular') return_html(config['post']['status'],cookie_html) return true else return false end end end function post_data() if not config['post']['open'] or not is_site_config('post') then return false end if method ~= "POST" then return false end content_length=tonumber(request_header['content-length']) if not content_length then return false end if content_length >108246867 then return false end local boundary = get_boundary() if boundary then ngx.req.read_body() local data = ngx.req.get_body_data() if not data then data=ngx.req.get_body_file() data=read_file_body(data) end if not data then return false end data233=string.gsub(data,'\r','') local tmp4 = ngx.re.gmatch(data,[[Content-Disposition.+]],'ijo') local tmp5 = ngx.re.gmatch(data,[[Content-Disposition: form-data; name=".+"; filename=".+"\r\nContent-Type:]],'ijo') local tmp6 = ngx.re.gmatch(data,[[Content-Disposition: form-data; name=".+"; filename=""\r\nContent-Type:]],'ijo') ret3={} while true do local m, err = tmp4() if m then table.insert(ret3,m) else break end end ret5={} while true do local m, err = tmp5() if m then table.insert(ret5,m) else break end end ret6={} while true do local m, err = tmp6() if m then table.insert(ret6,m) else break end end from_data(ret3,ret5,ret6) local tmp2 = ngx.re.gmatch(data,[[Content-Disposition.+filename=.+]],'ijo') local tmp3 = ngx.re.gmatch(data,[[Content-Disposition.+\s*f\r*\n*o\r*\n*r\r*\n*m\r*\n*-\r*\n*d\r*\n*a\r*\n*t\r*\n*a\r*\n*\s*;\r*\n*\s*n\r*\n*a\r*\n*m\r*\n*e=\r*\n*.+;\s*f\n*\s*\r*i\n*\s*\r*l\n*\s*\r*e\n*\s*\r*n\n*\s*\r*a\n*\s*\r*m\n*\s*\r*e\n*\s*\r*=.+\n*\s*\r*]],'ijo') ret={} while true do local m, err = tmp2() if m then table.insert(ret,m) else break end end ret2={} while true do local m, err = tmp3() if m then table.insert(ret2,m) else break end end disable_upload_ext2(ret2) if arrlen(ret)==0 and arrlen(ret2)>0 then return_error(15) end ret=gusb_string(ret) for k,v in pairs(ret) do disable_upload_ext(v) end local tmp2=ngx.re.match(data,[[Content-Type:[^\+]{100}]],'ijo') if tmp2 and tmp2[0] then data_in_php(tmp2[0]) end av=ngx.re.match(boundary,"=.+") if not av then return return_error(16) end header_data=ngx.re.gsub(av[0],'=','') if #header_data>200 then return_error(17) end data=string.gsub(data,'\n','') data=string.gsub(data,'\t','') local tmp_pyload2 = ngx.re.match(data,'Content-Disposition:.+\r--','ijo') if tmp_pyload2==nil then return false end tmpe_data2=split2(tmp_pyload2[0],header_data) if arrlen(tmpe_data2)>0 then if config['from_data'] then disable_upload_ext3(tmpe_data2,1) end end data=string.gsub(data,'\r','') local tmp_pyload = ngx.re.match(data,'Content-Disposition:.+Content-Type:','ijo') if tmp_pyload==nil then return false end tmpe_data=split2(tmp_pyload[0],header_data) if arrlen(tmpe_data)>0 then disable_upload_ext3(tmpe_data,2) end end return false end function cookie() if not config['cookie']['open'] or not is_site_config('cookie') then return false end if not request_header['cookie'] then return false end if type(request_header['cookie']) == "table" then return false end request_cookie = string.lower(request_header['cookie']) if is_ngx_match(cookie_rules,request_cookie,'cookie') then write_log('cookie','regular') return_html(config['cookie']['status'],cookie_html) return true end return false end function count_sieze(data) count=0 for k,v in pairs(data) do count=count+1 end return count end function de_dict2(l_key,l_data) if type(l_data) ~= "table" then return l_data end if arrlen(l_data) == 0 then return l_data end if not l_data then return false end local r_data = {} if arrlen(l_data) >= 100 then lan_ip('args','非法请求') return true end for li,lv in pairs(l_data) do r_data[l_key..tostring(li)] = lv end return r_data end function args() if not config['get']['open'] or not is_site_config('get') then return false end local rd_data = {} if type(uri_request_args)=='table' then for k,v in pairs(uri_request_args) do if type(v)=='table' then table.insert(rd_data,de_dict2(k,v)) end end end if count_sieze(uri_request_args)>=800 then error_rule = '参数太多GET传递的参数数量超过800,拒绝访问,如有误报请点击误报' write_log('args','参数太多GET传递的参数数量超过800,拒绝访问,如有误报请点击误报') local check_html = [[参数太多
宝塔免费WAF提醒您,传递的参数数量超过800,拒绝访问,如有误报请点击误报
]] ngx.header.content_type = "text/html;charset=utf8" ngx.say(check_html) ngx.exit(403) end if rd_data then for i2,v2 in ipairs(rd_data) do if is_ngx_match(args_rules,v2,'post') then write_log('args','regular') return_html(config['get']['status'],get_html) return true end end end if is_ngx_match(args_rules,uri_request_args,'args') then write_log('args','regular') return_html(config['get']['status'],get_html) return true end return false end function url() if not config['get']['open'] or not is_site_config('get') then return false end --正则-- if is_ngx_match(url_rules,uri,'url') then write_log('url','regular') return_html(config['get']['status'],get_html) return true end return false end function php_path() if site_config[server_name] == nil then return false end for _,rule in ipairs(site_config[server_name]['disable_php_path']) do if ngx_match(uri,rule .. "/.*\\.php$","isjo") then write_log('php_path','regular') return_html(config['other']['status'],other_html) return true end end return false end function url_path() if site_config[server_name] == nil then return false end for _,rule in ipairs(site_config[server_name]['disable_path']) do if ngx_match(uri,rule,"isjo") then write_log('path','regular') return_html(config['other']['status'],other_html) return true end end return false end function url_ext() if site_config[server_name] == nil then return false end for _,rule in ipairs(site_config[server_name]['disable_ext']) do if ngx_match(uri,"\\."..rule.."$","isjo") then write_log('url_ext','regular') return_html(config['other']['status'],other_html) return true end end return false end function url_rule_ex() if site_config[server_name] == nil then return false end if method == "POST" and not request_args then content_length=tonumber(request_header['content-length']) max_len = 64 * 1024 request_args = nil if content_length < max_len then ngx.req.read_body() request_args = ngx.req.get_post_args() end end for _,rule in ipairs(site_config[server_name]['url_rule']) do if ngx_match(uri,rule[1],"isjo") then if is_ngx_match(rule[2],uri_request_args,false) then write_log('url_rule','regular') return_html(config['other']['status'],other_html) return true end if method == "POST" and request_args ~= nil then if is_ngx_match(rule[2],request_args,'post') then write_log('post','regular') return_html(config['other']['status'],other_html) return true end end end end return false end function url_tell() if site_config[server_name] == nil then return false end for _,rule in ipairs(site_config[server_name]['url_tell']) do if ngx_match(uri,rule[1],"isjo") then if uri_request_args[rule[2]] ~= rule[3] then write_log('url_tell','regular') return_html(config['other']['status'],other_html) return true end end end return false end function continue_key(key) if method ~='POST' then return true end key = tostring(key) if string.len(key) > 64 then return false end; local keys = {"content","contents","body","msg","file","files","img","newcontent","message","subject","kw","srchtxt",""} for _,k in ipairs(keys) do if k == key then return false end; end return true; end function is_ngx_match(rules,sbody,rule_name) if rules == nil or sbody == nil then return false end if type(sbody) == "string" then sbody = {sbody} end if type(rules) == "string" then rules = {rules} end for k,body in pairs(sbody) do if continue_key(k) then for i,rule in ipairs(rules) do if site_config[server_name] and rule_name then local n = i - 1 for _,j in ipairs(site_config[server_name]['disable_rule'][rule_name]) do if n == j then rule = "" end end end if body and rule ~="" then if type(body) == "string" then if ngx_match(ngx.unescape_uri(body),rule,"isjo") then if method ~="POST" and rule=="'$" then lan_ip("args","SQL探测被系统封锁IP") end error_rule = rule .. ' >> ' .. k .. ':' .. body return true end end if type(k) == "string" then if ngx_match(ngx.unescape_uri(k),rule,"isjo") then error_rule = rule .. ' >> ' .. k return true end end end end end end return false end function is_key(keys,values) if keys == nil or values == nil then return false end if type(values) == "string" then values = {values} end if type(keys) == "string" then keys = {keys} end for _,value in pairs(values) do if type(value) == "boolean" or value == "" then return false end sval = ngx.unescape_uri(string.lower(ngx.unescape_uri(value))) for _,v in ipairs(keys) do if v == sval then return true end end end return false end function get_return_state(rstate,rmsg) result = {} result['status'] = rstate result['msg'] = rmsg return result end function get_btwaf_drop_ip() local data = ngx.shared.free_waf_drop_ip:get_keys(0) return data end function remove_btwaf_drop_ip() if not uri_request_args['ip'] or not is_ipaddr(uri_request_args['ip']) then return get_return_state(true,'格式错误') end ngx.shared.free_waf_drop_ip:delete(uri_request_args['ip']) return get_return_state(true,uri_request_args['ip'] .. '已解封') end function clean_btwaf_drop_ip() local data = get_btwaf_drop_ip() for _,value in ipairs(data) do ngx.shared.free_waf_drop_ip:delete(value) end return get_return_state(true,'已解封所有封锁IP') end function min_route() if ngx.var.remote_addr ~= '127.0.0.1' then return false end if uri == '/get_btwaf_drop_ip' then return_message(200,get_btwaf_drop_ip()) elseif uri == '/remove_btwaf_drop_ip' then return_message(200,remove_btwaf_drop_ip()) elseif uri == '/clean_btwaf_drop_ip' then return_message(200,clean_btwaf_drop_ip()) end end function return_message(status,msg) ngx.header.content_type = "application/json;" ngx.status = status ngx.say(json.encode(msg)) ngx.exit(status) end function return_html(status,html) ngx.header.content_type = "text/html" ngx.status = status ngx.say(html) ngx.exit(status) end function is_header_error() for k,v in pairs(ngx.req.get_headers(100000)) do if k=='cookie' or k=='host' or k=='origin' or k=='referer' or k=='user-agent' or k=='content-type' then if type(v) ~='string' then error_rule = 'header 头部存在异常'..' >> '..k..' >> '..'不是字符串' write_log('post','header 头部存在异常'..k..'不为字符串') local check_html = [[header 头部存在异常
宝塔WAF提醒您,header 请求异常,拒绝访问
]] ngx.header.content_type = "textml;charset=utf8" ngx.say(check_html) ngx.exit(200) end end end end function is_check_header() is_check_headers=ngx.req.get_headers(2000) count=0 if type(is_check_headers)=='table' then for k,v in pairs(is_check_headers) do if type(v)=='table' then for k2,v2 in pairs(v) do count=count+1 end else count=count+1 end end end if count>800 then return lan_ip('post','header字段大于800 被系统拦截') end end function ThinkPHP_RCE5_0_23() if method == "POST" then ngx.req.read_body() data = ngx.req.get_post_args() if data==nil then return false end if data['_method'] and data['method'] and data['server[REQUEST_METHOD]'] then is_type='ThinkPHP攻击' lan_ip('post','拦截ThinkPHP 5.x RCE 攻击') end if data['_method'] and data['method'] and data['server[]'] and data['get[]'] then is_type='ThinkPHP攻击' lan_ip('post','拦截ThinkPHP 5.x RCE 攻击,并且被封锁IP') end if data['_method'] then if type(data['_method'])=='string' then if data['_method'] and ngx_match(data['_method'],'construct','ijo') then is_type='ThinkPHP攻击' lan_ip('post','拦截ThinkPHP 5.x RCE 攻击,并且被封锁IP') end end if type(data['_method'])=='table' then if not data['_method'] then return false end for _,_v2 in pairs(data['_method']) do if type(_v2)=='string' then if ngx_match(_v2,'construct','ijo') then is_type='ThinkPHP攻击' lan_ip('post','拦截ThinkPHP 5.x RCE 攻击,并且被封锁IP') end end end end end end return false end function ThinkPHP_3_log() if string.find(uri,'^/Application/.+log$') or string.find(uri,'^/Application/.+php$') or string.find(uri,'^/application/.+log$') or string.find(uri,'^/application/.+php$') then is_type='ThinkPHP攻击' lan_ip('args','拦截ThinkPHP 3.x 获取敏感信息操作,并且被封锁IP') end if string.find(uri,'^/Runtime/.+log$') or string.find(uri,'^/Runtime/.+php$') or string.find(uri,'^/runtime/.+php$') or string.find(uri,'^/runtime/.+log$')then is_type='ThinkPHP攻击' lan_ip('args','拦截ThinkPHP 3.x 获取敏感信息操作,并且被封锁IP') end return false end function error_transfer_encoding() if request_header['transfer-encoding'] == nil then return false end if request_header['transfer-encoding'] then is_type='GET参数' lan_ip('args','拦截 Transfer-Encoding 块请求,并且被封锁IP') return true else return false end end local function return_zhi() return require "zhi" end function zhizu_ua_chkec(ua) ua_list2=return_zhi() if ua_list2 ~= nil then for _,k in ipairs(ua_list2['types']) do if k['ua_key'] ~= nil then local fa=string.find(string.lower(tostring(ua)),string.lower(tostring(k['ua_key']))) if fa ~= nil then return tonumber(k['id']),k['host_key'] end end end end end function get_zhizu_json(name) if ngx.shared.spider:get(cpath..name..'reptile') then return ngx.shared.spider:get(cpath..name..'reptile') end data = read_file_body(cpath .. tostring(name) ..'.json') if not data then data={} end ngx.shared.spider:set(cpath..name..'reptile',data,10000) return data end function zhizu_chekc(name,ip) data=get_zhizu_json(name) local ok ,zhizhu_list_data = pcall(function() return json.decode(data) end) if not ok then return false end if ngx.shared.spider:get(name) then if ngx.shared.spider:get(ip) then return true end return false end ngx.shared.spider:set(name,'1',86400) for _,k in ipairs(zhizhu_list_data) do ngx.shared.spider:set(k,'1',86400) end if ngx.shared.spider:get(ip) then return true end return false end function reptile_entrance(ua,ip) if ngx.shared.spider:get(ip) then return 2 end if ngx.shared.spider:get(ip..'baidu') then return 2 end if ngx.re.match(ip,':','ijo') then return 1 end if not ip then return 1 end if not ua then return 1 end if ngx.re.match(ua,'curl','ijo') or ngx.re.match(ua,'java','ijo') or ngx.re.match(ua,'python','ijo') then return 1.1 end local reptile_id,get_ua_key22=zhizu_ua_chkec(ua) if not reptile_id then return 1.2 end if not get_ua_key22 then return 1.3 end if tonumber(reptile_id) == 3 then if zhizu_chekc(reptile_id,ip) then return 2 else return 34 end end if zhizu_chekc(reptile_id,ip) then return 2 else baidu_error_count=ngx.shared.spider:get(ip..'baidu_error') if not baidu_error_count then local ret=host_pachong(ip,reptile_id,get_ua_key22) if ret~=2 then baidu_error_count=ngx.shared.spider:set(ip..'baidu_error',1,86400) return ret end return ret elseif baidu_error_count>=1 then return 188 else local ret=host_pachong(ip,reptile_id,get_ua_key22) if ret~=2 then ngx.shared.spider:incr(ip..'baidu_error',1) return ret end return ret end end return 66 end function host_pachong(ip,id,ua_key) if not ip then return 33 end if not id then return 33 end if not ua_key then return 33 end local key_id=ngx.shared.spider:get(ip..'baidu') if key_id == nil then local r,err= resolver:new{ nameservers = {"8.8.8.8", {"114.114.114.114", 53} }, retrans = 5, timeout = 2000} if not r then return 1888 end local data11111=r:reverse_query(tostring(ip)) if not data11111 then return 33 end if type(data11111)~='table' then return 1888 end if data11111['errcode'] then return 1888 end if not data11111[1] then return 1888 end if not data11111[1]['ptrdname'] then return 1888 end local types=string.find(string.lower(tostring(data11111[1]['ptrdname'])),string.lower(tostring(ua_key))) if types~=nil then write_file2(cpath..'/zhizhu'..id..'.json',ip.."\n") ngx.shared.spider:set(ip..'baidu',1,86400) return 2 else return 35 end else return 2 end end function get_server_name_waf() local c_name = ngx.var.server_name local my_name = ngx.shared.free_waf:get(c_name) if my_name then return my_name end local tmp = read_file_body(cpath .. '/domains.json') if not tmp then return c_name end local domains = json.decode(tmp) for _,v in ipairs(domains) do for _,d_name in ipairs(v['domains']) do if c_name == d_name then ngx.shared.free_waf:set(c_name,v['name'],3600) return v['name'] end end end if c_name =='_' then c_name="未绑定域名" end return c_name end function run_btwaf() server_name = get_server_name_waf() if not config['open'] or not is_site_config('open') then return false end error_rule = nil request_header = ngx.req.get_headers(100000) method = ngx.req.get_method() ip = get_client_ip() ipn = arrip(ip) request_uri = ngx.var.request_uri uri = ngx.unescape_uri(ngx.var.uri) uri_request_args = ngx.req.get_uri_args(10000000) cycle = config['cc']['cycle'] endtime = config['cc']['endtime'] limit = config['cc']['limit'] retry = config['retry'] retry_time = config['retry_time'] retry_cycle = config['retry_cycle'] min_route() site_cc = is_site_config('cc') if site_config[server_name] and site_cc then cycle = site_config[server_name]['cc']['cycle'] endtime = site_config[server_name]['cc']['endtime'] limit = site_config[server_name]['cc']['limit'] end if site_config[server_name] then retry = site_config[server_name]['retry'] retry_time = site_config[server_name]['retry_time'] retry_cycle = site_config[server_name]['retry_cycle'] end --is_header_error() if ip_white() then return true end ip_black() is_check_header() if url_white() then return true end url_black() if ngx.shared.spider:get(ip) then args() post() post_data() post_data_chekc() return false end --if type(request_header['user-agent'])~='string' then return ngx.exit(404) end local reptile_id=reptile_entrance(request_header['user-agent'],ip) if reptile_id==2 then args() post() post_data() post_data_chekc() return false end drop() drop_abroad() cc() cc2() user_agent() url() args() cookie() ThinkPHP_RCE5_0_23() ThinkPHP_3_log() error_transfer_encoding() scan_black() post() post_data_chekc() if site_config[server_name] then php_path() url_path() url_ext() url_rule_ex() url_tell() post_data() end end