local public = {} local ffi = require("ffi") local ck = require "new_cookie" local ipmatcher =require "ipmatcher" local is_type = "" local error_rule = "" local server = require "websocket.server" -- cffi相关函数开始 ffi.cdef [[ typedef struct DIR DIR; struct dirent { uint64_t d_ino; int64_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[]; }; DIR *opendir(const char *name); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp); int mkdir(const char *path, int mode); typedef unsigned int uid_t; typedef unsigned int gid_t; int chown(const char *path, uid_t owner, gid_t group); int access(const char *pathname, int mode); struct passwd { char *pw_name; char *pw_passwd; uid_t pw_uid; gid_t pw_gid; // 其他字段省略,可以根据实际需要添加 }; struct passwd *getpwnam(const char *name); typedef long time_t; typedef struct timeval { time_t tv_sec; time_t tv_usec; } timeval; typedef struct in6_addr { union { uint8_t u6_addr8[16]; uint16_t u6_addr16[8]; uint32_t u6_addr32[4]; } in6_u; } in6_addr; typedef struct in_addr { uint32_t s_addr; }in_addr; int inet_pton(int af, const char *src, void *dst); int gettimeofday(struct timeval *tv, void *tz); uint32_t ntohl(uint32_t netlong); ]] public.fastjson_list={} public.fastjson_list["java.io.InputStream"]=true public.fastjson_list["java.net.InetAddress"]=true public.fastjson_list["java.net.Inet6Address"]=true public.fastjson_list["java.net.InetSocketAddress"]=true public.fastjson_list["java.net.URL"]=true public.fastjson_list["org.dom4j.io.SAXContentHandler"]=true public.fastjson_list["org.dom4j.io.XMLWriter"]=true public.fastjson_list["org.python.antlr.ParseException"]=true public.fastjson_list["java.lang.Character"]=true public.fastjson_list["org.apache.xml.dtm.DTMConfigurationException"]=true public.fastjson_list["javax.xml.transform.SourceLocator"]=true public.fastjson_list["org.apache.xpath.objects.XNodeSetForDOM"]=true public.fastjson_list["org.apache.xpath.NodeSet"]=true public.fastjson_list["ognl.OgnlException"]=true public.fastjson_list["ognl.Evaluation"]=true public.fastjson_list["ognl.ASTMethod"]=true public.fastjson_list["java.net.Inet4Address"]=true public.fastjson_list["org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit"]=true public.fastjson_list["org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit"]=true public.fastjson_list["org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"]=true public.fastjson_list["java.util.Locale"]=true public.fastjson_list["org.codehaus.groovy.tools.javac.JavaStubCompilationUnit"]=true public.fastjson_list["org.codehaus.groovy.control.CompilationFailedException"]=true public.fastjson_list["java.lang.Exception"]=true public.fastjson_list["org.codehaus.groovy.control.ProcessingUnit"]=true public.fastjson_list["org.postgresql.jdbc.PgConnection"]=true public.fastjson_list["com.sun.rowset.JdbcRowSetImpl"]=true public.fastjson_list["java.lang.Class"]=true public.fastjson_list["com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"]=true public.fastjson_list["org.apache.tomcat.dbcp.dbcp.BasicDataSource"]=true public.fastjson_list["com.sun.org.apache.bcel.internal.util.ClassLoader"]=true public.fastjson_list["java.lang.AutoCloseable"]=true public.fastjson_list["sun.rmi.server.MarshalOutputStream"]=true public.fastjson_list["java.util.zip.InflaterOutputStream"]=true public.fastjson_list["java.io.FileOutputStream"]=true public.fastjson_list["com.alibaba.fastjson.JSONObject"]=true public.fastjson_list["org.apache.commons.io.input.ReaderInputStream"]=true public.fastjson_list["org.apache.commons.io.input.CharSequenceReader"]=true public.fastjson_list["java.lang.String"]=true public.fastjson_list["org.apache.commons.io.output.WriterOutputStream"]=true public.fastjson_list["org.apache.commons.io.output.FileWriterWithEncoding"]=true public.fastjson_list["org.apache.commons.io.input.XmlStreamReader"]=true public.fastjson_list["org.apache.commons.io.input.TeeInputStream"]=true public.fastjson_list["java.util.Currency"]=true public.fastjson_list["org.apache.commons.io.input.CharSequenceInputStream"]=true public.fastjson_list["org.apache.commons.io.input.BOMInputStream"]=true public.fastjson_list["jdk.nashorn.api.scripting.URLReader"]=true public.fastjson_list["org.apache.commons.io.ByteOrderMark"]=true public.fastjson_list["org.apache.commons.codec.binary.Base64InputStream"]=true public.fastjson_list["org.eclipse.core.internal.localstore.SafeFileOutputStream"]=true public.fastjson_list["com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"]=true public.fastjson_list["com.mysql.jdbc.ReplicationConnection"]=true public.fastjson_list["com.mysql.jdbc.JDBC4Connection"]=true public.fastjson_list["com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection"]=true public.fastjson_list["com.mysql.cj.jdbc.ha.ReplicationMySQLConnection"]=true public.fastjson_list["com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy"]=true public.fastjson_list["com.mysql.cj.conf.url.ReplicationConnectionUrl"]=true public.jackson_list={} public.jackson_list['ch.qos.logback.core.db.DriverManagerConnectionSource']=true public.jackson_list['com.zaxxer.hikari.HikariConfig']=true public.jackson_list['org.apache.xbean.propertyeditor.JndiConverter']=true public.jackson_list['br.com.anteros.dbcp.AnterosDBCPConfig']=true public.jackson_list['com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl']=true public.jackson_list['org.springframework.context.support.ClassPathXmlApplicationContext']=true public.jackson_list['org.jdom2.transform.XSLTransformer']=true public.jackson_list['org.springframework.context.support.FileSystemXmlApplicationContext']=true public.jackson_list['com.newrelic.agent.deps.ch.qos.logback.core.db.JNDIConnectionSource']=true public.jackson_list['oadd.org.apache.xalan.lib.sql.JNDIConnectionPool']=true public.jackson_list['com.sun.org.apache.xalan.internal.lib.sql.JNDIConnectionPool']=true public.jackson_list['org.jsecurity.realm.jndi.JndiRealmFactory']=true public.jackson_list['br.com.anteros.dbcp.AnterosDBCPDataSource']=true public.jackson_list['com.pastdev.httpcomponents.configuration.JndiConfiguration']=true public.jackson_list['oadd.org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS']=true public.jackson_list['org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS']=true public.jackson_list['org.apache.tomcat.dbcp.dbcp.cpdsadapter.DriverAdapterCPDS']=true public.jackson_list['org.apache.tomcat.dbcp.dbcp2.cpdsadapter.DriverAdapterCPDS']=true public.jackson_list['org.apache.tomcat.dbcp.dbcp.datasources.PerUserPoolDataSource']=true public.jackson_list['com.newrelic.agent.deps.ch.qos.logback.core.db.DriverManagerConnectionSource']=true --判断文件是否存在 function public.os_file_exists(filename) -- access 函数返回0表示成功,文件存在 return ffi.C.access(filename, 1) == 0 end --获取CPU的核心数量 function public.get_num_procs() local file = io.open("/proc/cpuinfo", "r") if not file then return 1 end local coreCount = 0 for line in file:lines() do local processor = line:match("^processor[%s]+:%s+(%d+)") if processor then coreCount = coreCount + 1 end end if coreCount==0 then return 1 end file:close() return coreCount end -- 返回MB 内存大小 function public.get_memory_size() local file = io.open("/proc/meminfo", "r") if not file then return 1024 end local memTotal = 1024000 for line in file:lines() do local key, value = line:match("^(%w+):[%s]+(%d+)%s+kB$") if key == "MemTotal" then memTotal = tonumber(value) break end end file:close() return memTotal/1024 end --获取毫秒时差 function public.getMilliseconds() local tv = ffi.new("struct timeval") ffi.C.gettimeofday(tv, nil) return tonumber(tv.tv_sec) * 1000 + tonumber(tv.tv_usec) / 1000 end function public.getMicroseconds() local tv = ffi.new("struct timeval") ffi.C.gettimeofday(tv, nil) return tonumber(tv.tv_sec) * 1000000 + tonumber(tv.tv_usec) end --计算某个函数的毫秒数 function public.measureTime(func,name) if ngx.var.remote_addr=="127.0.0.1" then return false end local startTime = public.getMicroseconds() for i=1,1000 do func() end local endTime = public.getMicroseconds() local elapsedTime = endTime - startTime public.logs("函数:"..name.." 执行1000次的时间为: "..tostring(elapsedTime).." 微秒") return elapsedTime end -- 设置文件或目录的所有者和组 -- @param path 文件或目录路径 -- @param owner 用户ID -- @param group 组ID -- @return 是否成功设置所有者和组 function public.chown(path, owner, group) local result = ffi.C.chown(path, owner, group) if result == 0 then return true else return false end end --判断文件是否存在 --@param path 文件路径 --@return true or false function public.file_exists(path) local file = io.open(path, "r") if file then io.close(file) return true end return false end -- 根据用户名获取用户的UID -- @param username 用户名 -- @return 用户的UID,如果用户不存在则返回nil function public.getUIDByUsername(username) local passwdStruct = ffi.C.getpwnam(username) if passwdStruct ~= nil then local uid = tonumber(passwdStruct.pw_uid) return uid else return nil end end -- 新建目录 -- @param path 目录路径 -- @return true or false function public.mkdir(path) local result = ffi.C.mkdir(path, tonumber("755", 8)) -- 755权限,表示读写执行权限 if result == 0 then return true else return false end end --获取文件夹下的所有文件 --@param path 文件夹路径 --@return 文件列表 function public.isdir(path) local dir_list = {} -- Open a directory local dir = ffi.C.opendir(path) if dir == nil then return false end ffi.C.closedir(dir) return true end -- cffi相关函数结束 --获取文件夹下的所有文件 --@param path 文件夹路径 --@return 文件列表 function public.listdir(path) local dir_list = {} local dir = ffi.C.opendir(path) if dir == nil then return dir_list end local entry = ffi.C.readdir(dir) while entry ~= nil do local entryName = ffi.string(entry.d_name) if entryName ~= "." and entryName ~= ".." then table.insert(dir_list, entryName) end entry = ffi.C.readdir(dir) end ffi.C.closedir(dir) return dir_list end -- 遍历/www/server 目录判断是否存在monitor 目录 function public.is_monitor() -- 判断缓存中是否存在 if ngx.shared.btwaf:get("is_monitor") and ngx.shared.btwaf:get("is_monitor")=="1" then return true end local server_list = public.listdir("/www/server") for _, v in ipairs(server_list) do if v == "monitor" then ngx.shared.btwaf:set("is_monitor", "1") return true end end ngx.shared.btwaf:set("is_monitor", "0") return false end -- 追加文件内容 -- @param filename 文件名 -- @param body 内容 -- @return true or false function public.append_file(filename, body) local fp = io.open(filename, "a+") if fp == nil then return nil end fp:write(body) fp:flush() fp:close() return true end -- 日志写入 -- @param ... 内容 -- @return true or false function public.logs(...) local data = "[" .. os.date("%Y-%m-%d %H:%M:%S") .. "]" for _, v in ipairs({...}) do if type(v) == "table" then -- 如果是table,尝试格式化输出 Json.encode_sparse_array(true, 1) data = data .. "\n" .. tostring(v) .. ": \n" .. public.PrintTable(v) else data = data .. " " .. tostring(v) end end local log_file = "/www/wwwlogs/btwaf_debug.log" public.append_file(log_file, data .. "\n") end -- 日志写入 -- @param ... 内容 -- @return true or false function public.logs_file(log_file,...) local data = "[" .. os.date("%Y-%m-%d %H:%M:%S") .. "]" for _, v in ipairs({...}) do if type(v) == "table" then -- 如果是table,尝试格式化输出 Json.encode_sparse_array(true, 1) data = data .. "\n" .. tostring(v) .. ": \n" .. public.PrintTable(v) else data = data .. " " .. tostring(v) end end public.append_file(log_file, data .. "\n") end --ip转为整数 --@param ip ip地址 --@return 整数 function public.ip2long(ip) local num = 0 if ip and type(ip) == "string" then local o1, o2, o3, o4 = ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") if o1 == nil or o2 == nil or o3 == nil or o4 == nil then return 0 end num = 2 ^ 24 * o1 + 2 ^ 16 * o2 + 2 ^ 8 * o3 + o4 end return num end function public.c_ip2long(ip) local ipv4_addr = ffi.new("in_addr") local result = ffi.C.inet_pton(2, ip, ipv4_addr) -- 2 代表 AF_INET (IPv4) if result == 1 then return ffi.C.ntohl(ipv4_addr.s_addr) -- 使用 ntohl 函数转换字节序 else return 0 end end -- 整数转为IP function public.long2ip(long) local floorList = {} local yushu = long for i = 3, 0, -1 do local res = math.floor(yushu / (256 ^ i)) table.insert(floorList, tonumber(res)) yushu = yushu - res * 256 ^ i end return table.concat(floorList, ".") end -- 验证是否为127.0.0.1 -- @param ip ip地址 -- @return true or false function public.is_localhost(ip) if not ip then return false end if ip == "unknown" then return false end if public.find_str(ip, ":") then return false end local ip_nmber = public.ip2long(ip) if ip_nmber == 0 then return false end if ip_nmber >= 2130706433 and ip_nmber <= 2130706687 then return true end return false end --判断是否是内网地址 --@param ip ip地址 --@return true or false function public.is_internal_ip(ips) if not ips then return false end if ips == "unknown" then return false end if public.find_str(ips, ":") then return false end local ip_nmber = public.ip2long(ips) if ip_nmber == 0 then return false end -- 192.168.0.1 到192.168.255.255 if ip_nmber >= 3232235520 and ip_nmber <= 3232301055 then return true end -- 172.16.0.0 到172.16.255.255 if ip_nmber >= 2886729728 and ip_nmber <= 2887843839 then return true end -- 10.0.0.1 到 10.255.255.255 if ip_nmber >= 167772161 and ip_nmber <= 184549375 then return true end return false end -- 返回json格式数据 -- @param status 状态码 -- @param msg 返回信息 function public.return_message(status, msg) ngx.header.content_type = "application/json;" ngx.header.Cache_Control = "no-store" ngx.status = status ngx.say(Json.encode(msg)) ngx.exit(status) end -- 返回html格式数据 -- @param status 状态码 -- @param html 返回html function public.return_html(status, html) ngx.header.content_type = "text/html" ngx.header.Cache_Control = "no-store" ngx.status = status if ngx.ctx.header_btwaf==nil then ngx.say(html) end ngx.exit(status) end --统一返回格式 --@param status 状态码 --@param msg 返回信息 function public.get_return_state(status, msg) local result = {} result["status"] = status result["msg"] = msg return result end function public.read_file_body(filename) if filename==nil then return nil end local fp = io.open(filename,'r') if fp == nil then return nil end local fbody = fp:read("*a") fp:close() if fbody == '' then return nil end return fbody end function public.read_file_body_rb(filename) if filename==nil then return nil end local fp = io.open(filename,'rb') if fp == nil then return nil end local fbody = fp:read("*a") fp:close() if fbody == '' then return nil end return fbody end function public.read_file(name) local fbody = public.read_file_body(BTWAF_RUN_PATH.."/rule/" .. name .. '.json') if fbody == nil then return {} end --判断Json格式是否正确 local status, result = pcall(Json.decode, fbody) if status then return result end return {} end function public.re_png(filename) local fp = io.open(filename,'rb') if fp == nil then return nil end local fbody = fp:read("*a") fp:close() if fbody == '' then return nil end return fbody end function public.write_file(filename,body) local fp = io.open(filename,'w') if fp == nil then return nil end fp:write(body) fp:flush() fp:close() return true end -- 搜索字符串(非匹配模式) -- @param str 字符串 -- @param find_str 查找的字符串 -- @return bool function public.find_str(str, find_str) if not str or not find_str then return false end local s, e = string.find(str, find_str, 1, true) if s and e then return true end return false end -- 验证是否为IPV4的地址 -- @param ip ip地址 -- @return true or false function public.is_ipv4(ip) local pattern = "^(%d+)%.(%d+)%.(%d+)%.(%d+)$" local a, b, c, d = ip:match(pattern) if not (a and b and c and d) then return false end if tonumber(a) > 255 or tonumber(b) > 255 or tonumber(c) > 255 or tonumber(d) > 255 then return false end return true end --验证是否为IPV6的地址 --@param ip ip地址 --@return true or false function public.is_ipv6(ip) local ipv6_addr = ffi.new("in6_addr") local result = ffi.C.inet_pton(10, ip, ipv6_addr) return result == 1 end -- 验证是否为IPV4的地址 -- @param ip ip地址 -- @return true or false function public.is_in_addr_ipv4(ip) local ipv4_addr = ffi.new("in_addr") local result = ffi.C.inet_pton(2, ip, ipv4_addr) return result == 1 end --判断是否为IP格式 function public.is_ip(ip) -- 判断长度 if #ip<10 then return false end if public.is_in_addr_ipv4(ip) then return true end if public.is_ipv6(ip) then return true end return false end --判断是否为IP格式 function public.is_ip_ver(ip) if public.is_in_addr_ipv4(ip) then return "v4" end if public.is_ipv6(ip) then return "v6" end return "no" end --判断是否为IP格式 function public.is_ipaddr(client_ip) if public.find_str(client_ip, ":") then return true end if public.is_ipv4(client_ip) then return true end return false end -- 字符串分割 -- @param string 字符串 -- @param reps 分割符 -- @return 分割后的字符串列表 function public.split(string, reps) if string == nil or string == "" or reps == nil then return nil end local result = {} for match in (string .. reps):gmatch("(.-)" .. reps) do table.insert(result, match) end return result end function public.url_split(url) if not url or type(url) ~= "string" then return {} end local result = {} local special_chars = {["'"]=true, ['"']=true, ["<"]=true, [">"]=true, ["%"]=true,["."]=true,[";"]=true} local start = 2 -- 跳过第一个'/' local len = #url local i = start local temp = "" local found_special = false while i <= len do local c = url:sub(i, i) if not found_special then if c == "/" then if #temp > 0 then table.insert(result, temp) temp = "" end elseif special_chars[c] then found_special = true temp = url:sub(i) table.insert(result, temp) break else temp = temp .. c end else temp = temp .. c end i = i + 1 end if not found_special and #temp > 0 then table.insert(result, temp) end return result end -- 取ngx.var.request_uri 不带参数的值 function public.get_request_uri() --返回的是字符串 local uri = ngx.var.request_uri if uri==ngx.ctx.uri then return ngx.var.request_uri end if uri==nil then return "/" end uri=uri:gsub('//+', '/') if uri==ngx.var.uri then return uri end --通过byte for i = 1, #uri do local byte = uri:byte(i) if byte == 63 then return uri:sub(1, i - 1) end end return uri end -- 取ngx.var.request_uri 不带参数的值 function public.get_request_uri_static(uri) local static={"css","js","png","gif","ico","jpg","jpeg","bmp","flush","swf","pdf","rar","zip","doc","docx","xlsx","gz","7z","tar","mp3","mp4","bz2"} ngx.ctx.suffix="" for i = #uri, #uri-6,-1 do local byte = uri:byte(i) if byte == 46 then ngx.ctx.suffix=uri:sub(i+1,#uri) break end end if static[ngx.ctx.suffix] then return false end return true end -- 字符串分割 【适用于复杂的字符粗分割-性能差】 -- @param string 字符串 -- @param reps 分割符 -- @return 分割后的字符串列表 function public.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 public.get_server_name_waf() local c_name = ngx.var.server_name local my_name = ngx.shared.btwaf:get(c_name) if my_name then return my_name end if c_name =='_' then c_name="未绑定域名" end local tmp = public.read_file_body(BTWAF_RUN_PATH .. '/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.btwaf:set(c_name,v['name'],3600) return v['name'] end end end if c_name =='127.0.0.1' then ngx.shared.btwaf:set(c_name,"127.0.0.1",3600) end return c_name end function public.arrlen(arr) if not arr then return 0 end local count = 0 for _,v in pairs(arr) do count = count + 1 end return count end -- 只需要判断是否存在 长度是否大于0 function public.is_len(arr) if not arr then return 0 end local count = 0 for _,v in pairs(arr) do count = count + 1 break end return count end function public.count_sieze(data) local count=0 if type(data)~="table" then return count end for _,v in pairs(data) do count=count+1 end return count end --获取规则列表 --@param rule_info 规则信息 --@return 规则列表 function public.select_rule(rules) if not rules then return {} end local new_rules = {} for _, v in ipairs(rules) do if v[1] == 1 then table.insert(new_rules, v[2]) end end return new_rules end function public.select_rule_args(rules) if not rules then return {} end local new_rules = {} for i,v in ipairs(rules) do if v[1] == 1 then local new_rules2 = {} table.insert(new_rules2,v[2]) table.insert(new_rules2,v[3]) if v[5]~=nil then table.insert(new_rules2,v[5]) else table.insert(new_rules2,100) end table.insert(new_rules,new_rules2) end end return new_rules end function public.is_site_config(cname) if Site_config[ngx.ctx.server_name] ~= nil then if cname == 'cc' then return Site_config[ngx.ctx.server_name][cname]['open'] else return Site_config[ngx.ctx.server_name][cname] end end return true end --获取表长度 --@param data 表 --@return 长度 function public.len(data) local count = 0 if type(data) ~= "table" then return count end for k, v in pairs(data) do count = count + 1 end return count end function public.ip_count_check() if not ngx.ctx.site_cc then return false end if not ngx.ctx.cc_ip_max_status then return false end if ngx.ctx.cc_ip_max==nil then return false end if ngx.shared.spider:get(ngx.ctx.ip) then return false end local ip=ngx.ctx.ip local token=ip.."|"..ngx.ctx.server_name local ipcount=ngx.shared.spider:get(token) if not ipcount then return false end if BTWAF_RULES.load_spider:match(ngx.ctx.ip) then ngx.shared.spider:set(ngx.ctx.ip, "1",18000) return true end local cc_ip_max=5000 if ngx.ctx.cc_ip_max>5000 then cc_ip_max=ngx.ctx.cc_ip_max end local tmp_ip_count=0 local tmp_traffic=0 local token_data=ngx.shared.ip:get(token) if token_data then token_data=Public.split(token_data,"|") if token_data~=nil then if Public.arrlen(token_data)>2 then local ip_tmp_count_tmp=tonumber(token_data[1]) if ip_tmp_count_tmp then tmp_ip_count=ip_tmp_count_tmp end local tmp_traffic_tmp=tonumber(token_data[3]) if tmp_traffic_tmp then tmp_traffic=tmp_traffic_tmp end end end end ipcount=ipcount+tmp_ip_count if ipcount>cc_ip_max then local safe_count,_ = ngx.shared.drop_sum:get(ip) if not safe_count then ngx.shared.drop_sum:set(ip,2,86400) safe_count = 1 else ngx.shared.drop_sum:incr(ip,1) end local lock_time = (ngx.ctx.endtime * safe_count) if lock_time > 86400 then lock_time = 86400 end ngx.shared.drop_ip:set(ip,ngx.ctx.retry+1,lock_time) IpInfo.bt_ip_filter(ip,lock_time) ngx.ctx.is_type='cc' local msg="单IP防护"..'一天内内累计超过'..cc_ip_max..'次请求,封锁' .. lock_time .. '秒' msg=msg.." (包括所有静态资源) 如需要调整阈值请到CC防御中设置单IP防御" IpInfo.write_log('cc',msg) IpInfo.write_drop_ip('cc',lock_time,msg) end if ngx.ctx.cc_ip_traffic~=nil and type(ngx.ctx.cc_ip_traffic)=='number' then local traffic=ngx.shared.spider:get(token.."|traffic") if traffic then traffic=tonumber(traffic) traffic=traffic+tmp_traffic if ngx.ctx.cc_ip_traffic<50 then ngx.ctx.cc_ip_traffic=50 end if traffic/1024/1024 >ngx.ctx.cc_ip_traffic then local safe_count,_ = ngx.shared.drop_sum:get(ip) if not safe_count then ngx.shared.drop_sum:set(ip,2,86400) safe_count = 1 else ngx.shared.drop_sum:incr(ip,1) end local lock_time = (ngx.ctx.endtime * safe_count) if lock_time > 86400 then lock_time = 86400 end ngx.shared.drop_ip:set(ip,ngx.ctx.retry+1,lock_time) IpInfo.bt_ip_filter(ip,lock_time) ngx.ctx.is_type='cc' local msg="单IP防护"..'当天内流量累计超过'..ngx.ctx.cc_ip_traffic..'M,封锁' .. lock_time .. '秒' msg=msg.." (包括所有静态资源) 如需要调整阈值请到CC防御中设置单IP防御" IpInfo.write_log('cc',msg) IpInfo.write_drop_ip('cc',lock_time,msg) end end end end function public.is_static() if ngx.ctx.method=="GET" then if Config['static_cc']~=nil and Config['static_cc']==true then return false end if ngx.shared.btwaf_data:get(ngx.md5(ngx.ctx.url_split)) then if ngx.ctx.cc_ip_max_status==true then public.ip_count_check() end return true end if ngx.re.find(ngx.ctx.url_split,"\\.(js|css|gif|jpg|jpeg|png|bmp|swf|ico|woff|woff2|webp|mp4|mp3)$","isjo") and not ngx.re.find(ngx.ctx.url_split, "%",'jo') then ngx.shared.btwaf_data:set(ngx.md5(ngx.ctx.url_split), 1, 1200) return true end end if ngx.ctx.cc_ip_max_status==true then public.ip_count_check() end return false end function public.return_html_body(title, t1, li, l2) local html_data = public.read_file_body(BTWAF_RUN_PATH .. "/html/default_return.html") local check_html, _ = string.gsub(html_data, "{{title}}", title) check_html, _ = string.gsub(check_html, "{{t1}}", t1) check_html, _ = string.gsub(check_html, "{{li}}", li) check_html, _ = string.gsub(check_html, "{{l2}}", l2) return check_html end function public.return_html_data(title, t1, li, l2) local html_data = public.read_file_body(BTWAF_RUN_PATH .. "/html/default_return.html") ngx.status = 403 ngx.header.content_type = "text/html;charset=utf8" ngx.header.Cache_Control = "no-store" local check_html, _ = string.gsub(html_data, "{{title}}", title) check_html, _ = string.gsub(check_html, "{{t1}}", t1) check_html, _ = string.gsub(check_html, "{{li}}", li) check_html, _ = string.gsub(check_html, "{{l2}}", l2) ngx.say(check_html) ngx.exit(403) end function public.is_ssl() if(ngx.re.match(ngx.ctx.request_uri,'^/.well-known/pki-validation/',"jo")) then return true end if(ngx.re.match(ngx.ctx.request_uri,'^/.well-known/acme-challenge/',"jo")) then return true end end -- Cookie转为table -- @return cookie table function public.getcookie() local cookie, _ = ck:new() if not cookie then return nil end if cookie:get_cookie_size()==0 then return nil end return cookie end function public.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 = public.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 public.de_dict (l_key,l_data) if type(l_data) ~= "table" then return l_data end if public.arrlen(l_data) == 0 then return l_data end if not l_data then return false end local r_data = {} if public.arrlen(l_data) >= 8000 then ngx.ctx.is_type='参数过多' IpInfo.lan_ip('sql','非法请求') return true end for li,lv in pairs(l_data) do r_data[l_key..tostring(li)] = lv end return r_data end -- 获取今天开始时间戳 -- @return timestamp int 时间戳 function public.get_today_start_time_logs() local date_time = os.date("*t") date_time.hour = 0 date_time.min = 0 date_time.sec = 0 return os.time(date_time) end -- 获取今天结束时间戳 -- @return timestamp int 时间戳 function public.get_today_end_time_logs() return public.get_today_start_time_logs() + 86400 end -- 获取今天开始时间戳 -- @return timestamp int 时间戳 function public.get_today_start_time() local start_time=ngx.shared.spider:get("get_today_start_time") if start_time then return public.int(start_time) end local date_time = os.date("*t") date_time.hour = 0 date_time.min = 0 date_time.sec = 0 local start_time=os.time(date_time) if tonumber(Hour)<23 then ngx.shared.spider:set("get_today_start_time",start_time,1800) elseif tonumber(Hour)==23 and tonumber(Minute)<50 then ngx.shared.spider:set("get_today_start_time",start_time,60) else ngx.shared.spider:set("get_today_start_time",start_time,5) end return start_time end -- 获取今天结束时间戳 -- @return timestamp int 时间戳 function public.get_today_end_time() return public.get_today_start_time() + 86400 end -- 指定统计值+1 -- @param string server_name 网站名称 -- @param string key 缓存key -- @param int num 增加的值 -- @param int expire 过期时间(秒) -- @return void function public.logs_incr(server_name, key, num, expire) if not server_name or not key or not num then return end key = server_name .. "_" .. key local val = ngx.shared.uri_total:get(key) if val == nil then ngx.shared.uri_total:set(key, 0, expire) end ngx.shared.uri_total:incr(key, num) --获取统计值 end -- 指定统计值+1 -- @param string server_name 网站名称 -- @param string key 缓存key -- @param int num 增加的值 -- @param int expire 过期时间(秒) -- @return void function public.logs_get(server_name, key) if not server_name or not key then return end key = server_name .. "_" .. key local val = ngx.shared.uri_total:get(key) if val == nil then val = 0 end return val end -- 获取缓存 -- @param string key -- @return int function public.get_cache(server_name,key) if not key then return end local skey = server_name .. '_status_' .. key local res = ngx.shared.uri_total:get(skey) if res == nil then res = 0 end return res end -- 获取锁 -- @param string key 锁的key -- @return bool function public.is_lock(key) -- 获取锁 local lock_key = "lock_" .. key return ngx.shared.uri_total:add(lock_key, 1, 0.5) end -- 获取锁 -- @param string key 锁的key -- @return bool function public.is_lock_end(key,expire) -- 获取锁 local lock_key = "lock_" .. key return ngx.shared.uri_total:add(lock_key, 1, expire) end -- 获取前1分钟 -- @return string 分钟数 function public.get_pre_minute() local time = os.time() local tmp_str = os.date("%Y-%m-%d %H:%M", time - 60) local tmp_arr = public.split(tmp_str, " ") local day = tmp_arr[1] local tmp_arr2 = public.split(tmp_arr[2], ":") local hour = tmp_arr2[1] local minute = tmp_arr2[2] return day, tonumber(hour), tonumber(minute) end function public.insert_request_total() ngx.sleep(1) local lock_key = 'public.insert_request_total' local lock = public.is_lock_end(lock_key,40) if not lock then return end Database.ReportInsert() end -- 取整数 -- @param num number 要处理的数值 -- @return number 处理后的数值 function public.int(num) if num ~= num then return 0 end return math.floor(num) end function public.get_html(ip,user_agent,server_name,today) local token = ngx.md5(ip..user_agent.."browser"..server_name..today) local count,_ = ngx.shared.btwaf:get(token) if count then local retry=5 if Config['retry']>5 then retry=Config['retry'] end if count > retry then local safe_count,_ = ngx.shared.drop_sum:get(ip) if not safe_count then ngx.shared.drop_sum:set(ip,1,86400) safe_count = 1 else ngx.shared.drop_sum:incr(ip,1) end local lock_time = (Config['retry_time'] * safe_count) if lock_time > 86400 then lock_time = 86400 end ngx.shared.drop_ip:set(ip,retry+1,lock_time) ngx.ctx.is_type='cc' ngx.shared.btwaf:delete(token) IpInfo.lan_ip('cc','攻击浏览器验证') else ngx.shared.btwaf:incr(token,1) end else ngx.shared.btwaf:set(token,1,Config['retry_cycle']) end local jsbody= string.format([[