Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,60 @@
http-guard
==========
# http-guard

prevent cc attack
HttpGuard是基于openresty,以lua脚本语言开发的防cc攻击软件。而openresty是集成了高性能web服务器Nginx,以及一系列的Nginx模块,这其中最重要的,也是我们主要用到的nginx lua模块。HttpGuard基于nginx lua开发,继承了nginx高并发,高性能的特点,可以以非常小的性能损耗来防范大规模的cc攻击。

下面介绍HttpGuard防cc的一些特性:

1. 限制单个IP或者UA在一定时间内的请求次数
2. 向访客发送302转向响应头来识别恶意用户,并阻止其再次访问
3. 向访客发送带有跳转功能的js代码来识别恶意用户,并阻止其再次访问
4. 向访客发送cookie来识别恶意用户,并阻止其再次访问
5. 支持向访客发送带有验证码的页面,来进一步识别,以免误伤
6. 支持直接断开恶意访客的连接
7. 支持结合iptables来阻止恶意访客再次连接
8. 支持白名单/黑名单功能
9. 支持根据统计特定端口的连接数来自动开启或关闭防cc模式

## 部署HttpGuard
### 安装openresty或者nginx lua

按照openresty官网手动安装[http://openresty.com](http://openresty.com)

### 安装HttpGuard

假设我们把HttpGuard安装到/data/www/waf/,当然你可以选择安装在任意目录。

```
cd /data/www
wget --no-check-certificate https://github.com/wenjun1055/HttpGuard/archive/master.zip
unzip master.zip
mv HttpGuard-master waf
```

### 生成验证码图片

为了支持验证码识别用户,我们需要先生成验证码图片。生成验证码图片需要系统安装有php,以及php-gd模块。
用以下命令执行getImg.php文件生成验证码

```
cd /data/www/waf/captcha/
/usr/local/php/bin/php getImg.php
```

大概要生成一万个图片,可能需要花几分钟的时间。

### 修改nginx.conf配置文件

向http区块输入如下代码:

```
lua_package_path "/data/www/waf/?.lua";
lua_shared_dict guard_dict 100m;
lua_shared_dict dict_captcha 70m;
init_by_lua_file '/data/www/waf/init.lua';
access_by_lua_file '/data/www/waf/runtime.lua';
lua_max_running_timers 1;
```

### 配置HttpGuard

详细配置说明在[config.lua](https://github.com/wenjun1055/HttpGuard/blob/master/guard.lua)中,请根据需求进行配置
2 changes: 1 addition & 1 deletion captcha/getImg.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function getAuthImage($text) {
$buttum_c = ImageColorAllocate($im,$tmpC0,$tmpC1,$tmpC2);
imagefill($im, 16, 13, $buttum_c);

$font = '/data/www/waf/captcha/t1.ttf';
$font = __DIR__ . '/t1.ttf';

for ($i=0;$i<strlen($text);$i++)
{
Expand Down
18 changes: 13 additions & 5 deletions config.lua
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
-- http-guard安装目录,修改为实际安装到的目录。
baseDir = '/data/www/waf/'
baseDir = '/home/http-guard/'

local Config = {
-- key是否动态生成,可选static,dynamic,如果选dynamic,下面所有的keySecret不需要更改,如果选static,修改手动修改下面的keySecret
keyDefine = "dynamic",

-- 被动防御,限制UA请求模块。根据在一定时间内统计到的单个UA请求次数作限制(专门针对火车头采集工具)
-- state : 为此模块的状态,表示开启或关闭,可选值为On或Off;
-- maxReqs,amongTime : 在amongTime秒内允许请求的最大次数maxReqs,如默认的是在10s内最大允许请求50次。
limitUaModules = { state = "On" , maxReqs = 5 , amongTime = 300},

-- 被动防御,限制请求模块。根据在一定时间内统计到的请求次数作限制,建议始终开启
-- state : 为此模块的状态,表示开启或关闭,可选值为On或Off;
-- maxReqs,amongTime : 在amongTime秒内允许请求的最大次数maxReqs,如默认的是在10s内最大允许请求50次。
-- urlProtect : 指定限制请求次数的url正则表达式文件,默认值为\.php$,表示只限制php的请求(当然,当urlMatchMode = "uri"时,此正则才能起作用)
limitReqModules = { state = "On" , maxReqs = 50 , amongTime = 10, urlProtect = baseDir.."url-protect/limit.txt" },
limitReqModules = { state = "On" , maxReqs = 5 , amongTime = 86400, urlProtect = baseDir.."url-protect/limit.txt" },


-- 主动防御,302响应头跳转模块。利用cc控制端不支持解析响应头的特点,来识别是否为正常用户,当有必要时才建议开启。
Expand Down Expand Up @@ -60,7 +65,7 @@ local Config = {
sudoPass = '',

-- 表示http-guard封锁ip的时间
blockTime = 600,
blockTime = 86400,

-- JsJumpModules redirectModules cookieModules验证通过后,ip在白名单的时间
whiteTime = 600,
Expand All @@ -80,7 +85,10 @@ local Config = {
reCaptchaPage = baseDir.."html/reCatchaPage.html",

-- 白名单ip文件,文件内容为正则表达式。
whiteIpModules = { state = "Off", ipList = baseDir.."url-protect/white_ip_list.txt" },
whiteIpModules = { state = "On", ipList = baseDir.."url-protect/white_ip_list.txt" },

-- 黑名单ip文件,文件内容为正则表达式。
blackIpModules = { state = "Off", ipList = baseDir.."url-protect/black_ip_list.txt" },

-- 如果需要从请求头获取真实ip,此值就需要设置,如x-forwarded-for
-- 当state为on时,此设置才有效
Expand All @@ -90,7 +98,7 @@ local Config = {
captchaDir = baseDir.."captcha/",

-- 是否开启debug日志
debug = false,
debug = true,

--日志目录,一般不需要修改.但需要设置logs所有者为nginx运行用户,如nginx运行用户为www,则命令为chown www logs
logPath = baseDir.."logs/",
Expand Down
112 changes: 108 additions & 4 deletions guard.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,93 @@ function Guard:ipInWhiteList(ip)
end
end

function Guard:ipInFileBlackList(ip)
if _Conf.fileBlackIpModulesIsOn then
self:debug("[IpInFileBlackList] fileBlackIpModules is on.",ip,"")

if ngx.re.match(ip, _Conf.fileBlackIpList) then --匹配黑名单列表
self:debug("[ipInFileBlackList] ip "..ip.. " match black list ".._Conf.fileBlackIpList,ip,"")
return true
else
return false
end
end
end


--收集不在白名单中的蜘蛛ip
function Guard:collectSpiderIp(ip, headers)
local spiderPattern = "baiduspider|360spider|sogou web spider|sogou inst spider|mediapartners|adsbot-google|googlebot"
local userAgent = string.lower(headers["user-agent"])
if ngx.re.match(userAgent, spiderPattern) then
local filename = _Conf.logPath.."/spider_ip.log"
local file = io.open(filename, "a+")
file:write(os.date('%Y-%m-%d %H:%M:%S').." IP "..ip.." UA "..userAgent.."\n")
file:close()
end
end

--黑名单模块
function Guard:blackListModules(ip,reqUri)
function Guard:blackListModules(ip, reqUri, headers)
local blackKey = ip.."black"
if _Conf.dict:get(blackKey) then --判断ip是否存在黑名单字典
self:debug("[blackListModules] ip "..ip.." in blacklist",ip,reqUri)
self:debug("[IpblackListModules] ip "..ip.." in blacklist",ip,reqUri)
self:takeAction(ip,reqUri) --存在则执行相应动作
end
end

if _Conf.limitUaModulesIsOn then
local userAgent = headers["user-agent"]
--不存在UA直接抛验证码
if not userAgent then
self:debug("[limitUaModules] ip "..ip.." not have ua", ip, reqUri)
self:takeAction(ip,reqUri) --存在则执行相应动作
end

local uaMd5 = ngx.md5(userAgent)
local blackUaKey = uaMd5 .. 'BlackUAKey'
if _Conf.dict:get(blackUaKey) then --判断ua是否存在黑名单字典
self:debug("[UablackListModules] ip "..ip.." in ua blacklist".." "..userAgent, ip, reqUri)
self:takeAction(ip,reqUri) --存在则执行相应动作
end
end
end

--限制请求速率模块
--限制UA请求速率模块
function Guard:limitUaModules(ip, reqUri, address, headers)
local userAgent = headers["user-agent"]
--不存在UA直接抛验证码
if not userAgent then
self:debug("[limitUaModules] ip "..ip.." not have ua", ip, reqUri)
self:takeAction(ip,reqUri) --存在则执行相应动作
end

local uaMd5 = ngx.md5(userAgent)
local blackUaKey = uaMd5 .. 'BlackUAKey'
local limitUaKey = uaMd5 .. 'LimitUaKey'
local uaTimes = _Conf.dict:get(limitUaKey) --获取此ua请求的次数

--增加一次请求记录
if uaTimes then
_Conf.dict:incr(limitUaKey, 1)
else
_Conf.dict:set(limitUaKey, 1, _Conf.limitUaModules.amongTime)
uaTimes = 0
end

local newUaTimes = uaTimes + 1
self:debug("[limitUaModules] newUaTimes " .. newUaTimes .. " " .. userAgent, ip, reqUri)

--判断请求数是否大于阀值,大于则添加黑名单
if newUaTimes > _Conf.limitUaModules.maxReqs then --判断是否请求数大于阀值
self:debug("[limitUaModules] ip "..ip.. " request exceed ".._Conf.limitUaModules.maxReqs.." "..userAgent, ip, reqUri)
_Conf.dict:set(blackUaKey, 0, _Conf.blockTime) --添加此ip到黑名单
self:log("[limitUaModules] IP "..ip.." visit "..newUaTimes.." times,block it. "..userAgent)
end

end


--限制IP请求速率模块
function Guard:limitReqModules(ip,reqUri,address)
if ngx.re.match(address,_Conf.limitUrlProtect,"i") then
self:debug("[limitReqModules] address "..address.." match reg ".._Conf.limitUrlProtect,ip,reqUri)
Expand All @@ -82,6 +159,14 @@ function Guard:limitReqModules(ip,reqUri,address)
self:debug("[limitReqModules] ip "..ip.. " request exceed ".._Conf.limitReqModules.maxReqs,ip,reqUri)
_Conf.dict:set(blackKey,0,_Conf.blockTime) --添加此ip到黑名单
self:log("[limitReqModules] IP "..ip.." visit "..newReqTimes.." times,block it.")

--大于20次的特别记录下来
if newReqTimes > 20 then
local filename = _Conf.logPath.."/large_flow.log"
local file = io.open(filename, "a+")
file:write(os.date('%Y-%m-%d %H:%M:%S').." IP "..ip.."\n")
file:close()
end
end

end
Expand Down Expand Up @@ -534,8 +619,27 @@ function Guard:verifyCaptcha(ip)
local captchaValue = _Conf.dict_captcha:get(captchaNum) --从字典获取post value对应的验证码值
if captchaValue == postValue then --比较验证码是否相等
self:debug("[verifyCaptcha] captcha is valid.delete from blacklist",ip,"")

_Conf.dict:delete(ip.."black") --从黑名单删除
_Conf.dict:delete(ip.."limitreqkey") --访问记录删除

if _Conf.limitUaModulesIsOn then
local headers = ngx.req.get_headers()
local userAgent = headers["user-agent"]
--不存在UA直接抛验证码
if not userAgent then
self:debug("[limitUaModules] ip "..ip.." not have ua", ip, reqUri)
self:takeAction(ip,reqUri) --存在则执行相应动作
end

local uaMd5 = ngx.md5(userAgent)
local blackUaKey = uaMd5 .. 'BlackUAKey'
local limitUaKey = uaMd5 .. 'LimitUaKey'

_Conf.dict:delete(blackUaKey) --从黑名单删除
_Conf.dict:delete(limitUaKey) --访问记录删除
end

local expire = ngx.time() + _Conf.keyExpire
local captchaKey = ngx.md5(table.concat({ip,_Conf.captchaKey,expire}))
local captchaKey = string.sub(captchaKey,"1","10")
Expand Down
Loading