├── README.md ├── captcha ├── getImg.php └── t1.ttf ├── config.lua ├── guard.lua ├── html ├── captcha.html ├── captcha.old.html ├── reCatchaPage.html └── reCatchaPage.old.html ├── init.lua ├── runtime.lua └── url-protect ├── 302.txt ├── black_ip_list.txt ├── cookie.txt ├── js.txt ├── limit.txt └── white_ip_list.txt /README.md: -------------------------------------------------------------------------------- 1 | # http-guard 2 | 3 | HttpGuard是基于openresty,以lua脚本语言开发的防cc攻击软件。而openresty是集成了高性能web服务器Nginx,以及一系列的Nginx模块,这其中最重要的,也是我们主要用到的nginx lua模块。HttpGuard基于nginx lua开发,继承了nginx高并发,高性能的特点,可以以非常小的性能损耗来防范大规模的cc攻击。 4 | 5 | 下面介绍HttpGuard防cc的一些特性: 6 | 7 | 1. 限制单个IP或者UA在一定时间内的请求次数 8 | 2. 向访客发送302转向响应头来识别恶意用户,并阻止其再次访问 9 | 3. 向访客发送带有跳转功能的js代码来识别恶意用户,并阻止其再次访问 10 | 4. 向访客发送cookie来识别恶意用户,并阻止其再次访问 11 | 5. 支持向访客发送带有验证码的页面,来进一步识别,以免误伤 12 | 6. 支持直接断开恶意访客的连接 13 | 7. 支持结合iptables来阻止恶意访客再次连接 14 | 8. 支持白名单/黑名单功能 15 | 9. 支持根据统计特定端口的连接数来自动开启或关闭防cc模式 16 | 17 | ## 部署HttpGuard 18 | ### 安装openresty或者nginx lua 19 | 20 | 按照openresty官网手动安装[http://openresty.com](http://openresty.com) 21 | 22 | ### 安装HttpGuard 23 | 24 | 假设我们把HttpGuard安装到/data/www/waf/,当然你可以选择安装在任意目录。 25 | 26 | ``` 27 | cd /data/www 28 | wget --no-check-certificate https://github.com/wenjun1055/HttpGuard/archive/master.zip 29 | unzip master.zip 30 | mv HttpGuard-master waf 31 | ``` 32 | 33 | ### 生成验证码图片 34 | 35 | 为了支持验证码识别用户,我们需要先生成验证码图片。生成验证码图片需要系统安装有php,以及php-gd模块。 36 | 用以下命令执行getImg.php文件生成验证码 37 | 38 | ``` 39 | cd /data/www/waf/captcha/ 40 | /usr/local/php/bin/php getImg.php 41 | ``` 42 | 43 | 大概要生成一万个图片,可能需要花几分钟的时间。 44 | 45 | ### 修改nginx.conf配置文件 46 | 47 | 向http区块输入如下代码: 48 | 49 | ``` 50 | lua_package_path "/data/www/waf/?.lua"; 51 | lua_shared_dict guard_dict 100m; 52 | lua_shared_dict dict_captcha 70m; 53 | init_by_lua_file '/data/www/waf/init.lua'; 54 | access_by_lua_file '/data/www/waf/runtime.lua'; 55 | lua_max_running_timers 1; 56 | ``` 57 | 58 | ### 配置HttpGuard 59 | 60 | 详细配置说明在[config.lua](https://github.com/wenjun1055/HttpGuard/blob/master/guard.lua)中,请根据需求进行配置 -------------------------------------------------------------------------------- /captcha/getImg.php: -------------------------------------------------------------------------------- 1 | =0 ) { 33 | imagesetpixel ($distortion_im, (int)($i+10+sin($j/$im_y*2*M_PI-M_PI*0.1)*4) , $j , $rgb); 34 | } 35 | } 36 | } 37 | //加入干扰象素; 38 | $count = 160;//干扰像素的数量 39 | for($i=0; $i<$count; $i++){ 40 | $randcolor = ImageColorallocate($distortion_im,mt_rand(0,255),mt_rand(0,255),mt_rand(0,255)); 41 | imagesetpixel($distortion_im, mt_rand()%$im_x , mt_rand()%$im_y , $randcolor); 42 | } 43 | 44 | $rand = mt_rand(5,30); 45 | $rand1 = mt_rand(15,25); 46 | $rand2 = mt_rand(5,10); 47 | for ($yy=$rand; $yy<=+$rand+2; $yy++){ 48 | for ($px=-80;$px<=80;$px=$px+0.1) 49 | { 50 | $x=$px/$rand1; 51 | if ($x!=0) 52 | { 53 | $y=sin($x); 54 | } 55 | $py=$y*$rand2; 56 | 57 | imagesetpixel($distortion_im, $px+80, $py+$yy, $text_c); 58 | } 59 | } 60 | 61 | //设置文件头; 62 | //Header("Content-type: image/JPEG"); 63 | 64 | //以PNG格式将图像输出到浏览器或文件; 65 | $fileName = $text . ".png"; 66 | ImagePNG($distortion_im,$fileName); 67 | 68 | //销毁一图像,释放与image关联的内存; 69 | ImageDestroy($distortion_im); 70 | ImageDestroy($im); 71 | } 72 | 73 | function make_rand($length="32"){//验证码文字生成函数 74 | $str="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; 75 | $result=""; 76 | for($i=0;$i<$length;$i++){ 77 | $num[$i]=rand(0,35); 78 | $result.=$str[$num[$i]]; 79 | } 80 | return $result; 81 | } 82 | 83 | 84 | //输出调用 85 | for ($i=1;$i<=10200;$i++) { 86 | $checkcode = make_rand(4); 87 | getAuthImage($checkcode); 88 | } 89 | 90 | ?> 91 | 92 | -------------------------------------------------------------------------------- /captcha/t1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenjun1055/HttpGuard/c28a0f225551866d0d36c3fcbc987caaad33429c/captcha/t1.ttf -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | -- http-guard安装目录,修改为实际安装到的目录。 2 | baseDir = '/home/http-guard/' 3 | 4 | local Config = { 5 | -- key是否动态生成,可选static,dynamic,如果选dynamic,下面所有的keySecret不需要更改,如果选static,修改手动修改下面的keySecret 6 | keyDefine = "dynamic", 7 | 8 | -- 被动防御,限制UA请求模块。根据在一定时间内统计到的单个UA请求次数作限制(专门针对火车头采集工具) 9 | -- state : 为此模块的状态,表示开启或关闭,可选值为On或Off; 10 | -- maxReqs,amongTime : 在amongTime秒内允许请求的最大次数maxReqs,如默认的是在10s内最大允许请求50次。 11 | limitUaModules = { state = "On" , maxReqs = 5 , amongTime = 300}, 12 | 13 | -- 被动防御,限制请求模块。根据在一定时间内统计到的请求次数作限制,建议始终开启 14 | -- state : 为此模块的状态,表示开启或关闭,可选值为On或Off; 15 | -- maxReqs,amongTime : 在amongTime秒内允许请求的最大次数maxReqs,如默认的是在10s内最大允许请求50次。 16 | -- urlProtect : 指定限制请求次数的url正则表达式文件,默认值为\.php$,表示只限制php的请求(当然,当urlMatchMode = "uri"时,此正则才能起作用) 17 | limitReqModules = { state = "On" , maxReqs = 5 , amongTime = 86400, urlProtect = baseDir.."url-protect/limit.txt" }, 18 | 19 | 20 | -- 主动防御,302响应头跳转模块。利用cc控制端不支持解析响应头的特点,来识别是否为正常用户,当有必要时才建议开启。 21 | -- state : 为此模块的状态,表示开启或关闭,可选值为On或Off; 22 | -- verifyMaxFail amongTime : 因为此模块会发送带有cckey及keyexpire的302响应头,如果访客在amongTime时间内超过verifyMaxFail次没有跳转到302响应头里的url,就会被添加到黑名单,默认值为5次。 23 | -- keySecret : 用于生成token的密码,如果上面的keyDefine为dynamic,就不需要修改 24 | -- urlProtect 同limitReqModules模块中的urlProtect的解释。 25 | redirectModules = { state = "Off" ,verifyMaxFail = 5, keySecret = 'yK48J276hg', amongTime = 60 ,urlProtect = baseDir.."url-protect/302.txt"}, 26 | 27 | 28 | -- 主动防御,发送js跳转代码模块。利用cc控制端无法解析js跳转的特点,来识别是否为正常用户,当有必要时才建议开启。 29 | -- state : 为此模块的状态,表示开启或关闭,可选值为On或Off; 30 | -- verifyMaxFail amongTime : 因为此模块会发送带有js跳转代码的响应体,如果访客在amongTime时间内超过verifyMaxFail次没有跳转到js跳转代码里的url,就会被添加到黑名单,默认值为5次。 31 | -- keySecret : 用于生成token的密码,如果上面的keyDefine为dynamic,就不需要修改 32 | -- urlProtect 同limitReqModules模块中的urlProtect的解释。 33 | JsJumpModules = { state = "Off" ,verifyMaxFail = 5, keySecret = 'QSjL6p38h9', amongTime = 60 , urlProtect = baseDir.."url-protect/js.txt"}, 34 | 35 | -- 主动防御,发送cookie验证模块。此模块会向访客发送cookie,然后等待访客返回正确的cookie,此模块利用cc控制端无法支持cookie的特点,来识别cc攻击,当有必要时才建议开启 36 | -- state : 为此模块的状态,表示开启或关闭,可选值为On或Off; 37 | -- verifyMaxFail amongTime : 因为此模块会发送cookie,如果访客在amongTime时间内超过verifyMaxFail次没有返回正确的cookie,就会被添加到黑名单,默认值为5次。 38 | -- keySecret : 用于生成token的密码,如果上面的keyDefine为dynamic,就不需要修改 39 | -- urlProtect 同limitReqModules模块中的urlProtect的解释。 40 | cookieModules = { state = "Off" ,verifyMaxFail = 5, keySecret = 'bGMfY2D5t3', amongTime = 60 , urlProtect = baseDir.."url-protect/cookie.txt"}, 41 | 42 | -- 自动开启主动防御,原理是根据protectPort端口的已连接数超过maxConnection来确定 43 | -- state : 为此模块的状态,表示开启或关闭,可选值为On或Off; 44 | -- interval 间隔30秒检查一次连接数,默认为30秒。 45 | -- protectPort,maxConnection,normalTimes,exceedTimes : enableModule中的模块为关闭状态时,当端口protectPort的连接数连续exceedTimes次超过maxConnection时,开启enableModule中的模块; 46 | -- enableModule中的模块为开启状态时,当端口protectPort的连接数连续normalTimes次低于maxConnection时,关闭enableModule中的模块。 47 | -- ssCommand : 我们是使用ss命令来检查特定端口的已连接的连接数,ss命令比同类的命令netstat快得多。请把ss命令的路径改为自己系统上的路径。 48 | -- enableModules : 自动启动哪个主动防御模块,可选值为redirectModules JsJumpModules cookieModules 49 | autoEnable = { state = "off", protectPort = "80", interval = 30, normalTimes = 3,exceedTimes = 2,maxConnection = 500, ssCommand = "/usr/sbin/ss" ,enableModule = "redirectModules"}, 50 | 51 | -- 用于当输入验证码验证通过时,生成key的密码.如果上面的keyDefine为dynamic,就不需要修改 52 | captchaKey = "K4QEaHjwyF", 53 | 54 | -- ip在黑名单时执行的动作(可选值captcha,forbidden,iptables) 55 | -- 值为captcha时,表示ip在黑名单后返回带有验证码的页面,输入正确的验证码才允许继续访问网站 56 | -- 值为forbidden时,表示ip在黑名单后,服务器会直接断开与用户的连接. 57 | -- 值为iptables时,表示ip在黑名单后,http-guard会用iptables封锁此ip的连接 58 | -- 当值为iptables时,需要为nginx运行用户设置密码及添加到sudo以便能执行iptables命令。假设nginx运行用户为www,设置方法为: 59 | -- 1.设置www密码,命令为passwd www 60 | -- 2.以根用户执行visudo命令,添加www ALL=(root) /sbin/iptables -I INPUT -p tcp -s [0-9.]* --dport 80 -j DROP 61 | -- 3.以根用户执行visudo命令,找到Default requiretty注释,即更改为#Default requiretty,如果找不到此设置,就不需要改。 62 | blockAction = "captcha", 63 | 64 | -- nginx运行用户的sudo密码,blockAction值为iptables需要设置,否则不需要 65 | sudoPass = '', 66 | 67 | -- 表示http-guard封锁ip的时间 68 | blockTime = 86400, 69 | 70 | -- JsJumpModules redirectModules cookieModules验证通过后,ip在白名单的时间 71 | whiteTime = 600, 72 | 73 | -- 用于生成token密码的key过期时间 74 | keyExpire = 600, 75 | 76 | -- 匹配url模式,可选值requestUri,uri 77 | -- 值requestUri时,url-protect目录下的正则匹配的是浏览器最初请求的地址且没有被decode,带参数的链接 78 | -- 值为uri时, url-protect目录下的正则匹配的是经过重写过的地址,不带参数,且已经decode. 79 | urlMatchMode = "uri", 80 | 81 | -- 验证码页面路径,一般不需要修改 82 | captchaPage = baseDir.."html/captcha.html", 83 | 84 | -- 输入验证码错误时显示的页面路径,一般不需要修改 85 | reCaptchaPage = baseDir.."html/reCatchaPage.html", 86 | 87 | -- 白名单ip文件,文件内容为正则表达式。 88 | whiteIpModules = { state = "On", ipList = baseDir.."url-protect/white_ip_list.txt" }, 89 | 90 | -- 黑名单ip文件,文件内容为正则表达式。 91 | blackIpModules = { state = "Off", ipList = baseDir.."url-protect/black_ip_list.txt" }, 92 | 93 | -- 如果需要从请求头获取真实ip,此值就需要设置,如x-forwarded-for 94 | -- 当state为on时,此设置才有效 95 | realIpFromHeader = { state = "Off", header = "x-forwarded-for"}, 96 | 97 | -- 指定验证码图片目录,一般不需要修改 98 | captchaDir = baseDir.."captcha/", 99 | 100 | -- 是否开启debug日志 101 | debug = true, 102 | 103 | --日志目录,一般不需要修改.但需要设置logs所有者为nginx运行用户,如nginx运行用户为www,则命令为chown www logs 104 | logPath = baseDir.."logs/", 105 | } 106 | 107 | return Config -------------------------------------------------------------------------------- /guard.lua: -------------------------------------------------------------------------------- 1 | local Guard = {} 2 | 3 | --debug日志 4 | function Guard:debug(data,ip,reqUri) 5 | if _Conf.debug then 6 | local date = os.date("%Y-%m-%d") 7 | local filename = _Conf.logPath.."/debug-"..date..".log" 8 | local file = io.open(filename,"a+") 9 | file:write(os.date('%Y-%m-%d %H:%M:%S').." [DEBUG] "..data.." IP "..ip.." GET "..reqUri.."\n") 10 | file:close() 11 | end 12 | end 13 | 14 | --攻击日志 15 | function Guard:log(data) 16 | local date = os.date("%Y-%m-%d") 17 | local filename = _Conf.logPath.."/attack-"..date..".log" 18 | local file = io.open(filename,"a+") 19 | file:write(os.date('%Y-%m-%d %H:%M:%S').." [WARNING] "..data.."\n") 20 | file:close() 21 | end 22 | 23 | --获取真实ip 24 | function Guard:getRealIp(remoteIp,headers) 25 | if _Conf.realIpFromHeaderIsOn then 26 | readIp = headers[_Conf.realIpFromHeader.header] 27 | if readIp then 28 | self:debug("[getRealIp] realIpFromHeader is on.return ip "..readIp,remoteIp,"") 29 | return headers[_Conf.realIpFromHeader.header] 30 | else 31 | return remoteIp 32 | end 33 | else 34 | return remoteIp 35 | end 36 | end 37 | 38 | --白名单模块 39 | function Guard:ipInWhiteList(ip) 40 | if _Conf.whiteIpModulesIsOn then --判断是否开启白名单模块 41 | self:debug("[ipInWhiteList] whiteIpModules is on.",ip,"") 42 | 43 | if ngx.re.match(ip, _Conf.whiteIpList) then --匹配白名单列表 44 | self:debug("[ipInWhiteList] ip "..ip.. " match white list ".._Conf.whiteIpList,ip,"") 45 | return true 46 | else 47 | return false 48 | end 49 | end 50 | end 51 | 52 | function Guard:ipInFileBlackList(ip) 53 | if _Conf.fileBlackIpModulesIsOn then 54 | self:debug("[IpInFileBlackList] fileBlackIpModules is on.",ip,"") 55 | 56 | if ngx.re.match(ip, _Conf.fileBlackIpList) then --匹配黑名单列表 57 | self:debug("[ipInFileBlackList] ip "..ip.. " match black list ".._Conf.fileBlackIpList,ip,"") 58 | return true 59 | else 60 | return false 61 | end 62 | end 63 | end 64 | 65 | 66 | --收集不在白名单中的蜘蛛ip 67 | function Guard:collectSpiderIp(ip, headers) 68 | local spiderPattern = "baiduspider|360spider|sogou web spider|sogou inst spider|mediapartners|adsbot-google|googlebot" 69 | local userAgent = string.lower(headers["user-agent"]) 70 | if ngx.re.match(userAgent, spiderPattern) then 71 | local filename = _Conf.logPath.."/spider_ip.log" 72 | local file = io.open(filename, "a+") 73 | file:write(os.date('%Y-%m-%d %H:%M:%S').." IP "..ip.." UA "..userAgent.."\n") 74 | file:close() 75 | end 76 | end 77 | 78 | --黑名单模块 79 | function Guard:blackListModules(ip, reqUri, headers) 80 | local blackKey = ip.."black" 81 | if _Conf.dict:get(blackKey) then --判断ip是否存在黑名单字典 82 | self:debug("[IpblackListModules] ip "..ip.." in blacklist",ip,reqUri) 83 | self:takeAction(ip,reqUri) --存在则执行相应动作 84 | end 85 | 86 | if _Conf.limitUaModulesIsOn then 87 | local userAgent = headers["user-agent"] 88 | --不存在UA直接抛验证码 89 | if not userAgent then 90 | self:debug("[limitUaModules] ip "..ip.." not have ua", ip, reqUri) 91 | self:takeAction(ip,reqUri) --存在则执行相应动作 92 | end 93 | 94 | local uaMd5 = ngx.md5(userAgent) 95 | local blackUaKey = uaMd5 .. 'BlackUAKey' 96 | if _Conf.dict:get(blackUaKey) then --判断ua是否存在黑名单字典 97 | self:debug("[UablackListModules] ip "..ip.." in ua blacklist".." "..userAgent, ip, reqUri) 98 | self:takeAction(ip,reqUri) --存在则执行相应动作 99 | end 100 | end 101 | end 102 | 103 | --限制UA请求速率模块 104 | function Guard:limitUaModules(ip, reqUri, address, headers) 105 | local userAgent = headers["user-agent"] 106 | --不存在UA直接抛验证码 107 | if not userAgent then 108 | self:debug("[limitUaModules] ip "..ip.." not have ua", ip, reqUri) 109 | self:takeAction(ip,reqUri) --存在则执行相应动作 110 | end 111 | 112 | local uaMd5 = ngx.md5(userAgent) 113 | local blackUaKey = uaMd5 .. 'BlackUAKey' 114 | local limitUaKey = uaMd5 .. 'LimitUaKey' 115 | local uaTimes = _Conf.dict:get(limitUaKey) --获取此ua请求的次数 116 | 117 | --增加一次请求记录 118 | if uaTimes then 119 | _Conf.dict:incr(limitUaKey, 1) 120 | else 121 | _Conf.dict:set(limitUaKey, 1, _Conf.limitUaModules.amongTime) 122 | uaTimes = 0 123 | end 124 | 125 | local newUaTimes = uaTimes + 1 126 | self:debug("[limitUaModules] newUaTimes " .. newUaTimes .. " " .. userAgent, ip, reqUri) 127 | 128 | --判断请求数是否大于阀值,大于则添加黑名单 129 | if newUaTimes > _Conf.limitUaModules.maxReqs then --判断是否请求数大于阀值 130 | self:debug("[limitUaModules] ip "..ip.. " request exceed ".._Conf.limitUaModules.maxReqs.." "..userAgent, ip, reqUri) 131 | _Conf.dict:set(blackUaKey, 0, _Conf.blockTime) --添加此ip到黑名单 132 | self:log("[limitUaModules] IP "..ip.." visit "..newUaTimes.." times,block it. "..userAgent) 133 | end 134 | 135 | end 136 | 137 | 138 | --限制IP请求速率模块 139 | function Guard:limitReqModules(ip,reqUri,address) 140 | if ngx.re.match(address,_Conf.limitUrlProtect,"i") then 141 | self:debug("[limitReqModules] address "..address.." match reg ".._Conf.limitUrlProtect,ip,reqUri) 142 | local blackKey = ip.."black" 143 | local limitReqKey = ip.."limitreqkey" --定义limitreq key 144 | local reqTimes = _Conf.dict:get(limitReqKey) --获取此ip请求的次数 145 | 146 | --增加一次请求记录 147 | if reqTimes then 148 | _Conf.dict:incr(limitReqKey, 1) 149 | else 150 | _Conf.dict:set(limitReqKey, 1, _Conf.limitReqModules.amongTime) 151 | reqTimes = 0 152 | end 153 | 154 | local newReqTimes = reqTimes + 1 155 | self:debug("[limitReqModules] newReqTimes "..newReqTimes,ip,reqUri) 156 | 157 | --判断请求数是否大于阀值,大于则添加黑名单 158 | if newReqTimes > _Conf.limitReqModules.maxReqs then --判断是否请求数大于阀值 159 | self:debug("[limitReqModules] ip "..ip.. " request exceed ".._Conf.limitReqModules.maxReqs,ip,reqUri) 160 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 161 | self:log("[limitReqModules] IP "..ip.." visit "..newReqTimes.." times,block it.") 162 | 163 | --大于20次的特别记录下来 164 | if newReqTimes > 20 then 165 | local filename = _Conf.logPath.."/large_flow.log" 166 | local file = io.open(filename, "a+") 167 | file:write(os.date('%Y-%m-%d %H:%M:%S').." IP "..ip.."\n") 168 | file:close() 169 | end 170 | end 171 | 172 | end 173 | end 174 | 175 | --302转向模块 176 | function Guard:redirectModules(ip,reqUri,address) 177 | if ngx.re.match(address,_Conf.redirectUrlProtect,"i") then 178 | self:debug("[redirectModules] address "..address.." match reg ".._Conf.redirectUrlProtect,ip,reqUri) 179 | local whiteKey = ip.."white302" 180 | local inWhiteList = _Conf.dict:get(whiteKey) 181 | 182 | if inWhiteList then --如果在白名单 183 | self:debug("[redirectModules] in white ip list",ip,reqUri) 184 | return 185 | else 186 | --如果不在白名单,再检测是否有cookie凭证 187 | local now = ngx.time() --当前时间戳 188 | local challengeTimesKey = table.concat({ip,"challenge302"}) 189 | local challengeTimesValue = _Conf.dict:get(challengeTimesKey) 190 | local blackKey = ip.."black" 191 | local cookie_key = ngx.var["cookie_key302"] --获取cookie密钥 192 | local cookie_expire = ngx.var["cookie_expire302"] --获取cookie密钥过期时间 193 | 194 | if cookie_key and cookie_expire then 195 | local key_make = ngx.md5(table.concat({ip,_Conf.redirectModules.keySecret,cookie_expire})) 196 | local key_make = string.sub(key_make,"1","10") 197 | --判断cookie是否有效 198 | if tonumber(cookie_expire) > now and cookie_key == key_make then 199 | self:debug("[redirectModules] cookie key is valid.",ip,reqUri) 200 | if challengeTimesValue then 201 | _Conf.dict:delete(challengeTimesKey) --删除验证失败计数器 202 | end 203 | _Conf.dict:set(whiteKey,0,_Conf.whiteTime) --添加到白名单 204 | return 205 | else 206 | self:debug("[redirectModules] cookie key is invalid.",ip,reqUri) 207 | local expire = now + _Conf.keyExpire 208 | local key_new = ngx.md5(table.concat({ip,_Conf.redirectModules.keySecret,expire})) 209 | local key_new = string.sub(key_new,"1","10") 210 | --定义转向的url 211 | local newUrl = '' 212 | local newReqUri = ngx.re.match(reqUri, "(.*?)\\?(.+)") 213 | if newReqUri then 214 | local reqUriNoneArgs = newReqUri[1] 215 | local args = newReqUri[2] 216 | --删除cckey和keyexpire 217 | local newArgs = ngx.re.gsub(args, "[&?]?key302=[^&]+&?|expire302=[^&]+&?", "", "i") 218 | if newArgs == "" then 219 | newUrl = table.concat({reqUriNoneArgs,"?key302=",key_new,"&expire302=",expire}) 220 | else 221 | newUrl = table.concat({reqUriNoneArgs,"?",newArgs,"&key302=",key_new,"&expire302=",expire}) 222 | end 223 | else 224 | newUrl = table.concat({reqUri,"?key302=",key_new,"&expire302=",expire}) 225 | 226 | end 227 | 228 | --验证失败次数加1 229 | if challengeTimesValue then 230 | _Conf.dict:incr(challengeTimesKey,1) 231 | if challengeTimesValue + 1> _Conf.redirectModules.verifyMaxFail then 232 | self:debug("[redirectModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 233 | self:log("[redirectModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.") 234 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 235 | end 236 | else 237 | _Conf.dict:set(challengeTimesKey,1,_Conf.redirectModules.amongTime) 238 | end 239 | 240 | --删除cookie 241 | ngx.header['Set-Cookie'] = {"key302=; path=/", "expire302=; expires=Sat, 01-Jan-2000 00:00:00 GMT; path=/"} 242 | return ngx.redirect(newUrl, 302) --发送302转向 243 | end 244 | else 245 | --如果没有找到cookie,则检测是否带cckey参数 246 | local ccKeyValue = ngx.re.match(reqUri, "key302=([^&]+)","i") 247 | local expire = ngx.re.match(reqUri, "expire302=([^&]+)","i") 248 | 249 | if ccKeyValue and expire then --是否有cckey和keyexpire参数 250 | local ccKeyValue = ccKeyValue[1] 251 | local expire = expire[1] 252 | local key_make = ngx.md5(table.concat({ip,_Conf.redirectModules.keySecret,expire})) 253 | local key_make = string.sub(key_make,"1","10") 254 | self:debug("[redirectModules] ccKeyValue "..ccKeyValue,ip,reqUri) 255 | self:debug("[redirectModules] expire "..expire,ip,reqUri) 256 | self:debug("[redirectModules] key_make "..key_make,ip,reqUri) 257 | self:debug("[redirectModules] ccKeyValue "..ccKeyValue,ip,reqUri) 258 | if key_make == ccKeyValue and now < tonumber(expire) then--判断传过来的cckey参数值是否等于字典记录的值,且没有过期 259 | self:debug("[redirectModules] ip "..ip.." arg key302 "..ccKeyValue.." is valid.add ip to write list.",ip,reqUri) 260 | 261 | if challengeTimesValue then 262 | _Conf.dict:delete(challengeTimesKey) --删除验证失败计数器 263 | end 264 | _Conf.dict:set(whiteKey,0,_Conf.whiteTime) --添加到白名单 265 | ngx.header['Set-Cookie'] = {"key302="..key_make.."; path=/", "expire302="..expire.."; path=/"} --发送cookie凭证 266 | return 267 | else --如果不相等,则再发送302转向 268 | self:debug("[redirectModules] ip "..ip.." arg key302 is invalid.",ip,reqUri) 269 | local expire = now + _Conf.keyExpire 270 | local key_new = ngx.md5(table.concat({ip,_Conf.redirectModules.keySecret,expire})) 271 | local key_new = string.sub(key_new,"1","10") 272 | 273 | --验证失败次数加1 274 | if challengeTimesValue then 275 | _Conf.dict:incr(challengeTimesKey,1) 276 | if challengeTimesValue + 1 > _Conf.redirectModules.verifyMaxFail then 277 | self:debug("[redirectModules] client "..ip.." challenge 302key failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 278 | self:log("[redirectModules] client "..ip.." challenge 302key failed "..challengeTimesValue.." times,add to blacklist.") 279 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 280 | end 281 | else 282 | _Conf.dict:set(challengeTimesKey,1,_Conf.redirectModules.amongTime) 283 | end 284 | --定义转向的url 285 | local newUrl = '' 286 | local newReqUri = ngx.re.match(reqUri, "(.*?)\\?(.+)") 287 | if newReqUri then 288 | local reqUriNoneArgs = newReqUri[1] 289 | local args = newReqUri[2] 290 | --删除cckey和keyexpire 291 | local newArgs = ngx.re.gsub(args, "[&?]?key302=[^&]+&?|expire302=[^&]+&?", "", "i") 292 | if newArgs == "" then 293 | newUrl = table.concat({reqUriNoneArgs,"?key302=",key_new,"&expire302=",expire}) 294 | else 295 | newUrl = table.concat({reqUriNoneArgs,"?",newArgs,"&key302=",key_new,"&expire302=",expire}) 296 | end 297 | else 298 | newUrl = table.concat({reqUri,"?key302=",key_new,"&expire302=",expire}) 299 | 300 | end 301 | 302 | return ngx.redirect(newUrl, 302) --发送302转向 303 | end 304 | else 305 | --验证失败次数加1 306 | if challengeTimesValue then 307 | _Conf.dict:incr(challengeTimesKey,1) 308 | if challengeTimesValue +1 > _Conf.redirectModules.verifyMaxFail then 309 | self:debug("[redirectModules] client "..ip.." challenge 302key failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 310 | self:log("[redirectModules] client "..ip.." challenge 302key failed "..challengeTimesValue.." times,add to blacklist.") 311 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 312 | end 313 | else 314 | _Conf.dict:set(challengeTimesKey,1,_Conf.redirectModules.amongTime) 315 | end 316 | 317 | local expire = now + _Conf.keyExpire 318 | local key_new = ngx.md5(table.concat({ip,_Conf.redirectModules.keySecret,expire})) 319 | local key_new = string.sub(key_new,"1","10") 320 | 321 | --定义转向的url 322 | local newUrl = '' 323 | local newReqUri = ngx.re.match(reqUri, "(.*?)\\?(.+)") 324 | if newReqUri then 325 | local reqUriNoneArgs = newReqUri[1] 326 | local args = newReqUri[2] 327 | --删除cckey和keyexpire 328 | local newArgs = ngx.re.gsub(args, "[&?]?key302=[^&]+&?|expire302=[^&]+&?", "", "i") 329 | if newArgs == "" then 330 | newUrl = table.concat({reqUriNoneArgs,"?key302=",key_new,"&expire302=",expire}) 331 | else 332 | newUrl = table.concat({reqUriNoneArgs,"?",newArgs,"&key302=",key_new,"&expire302=",expire}) 333 | end 334 | else 335 | newUrl = table.concat({reqUri,"?key302=",key_new,"&expire302=",expire}) 336 | 337 | end 338 | 339 | return ngx.redirect(newUrl, 302) --发送302转向 340 | end 341 | end 342 | end 343 | end 344 | end 345 | 346 | --js跳转模块 347 | function Guard:JsJumpModules(ip,reqUri,address) 348 | if ngx.re.match(address,_Conf.JsJumpUrlProtect,"i") then 349 | self:debug("[JsJumpModules] address "..address.." match reg ".._Conf.JsJumpUrlProtect,ip,reqUri) 350 | local whiteKey = ip.."whitejs" 351 | local inWhiteList = _Conf.dict:get(whiteKey) 352 | 353 | if inWhiteList then --如果在白名单 354 | self:debug("[JsJumpModules] in white ip list",ip,reqUri) 355 | return 356 | else 357 | --如果不在白名单,检测是否有cookie凭证 358 | local cookie_key = ngx.var["cookie_keyjs"] --获取cookie密钥 359 | local cookie_expire = ngx.var["cookie_expirejs"] --获取cookie密钥过期时间 360 | local now = ngx.time() --当前时间戳 361 | local challengeTimesKey = table.concat({ip,"challengejs"}) 362 | local challengeTimesValue = _Conf.dict:get(challengeTimesKey) 363 | local blackKey = ip.."black" 364 | local cookie_key = ngx.var["cookie_keyjs"] --获取cookie密钥 365 | local cookie_expire = ngx.var["cookie_expirejs"] --获取cookie密钥过期时间 366 | 367 | if cookie_key and cookie_expire then 368 | local key_make = ngx.md5(table.concat({ip,_Conf.JsJumpModules.keySecret,cookie_expire})) 369 | local key_make = string.sub(key_make,"1","10") 370 | if tonumber(cookie_expire) > now and cookie_key == key_make then 371 | if challengeTimesValue then 372 | _Conf.dict:delete(challengeTimesKey) --删除验证失败计数器 373 | end 374 | self:debug("[JsJumpModules] cookie key is valid.",ip,reqUri) 375 | _Conf.dict:set(whiteKey,0,_Conf.whiteTime) --添加ip到白名单 376 | return 377 | else 378 | --验证失败次数加1 379 | if challengeTimesValue then 380 | _Conf.dict:incr(challengeTimesKey,1) 381 | if challengeTimesValue +1 > _Conf.JsJumpModules.verifyMaxFail then 382 | self:debug("[JsJumpModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 383 | self:log("[JsJumpModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.") 384 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 385 | end 386 | else 387 | _Conf.dict:set(challengeTimesKey,1,_Conf.JsJumpModules.amongTime) 388 | end 389 | 390 | self:debug("[JsJumpModules] cookie key is invalid.",ip,reqUri) 391 | local expire = now + _Conf.keyExpire 392 | local key_new = ngx.md5(table.concat({ip,_Conf.JsJumpModules.keySecret,expire})) 393 | local key_new = string.sub(key_new,"1","10") 394 | 395 | --定义转向的url 396 | local newUrl = '' 397 | local newReqUri = ngx.re.match(reqUri, "(.*?)\\?(.+)") 398 | if newReqUri then 399 | local reqUriNoneArgs = newReqUri[1] 400 | local args = newReqUri[2] 401 | --删除cckey和keyexpire 402 | local newArgs = ngx.re.gsub(args, "[&?]?keyjs=[^&]+&?|expirejs=[^&]+&?", "", "i") 403 | if newArgs == "" then 404 | newUrl = table.concat({reqUriNoneArgs,"?keyjs=",key_new,"&expirejs=",expire}) 405 | else 406 | newUrl = table.concat({reqUriNoneArgs,"?",newArgs,"&keyjs=",key_new,"&expirejs=",expire}) 407 | end 408 | else 409 | newUrl = table.concat({reqUri,"?keyjs=",key_new,"&expirejs=",expire}) 410 | 411 | end 412 | 413 | local jsJumpCode=table.concat({""}) --定义js跳转代码 414 | ngx.header.content_type = "text/html" 415 | --删除cookie 416 | ngx.header['Set-Cookie'] = {"keyjs=; path=/", "expirejs=; expires=Sat, 01-Jan-2000 00:00:00 GMT; path=/"} 417 | ngx.print(jsJumpCode) 418 | ngx.exit(200) 419 | end 420 | else 421 | --如果没有cookie凭证,检测url是否带有cckey参数 422 | local ccKeyValue = ngx.re.match(reqUri, "keyjs=([^&]+)","i") 423 | local expire = ngx.re.match(reqUri, "expirejs=([^&]+)","i") 424 | 425 | if ccKeyValue and expire then 426 | local ccKeyValue = ccKeyValue[1] 427 | local expire = expire[1] 428 | 429 | local key_make = ngx.md5(table.concat({ip,_Conf.JsJumpModules.keySecret,expire})) 430 | local key_make = string.sub(key_make,"1","10") 431 | 432 | if key_make == ccKeyValue and now < tonumber(expire) then--判断传过来的cckey参数值是否等于字典记录的值,且没有过期 433 | self:debug("[JsJumpModules] ip "..ip.." arg keyjs "..ccKeyValue.." is valid.add ip to white list.",ip,reqUri) 434 | if challengeTimesValue then 435 | _Conf.dict:delete(challengeTimesKey) --删除验证失败计数器 436 | end 437 | _Conf.dict:set(whiteKey,0,_Conf.whiteTime) --添加ip到白名单 438 | ngx.header['Set-Cookie'] = {"keyjs="..key_make.."; path=/", "expirejs="..expire.."; path=/"} --发送cookie凭证 439 | return 440 | else --如果不相等,则再发送302转向 441 | --验证失败次数加1 442 | if challengeTimesValue then 443 | _Conf.dict:incr(challengeTimesKey,1) 444 | if challengeTimesValue + 1 > _Conf.JsJumpModules.verifyMaxFail then 445 | self:debug("[JsJumpModules] client "..ip.." challenge jskey failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 446 | self:log("[JsJumpModules] client "..ip.." challenge jskey failed "..challengeTimesValue.." times,add to blacklist.") 447 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 448 | end 449 | else 450 | _Conf.dict:set(challengeTimesKey,1,_Conf.JsJumpModules.amongTime) 451 | end 452 | 453 | self:debug("[JsJumpModules] ip "..ip.." arg keyjs is invalid.",ip,reqUri) 454 | local expire = now + _Conf.keyExpire 455 | local key_new = ngx.md5(table.concat({ip,_Conf.JsJumpModules.keySecret,expire})) 456 | local key_new = string.sub(key_new,"1","10") 457 | --定义转向的url 458 | local newUrl = '' 459 | local newReqUri = ngx.re.match(reqUri, "(.*?)\\?(.+)") 460 | if newReqUri then 461 | local reqUriNoneArgs = newReqUri[1] 462 | local args = newReqUri[2] 463 | --删除cckey和keyexpire 464 | local newArgs = ngx.re.gsub(args, "[&?]?keyjs=[^&]+&?|expirejs=[^&]+&?", "", "i") 465 | if newArgs == "" then 466 | newUrl = table.concat({reqUriNoneArgs,"?keyjs=",key_new,"&expirejs=",expire}) 467 | else 468 | newUrl = table.concat({reqUriNoneArgs,"?",newArgs,"&keyjs=",key_new,"&expirejs=",expire}) 469 | end 470 | else 471 | newUrl = table.concat({reqUri,"?keyjs=",key_new,"&expirejs=",expire}) 472 | 473 | end 474 | local jsJumpCode=table.concat({""}) --定义js跳转代码 475 | ngx.header.content_type = "text/html" 476 | ngx.print(jsJumpCode) 477 | ngx.exit(200) 478 | end 479 | else 480 | --验证失败次数加1 481 | if challengeTimesValue then 482 | _Conf.dict:incr(challengeTimesKey,1) 483 | if challengeTimesValue + 1 > _Conf.JsJumpModules.verifyMaxFail then 484 | self:debug("[JsJumpModules] client "..ip.." challenge jskey failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 485 | self:log("[JsJumpModules] client "..ip.." challenge jskey failed "..challengeTimesValue.." times,add to blacklist.") 486 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 487 | end 488 | else 489 | _Conf.dict:set(challengeTimesKey,1,_Conf.JsJumpModules.amongTime) 490 | end 491 | 492 | --定义转向的url 493 | local expire = now + _Conf.keyExpire 494 | local key_new = ngx.md5(table.concat({ip,_Conf.JsJumpModules.keySecret,expire})) 495 | local key_new = string.sub(key_new,"1","10") 496 | 497 | --定义转向的url 498 | local newUrl = '' 499 | local newReqUri = ngx.re.match(reqUri, "(.*?)\\?(.+)") 500 | if newReqUri then 501 | local reqUriNoneArgs = newReqUri[1] 502 | local args = newReqUri[2] 503 | --删除cckey和keyexpire 504 | local newArgs = ngx.re.gsub(args, "[&?]?keyjs=[^&]+&?|expirejs=[^&]+&?", "", "i") 505 | if newArgs == "" then 506 | newUrl = table.concat({reqUriNoneArgs,"?keyjs=",key_new,"&expirejs=",expire}) 507 | else 508 | newUrl = table.concat({reqUriNoneArgs,"?",newArgs,"&keyjs=",key_new,"&expirejs=",expire}) 509 | end 510 | else 511 | newUrl = table.concat({reqUri,"?keyjs=",key_new,"&expirejs=",expire}) 512 | 513 | end 514 | 515 | local jsJumpCode=table.concat({""}) --定义js跳转代码 516 | ngx.header.content_type = "text/html" 517 | ngx.print(jsJumpCode) 518 | ngx.exit(200) 519 | end 520 | end 521 | end 522 | end 523 | end 524 | 525 | --cookie验证模块 526 | function Guard:cookieModules(ip,reqUri,address) 527 | if ngx.re.match(address,_Conf.cookieUrlProtect,"i") then 528 | self:debug("[cookieModules] address "..address.." match reg ".._Conf.cookieUrlProtect,ip,reqUri) 529 | local whiteKey = ip.."whitecookie" 530 | local inWhiteList = _Conf.dict:get(whiteKey) 531 | 532 | if inWhiteList then --如果在白名单 533 | self:debug("[cookieModules] in white ip list.",ip,reqUri) 534 | return 535 | else 536 | local cookie_key = ngx.var["cookie_keycookie"] --获取cookie密钥 537 | local cookie_expire = ngx.var["cookie_expirecookie"] --获取cookie密钥过期时间 538 | local now = ngx.time() --当前时间戳 539 | local challengeTimesKey = table.concat({ip,"challengecookie"}) 540 | local challengeTimesValue = _Conf.dict:get(challengeTimesKey) 541 | local blackKey = ip.."black" 542 | 543 | if cookie_key and cookie_expire then --判断是否有收到cookie 544 | local key_make = ngx.md5(table.concat({ip,_Conf.cookieModules.keySecret,cookie_expire})) 545 | local key_make = string.sub(key_make,"1","10") 546 | if tonumber(cookie_expire) > now and cookie_key == key_make then 547 | if challengeTimesValue then 548 | _Conf.dict:delete(challengeTimesKey) --删除验证失败计数器 549 | end 550 | self:debug("[cookieModules] cookie key is valid.add to white ip list",ip,reqUri) 551 | _Conf.dict:set(whiteKey,0,_Conf.whiteTime) --添加ip到白名单 552 | return 553 | else 554 | self:debug("[cookieModules] cookie key is invalid",ip,reqUri) 555 | --验证失败次数加1 556 | if challengeTimesValue then 557 | _Conf.dict:incr(challengeTimesKey,1) 558 | if challengeTimesValue +1 > _Conf.cookieModules.verifyMaxFail then 559 | self:debug("[cookieModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 560 | self:log("[cookieModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.") 561 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 562 | end 563 | else 564 | _Conf.dict:set(challengeTimesKey,1,_Conf.cookieModules.amongTime) 565 | end 566 | 567 | ngx.header['Set-Cookie'] = {"keycookie=; path=/", "expirecookie=; expires=Sat, 01-Jan-2000 00:00:00 GMT; path=/"} --删除cookie 568 | end 569 | else --找不到cookie 570 | self:debug("[cookieModules] cookie not found.",ip,reqUri) 571 | --验证失败次数加1 572 | if challengeTimesValue then 573 | _Conf.dict:incr(challengeTimesKey,1) 574 | if challengeTimesValue +1 > _Conf.cookieModules.verifyMaxFail then 575 | self:debug("[cookieModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.",ip,reqUri) 576 | self:log("[cookieModules] client "..ip.." challenge cookie failed "..challengeTimesValue.." times,add to blacklist.") 577 | _Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单 578 | end 579 | else 580 | _Conf.dict:set(challengeTimesKey,1,_Conf.cookieModules.amongTime) 581 | end 582 | 583 | local expire = now + _Conf.keyExpire 584 | local key_new = ngx.md5(table.concat({ip,_Conf.cookieModules.keySecret,expire})) 585 | local key_new = string.sub(key_new,"1","10") 586 | 587 | self:debug("[cookieModules] send cookie to client.",ip,reqUri) 588 | ngx.header['Set-Cookie'] = {"keycookie="..key_new.."; path=/", "expirecookie="..expire.."; path=/"} --发送cookie凭证 589 | end 590 | end 591 | end 592 | end 593 | 594 | --获取验证码 595 | function Guard:getCaptcha() 596 | math.randomseed(ngx.now()) --随机种子 597 | local random = math.random(1,10000) --生成1-10000之前的随机数 598 | self:debug("[getCaptcha] get random num "..random,"","") 599 | local captchaValue = _Conf.dict_captcha:get(random) --取得字典中的验证码 600 | self:debug("[getCaptcha] get captchaValue "..captchaValue,"","") 601 | local captchaImg = _Conf.dict_captcha:get(captchaValue) --取得验证码对应的图片 602 | --返回图片 603 | ngx.header.content_type = "image/jpeg" 604 | ngx.header['Set-Cookie'] = table.concat({"captchaNum=",random,"; path=/"}) 605 | ngx.print(captchaImg) 606 | ngx.exit(200) 607 | end 608 | 609 | --验证验证码 610 | function Guard:verifyCaptcha(ip) 611 | ngx.req.read_body() 612 | local captchaNum = ngx.var["cookie_captchaNum"] --获取cookie captchaNum值 613 | local preurl = ngx.var["cookie_preurl"] --获取上次访问url 614 | self:debug("[verifyCaptcha] get cookie captchaNum "..captchaNum,ip,"") 615 | local args = ngx.req.get_post_args() --获取post参数 616 | local postValue = args["response"] --获取post value参数 617 | postValue = string.lower(postValue) 618 | self:debug("[verifyCaptcha] get post arg response "..postValue,ip,"") 619 | local captchaValue = _Conf.dict_captcha:get(captchaNum) --从字典获取post value对应的验证码值 620 | if captchaValue == postValue then --比较验证码是否相等 621 | self:debug("[verifyCaptcha] captcha is valid.delete from blacklist",ip,"") 622 | 623 | _Conf.dict:delete(ip.."black") --从黑名单删除 624 | _Conf.dict:delete(ip.."limitreqkey") --访问记录删除 625 | 626 | if _Conf.limitUaModulesIsOn then 627 | local headers = ngx.req.get_headers() 628 | local userAgent = headers["user-agent"] 629 | --不存在UA直接抛验证码 630 | if not userAgent then 631 | self:debug("[limitUaModules] ip "..ip.." not have ua", ip, reqUri) 632 | self:takeAction(ip,reqUri) --存在则执行相应动作 633 | end 634 | 635 | local uaMd5 = ngx.md5(userAgent) 636 | local blackUaKey = uaMd5 .. 'BlackUAKey' 637 | local limitUaKey = uaMd5 .. 'LimitUaKey' 638 | 639 | _Conf.dict:delete(blackUaKey) --从黑名单删除 640 | _Conf.dict:delete(limitUaKey) --访问记录删除 641 | end 642 | 643 | local expire = ngx.time() + _Conf.keyExpire 644 | local captchaKey = ngx.md5(table.concat({ip,_Conf.captchaKey,expire})) 645 | local captchaKey = string.sub(captchaKey,"1","10") 646 | self:debug("[verifyCaptcha] expire "..expire,ip,"") 647 | self:debug("[verifyCaptcha] captchaKey "..captchaKey,ip,"") 648 | ngx.header['Set-Cookie'] = {"captchaKey="..captchaKey.."; path=/", "captchaExpire="..expire.."; path=/"} 649 | return ngx.redirect(preurl) --返回上次访问url 650 | else 651 | --重新发送验证码页面 652 | self:debug("[verifyCaptcha] captcha invalid",ip,"") 653 | ngx.header.content_type = "text/html" 654 | ngx.print(_Conf.reCaptchaPage) 655 | ngx.exit(200) 656 | end 657 | end 658 | 659 | --拒绝访问动作 660 | function Guard:forbiddenAction() 661 | ngx.header.content_type = "text/html" 662 | ngx.exit(444) 663 | end 664 | 665 | --展示验证码页面动作 666 | function Guard:captchaAction(reqUri) 667 | ngx.header.content_type = "text/html" 668 | ngx.header['Set-Cookie'] = table.concat({"preurl=",reqUri,"; path=/"}) 669 | ngx.print(_Conf.captchaPage) 670 | ngx.exit(200) 671 | end 672 | 673 | --执行相应动作 674 | function Guard:takeAction(ip,reqUri) 675 | if _Conf.captchaAction then 676 | local cookie_key = ngx.var["cookie_captchaKey"] --获取cookie captcha密钥 677 | local cookie_expire = ngx.var["cookie_captchaExpire"] --获取cookie captcha过期时间 678 | if cookie_expire and cookie_key then 679 | local now = ngx.time() 680 | local key_make = ngx.md5(table.concat({ip,_Conf.captchaKey,cookie_expire})) 681 | local key_make = string.sub(key_make,"1","10") 682 | self:debug("[takeAction] cookie_expire "..cookie_expire,ip,reqUri) 683 | self:debug("[takeAction] cookie_key "..cookie_key,ip,reqUri) 684 | self:debug("[takeAction] now "..now,ip,reqUri) 685 | self:debug("[takeAction] key_make "..key_make,ip,reqUri) 686 | if tonumber(cookie_expire) > now and cookie_key == key_make then 687 | self:debug("[takeAction] cookie key is valid.",ip,reqUri) 688 | return 689 | else 690 | self:debug("[takeAction] cookie key is invalid",ip,reqUri) 691 | self:captchaAction(reqUri) 692 | end 693 | else 694 | self:debug("[takeAction] return captchaAction",ip,reqUri) 695 | self:captchaAction(reqUri) 696 | end 697 | elseif _Conf.forbiddenAction then 698 | self:debug("[takeAction] return forbiddenAction",ip,reqUri) 699 | self:forbiddenAction() 700 | 701 | elseif _Conf.iptablesAction then 702 | ngx.thread.spawn(Guard.addToIptables,Guard,ip) 703 | end 704 | end 705 | 706 | --添加进iptables drop表 707 | function Guard:addToIptables(ip) 708 | local cmd = "echo ".._Conf.sudoPass.." | sudo -S /sbin/iptables -I INPUT -p tcp -s "..ip.." --dport 80 -j DROP" 709 | os.execute(cmd) 710 | end 711 | 712 | --自动开启或关闭防cc功能 713 | function Guard:autoSwitch() 714 | if not _Conf.dict_captcha:get("monitor") then 715 | _Conf.dict_captcha:set("monitor",0,_Conf.autoEnable.interval) 716 | local f=io.popen(_Conf.autoEnable.ssCommand.." -tan state established '( sport = :".._Conf.autoEnable.protectPort.." or dport = :".._Conf.autoEnable.protectPort.." )' | wc -l") 717 | local result=f:read("*all") 718 | local connection=tonumber(result) 719 | Guard:debug("[autoSwitch] current connection for port ".._Conf.autoEnable.protectPort.." is "..connection,"","") 720 | if _Conf.autoEnable.enableModule == "redirectModules" then 721 | local redirectOn = _Conf.dict_captcha:get("redirectOn") 722 | if redirectOn == 1 then 723 | _Conf.dict_captcha:set("exceedCount",0) --超限次数清0 724 | --如果当前连接在最大连接之下,为正常次数加1 725 | if connection < _Conf.autoEnable.maxConnection then 726 | _Conf.dict_captcha:incr("normalCount",1) 727 | end 728 | 729 | --如果正常次数大于_Conf.autoEnable.normalTimes,关闭redirectModules 730 | local normalCount = _Conf.dict_captcha:get("normalCount") 731 | if normalCount > _Conf.autoEnable.normalTimes then 732 | Guard:log("[autoSwitch] turn redirectModules off.") 733 | _Conf.dict_captcha:set("redirectOn",0) 734 | end 735 | else 736 | _Conf.dict_captcha:set("normalCount",0) --正常次数清0 737 | --如果当前连接在最大连接之上,为超限次数加1 738 | if connection > _Conf.autoEnable.maxConnection then 739 | _Conf.dict_captcha:incr("exceedCount",1) 740 | end 741 | 742 | --如果超限次数大于_Conf.autoEnable.exceedTimes,开启redirectModules 743 | local exceedCount = _Conf.dict_captcha:get("exceedCount") 744 | if exceedCount > _Conf.autoEnable.exceedTimes then 745 | Guard:log("[autoSwitch] turn redirectModules on.") 746 | _Conf.dict_captcha:set("redirectOn",1) 747 | end 748 | end 749 | 750 | elseif _Conf.autoEnable.enableModule == "JsJumpModules" then 751 | local jsOn = _Conf.dict_captcha:get("jsOn") 752 | if jsOn == 1 then 753 | _Conf.dict_captcha:set("exceedCount",0) --超限次数清0 754 | --如果当前连接在最大连接之下,为正常次数加1 755 | if connection < _Conf.autoEnable.maxConnection then 756 | _Conf.dict_captcha:incr("normalCount",1) 757 | end 758 | 759 | --如果正常次数大于_Conf.autoEnable.normalTimes,关闭JsJumpModules 760 | local normalCount = _Conf.dict_captcha:get("normalCount") 761 | if normalCount > _Conf.autoEnable.normalTimes then 762 | Guard:log("[autoSwitch] turn JsJumpModules off.") 763 | _Conf.dict_captcha:set("jsOn",0) 764 | end 765 | else 766 | _Conf.dict_captcha:set("normalCount",0) --正常次数清0 767 | --如果当前连接在最大连接之上,为超限次数加1 768 | if connection > _Conf.autoEnable.maxConnection then 769 | _Conf.dict_captcha:incr("exceedCount",1) 770 | end 771 | 772 | --如果超限次数大于_Conf.autoEnable.exceedTimes,开启JsJumpModules 773 | local exceedCount = _Conf.dict_captcha:get("exceedCount") 774 | if exceedCount > _Conf.autoEnable.exceedTimes then 775 | Guard:log("[autoSwitch] turn JsJumpModules on.") 776 | _Conf.dict_captcha:set("jsOn",1) 777 | end 778 | end 779 | 780 | elseif _Conf.autoEnable.enableModule == "cookieModules" then 781 | local cookieOn = _Conf.dict_captcha:get("cookieOn") 782 | if cookieOn == 1 then 783 | _Conf.dict_captcha:set("exceedCount",0) --超限次数清0 784 | --如果当前连接在最大连接之下,为正常次数加1 785 | if connection < _Conf.autoEnable.maxConnection then 786 | _Conf.dict_captcha:incr("normalCount",1) 787 | end 788 | 789 | --如果正常次数大于_Conf.autoEnable.normalTimes,关闭cookieModules 790 | local normalCount = _Conf.dict_captcha:get("normalCount") 791 | if normalCount > _Conf.autoEnable.normalTimes then 792 | Guard:log("[autoSwitch] turn cookieModules off.") 793 | _Conf.dict_captcha:set("cookieOn",0) 794 | end 795 | else 796 | _Conf.dict_captcha:set("normalCount",0) --正常次数清0 797 | --如果当前连接在最大连接之上,为超限次数加1 798 | if connection > _Conf.autoEnable.maxConnection then 799 | _Conf.dict_captcha:incr("exceedCount",1) 800 | end 801 | 802 | --如果超限次数大于_Conf.autoEnable.exceedTimes,开启cookieModules 803 | local exceedCount = _Conf.dict_captcha:get("exceedCount") 804 | if exceedCount > _Conf.autoEnable.exceedTimes then 805 | Guard:log("[autoSwitch] turn cookieModules on.") 806 | _Conf.dict_captcha:set("cookieOn",1) 807 | end 808 | end 809 | end 810 | end 811 | end 812 | 813 | return Guard -------------------------------------------------------------------------------- /html/captcha.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |本站为防止被恶意攻击已开启验证机制
156 |此验证页面仅在被认为恶意攻击时出现
157 |您正常浏览时也有可能触发防攻击程序
158 |请输入验证码继续访问!!!
160 | 167 |您的查询看起来类似于来自计算机软件的自动请求。为了保护我们的用户,请原谅我们现在暂时不能处理您的请求。
18 |要继续访问网页,请输入下面所示字符:
19 | 22 |本站为防止被恶意攻击已开启验证机制
156 |此验证页面仅在被认为恶意攻击时出现
157 |您正常浏览时也有可能触发防攻击程序
158 |请输入验证码继续访问!!!
160 |验证码输入错误,请重新输入
161 | 168 |您的查询看起来类似于来自计算机软件的自动请求。为了保护我们的用户,请原谅我们现在暂时不能处理您的请求。
18 |要继续访问网页,请输入下面所示字符:
19 |验证码输入错误,请重新输入
20 | 23 |