diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..4dca92b --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9e6e353 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..22f51ac --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/ngx_lua_waf.iml b/.idea/ngx_lua_waf.iml new file mode 100644 index 0000000..6711606 --- /dev/null +++ b/.idea/ngx_lua_waf.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..dbefb79 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,709 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1474616676553 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 34e8907..75a32bf 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,56 @@ -##ngx_lua_waf - -ngx_lua_waf是我刚入职趣游时候开发的一个基于ngx_lua的web应用防火墙。 - -代码很简单,开发初衷主要是使用简单,高性能和轻量级。 - -现在开源出来,遵从MIT许可协议。其中包含我们的过滤规则。如果大家有什么建议和想fa,欢迎和我一起完善。 - -###用途: - - 防止sql注入,本地包含,部分溢出,fuzzing测试,xss,SSRF等web攻击 - 防止svn/备份之类文件泄漏 - 防止ApacheBench之类压力测试工具的攻击 - 屏蔽常见的扫描黑客工具,扫描器 - 屏蔽异常的网络请求 - 屏蔽图片附件类目录php执行权限 - 防止webshell上传 - -###推荐安装: - -推荐使用lujit2.1做lua支持 - -ngx_lua如果是0.9.2以上版本,建议正则过滤函数改为ngx.re.find,匹配效率会提高三倍左右。 - - -###使用说明: - -nginx安装路径假设为:/usr/local/nginx/conf/ - -把ngx_lua_waf下载到conf目录下,解压命名为waf - -在nginx.conf的http段添加 - - lua_package_path "/usr/local/nginx/conf/waf/?.lua"; - lua_shared_dict limit 10m; - init_by_lua_file /usr/local/nginx/conf/waf/init.lua; - access_by_lua_file /usr/local/nginx/conf/waf/waf.lua; - -配置config.lua里的waf规则目录(一般在waf/conf/目录下) - - RulePath = "/usr/local/nginx/conf/waf/wafconf/" - -绝对路径如有变动,需对应修改 - -然后重启nginx即可 - - -###配置文件详细说明: - - RulePath = "/usr/local/nginx/conf/waf/wafconf/" - --规则存放目录 - attacklog = "off" - --是否开启攻击信息记录,需要配置logdir - logdir = "/usr/local/nginx/logs/hack/" - --log存储目录,该目录需要用户自己新建,切需要nginx用户的可写权限 - UrlDeny="on" - --是否拦截url访问 - Redirect="on" - --是否拦截后重定向 - CookieMatch = "on" - --是否拦截cookie攻击 - postMatch = "on" - --是否拦截post攻击 - whiteModule = "on" - --是否开启URL白名单 - black_fileExt={"php","jsp"} - --填写可上传文件后缀类型 - ipWhitelist={"127.0.0.1"} - --ip白名单,多个ip用逗号分隔 - ipBlocklist={"1.0.0.1"} - --ip黑名单,多个ip用逗号分隔 - CCDeny="on" - --是否开启拦截cc攻击(需要nginx.conf的http段增加lua_shared_dict limit 10m;) - CCrate = "100/60" - --设置cc攻击频率,单位为秒. - --默认1分钟同一个IP只能请求同一个地址100次 - html=[[Please go away~~]] - --警告内容,可在中括号内自定义 - 备注:不要乱动双引号,区分大小写 - -###检查规则是否生效 - -部署完毕可以尝试如下命令: - - curl http://xxxx/test.php?id=../etc/passwd - 返回"Please go away~~"字样,说明规则生效。 - -注意:默认,本机在白名单不过滤,可自行调整config.lua配置 - - -###效果图如下: - -![sec](http://i.imgur.com/wTgOcm2.png) - -![sec](http://i.imgur.com/DqU30au.png) - -###规则更新: - -考虑到正则的缓存问题,动态规则会影响性能,所以暂没用共享内存字典和redis之类东西做动态管理。 - -规则更新可以把规则文件放置到其他服务器,通过crontab任务定时下载来更新规则,nginx reload即可生效。以保障ngx lua waf的高性能。 - -只记录过滤日志,不开启过滤,在代码里在check前面加上--注释即可,如果需要过滤,反之 - -###一些说明: - - 过滤规则在wafconf下,可根据需求自行调整,每条规则需换行,或者用|分割 - - global是全局过滤文件,里面的规则对post和get都过滤 - get是只在get请求过滤的规则 - post是只在post请求过滤的规则 - whitelist是白名单,里面的url匹配到不做过滤 - user-agent是对user-agent的过滤规则 - - - 默认开启了get和post过滤,需要开启cookie过滤的,编辑waf.lua取消部分--注释即可 - - 日志文件名称格式如下:虚拟主机名_sec.log - - -## Copyright - - - - - - - - - - - - - - -
Weibo神奇的魔法师
Forumhttp://bbs.linuxtone.org/
CopyrightCopyright (c) 2013- loveshell
LicenseMIT License
- -感谢ngx_lua模块的开发者[@agentzh](https://github.com/agentzh/),春哥是我所接触过开源精神最好的人 +### nginx lua waf + +##### 参考 +1. https://github.com/loveshell/ngx_lua_waf +2. https://github.com/p0pr0ck5/lua-resty-waf + + +#### 使用 +1. 安装Nginx和lua插件 或者直接安装 openresty +2. git clone +3. nginx.conf 配置文件 + http段 + ... + + lua_package_path "/data/server/nginx/conf/waf/?.lua"; + lua_shared_dict limit 10m; + ... + +4. 在location中使用 + + location / { + access_by_lua ' + local lua_waf = require "core" + local waf = lua_waf:new("default") + waf:set_option("cc_rate", "2/60") + waf:set_option("active", true) + waf:set_option("white_ip_list", {"192.168.128.0/24", "127.0.0.1"}) + waf:run() + '; + ... + } + +5. reload + +#### 说明 +— 默认配置文件 + + _M.defaults = { + active = false, + cc_deny = true, + cc_rate = "100/600", + cc_deny_seconds = 600, + cc_deny_code = 404, + log_path = "/tmp/nginx_waf.log", + white_ip_list = {}, + black_ip_list = {}, + black_return_code = 403, + } + +- 单独设置 + + waf:set_option("cc_rate", "2/60") + +- 问题排查 + nginx error日志 和 log_path + \ No newline at end of file diff --git a/config.lua b/config.lua index 6e66ce6..333bd54 100644 --- a/config.lua +++ b/config.lua @@ -1,45 +1,25 @@ -RulePath = "/usr/local/nginx/conf/waf/wafconf/" -attacklog = "on" -logdir = "/usr/local/nginx/logs/hack/" -UrlDeny="on" -Redirect="on" -CookieMatch="on" -postMatch="on" -whiteModule="on" -black_fileExt={"php","jsp"} -ipWhitelist={"127.0.0.1"} -ipBlocklist={"1.0.0.1"} -CCDeny="off" -CCrate="100/60" -html=[[ - - -网站防火墙 - - +-- +-- Created by IntelliJ IDEA. +-- User: ibuler +-- Date: 16/9/22 +-- Time: 下午7:13 +-- + + +local _M = {} +_M.version = '0.1.0' - -
- - -
-
网站防火墙
-
-

您的请求带有不合法参数,已被网站管理员设置拦截!

-

可能原因:您提交的内容包含危险的攻击请求

-

如何解决:

-
  • 1)检查提交内容;
  • -
  • 2)如网站托管,请联系空间提供商;
  • -
  • 3)普通网站访客,请联系网站管理员;
-
-
-
- -]] +_M.defaults = { + active = false, + cc_deny = true, + cc_rate = "100/600", + cc_deny_seconds = 600, + cc_deny_code = 404, + log_path = "/tmp/nginx_waf.log", + white_ip_list = {}, + black_ip_list = {}, + black_return_code = 403, +} + +return _M diff --git a/core.lua b/core.lua new file mode 100644 index 0000000..b45b8b9 --- /dev/null +++ b/core.lua @@ -0,0 +1,183 @@ +-- +-- Created by IntelliJ IDEA. +-- User: ibuler +-- Date: 16/9/22 +-- Time: 下午7:13 +-- + + +local _M = {} +_M.version = '0.1.0' +log_inited = {} + +local get_headers = ngx.req.get_headers +local config = require "config" +local iputils = require "iputils" +local mt = {__index=_M } +local limit = ngx.shared.limit +local _cidr_cache = {} + +local function get_client_ip() + local ip = get_headers()["X-Real-IP"] + if ip == nil then + ip = ngx.var.remote_addr + end + + if ip == nil then + ip = "unkown" + end + return ip +end + +function _M.table_copy(orig_table) + local copy = {} + + for k, v in pairs(orig_table) do + if type(v) ~= "table" then + copy[k] = v + else + copy[k] = _M.table_copy(v) + end + end + return copy +end + +function _M.new(self, name) + local t = {} + name = name or "" + t["name"] = name + t["config"] = _M.table_copy(config.defaults) + return setmetatable(t, mt) +end + +function _M.set_option(self, key, value) + self["config"][key] = value +end + +function _M.deny_cc(self) + local uri = ngx.var.uri + local max_visit = tonumber(string.match(self.config.cc_rate, '(.*)/')) + local count_period = tonumber(string.match(self.config.cc_rate, '/(.*)')) + local ip = get_client_ip() + + local token = ip..":"..uri + local req, _ = limit:get(token) + + if req then + if req > max_visit then + if self.config.active then + ngx.exit(self.config.cc_deny_code) + return true + else + return false + end + elseif req == max_visit then + self:log("[Deny_cc] Block "..token) + limit:incr(token, 1) + if self.config.active then + ngx.exit(self.config.cc_deny_code) + end + return true + else + limit:incr(token, 1) + end + else + limit:set(token, 1, count_period) + end +end + +function cidr_match(ip, cidr_pattern) + local t = {} + local n = 1 + + if (type(cidr_pattern) ~= "table") then + cidr_pattern = { cidr_pattern } + end + + for _, v in ipairs(cidr_pattern) do + -- try to grab the parsed cidr from out module cache + local cidr = _cidr_cache[v] + + -- if it wasn't there, compute and cache the value + if (not cidr) then + local lower, upper = iputils.parse_cidr(v) + cidr = { lower, upper } + _cidr_cache[v] = cidr + end + + t[n] = cidr + n = n + 1 + end + + return iputils.ip_in_cidrs(ip, t), ip +end + +function _M.log(self, msg) + ngx.log(ngx.WARN, self.config.log_path) + if log_inited[self.config.log_path] == nil then + log_inited[self.config.log_path] = io.open(self.config.log_path, 'a') + end + self.fd = log_inited[self.config.log_path] + if self.config.active then + self.fd:write(ngx.localtime().." [ACTIVE] ".."["..self.name.."] "..msg..'\n') + else + self.fd:write(ngx.localtime().." [MONITOR] ".."["..self.name.."] "..msg..'\n') + end + self.fd:flush() +end + +function _M.in_white_ip_list(self) + local ip = get_client_ip() + local white_ip_token = ip.."white" + local is_white, _ = limit:get(white_ip_token) + + if is_white then + return true + end + + local white_ip_list = self.config.white_ip_list + if next(white_ip_list) ~= nil then + if cidr_match(ip, white_ip_list) then + limit:set(white_ip_token, true, 3600) + self:log("[White_ip] In white list passed: "..ip) + return true + end + end + return false +end + +function _M.in_black_ip_list(self) + local ip = get_client_ip() + local block_ip_token = ip.."block" + local is_block, _ = limit:get(block_ip_token) + + if is_block then + if self.config.active then + ngx.exit(self.config.black_return_code) + end + return true + end + + local black_ip_list = self.config.black_ip_list + if next(black_ip_list) ~= nil then + if cidr_match(ip, black_ip_list) then + limit:set(block_ip_token, true, 3600) + self:log("[Black_ip] In black list denied: "..ip) + if self.config.active then + ngx.exit(self.config.black_return_code) + end + return true + end + end + return false + +end + +function _M.run(self) + if self:in_black_ip_list() then + elseif self:in_white_ip_list() then + elseif self.config.cc_deny and self:deny_cc() then + end +end + +return _M diff --git a/init.lua b/init.lua deleted file mode 100644 index eb44bf0..0000000 --- a/init.lua +++ /dev/null @@ -1,243 +0,0 @@ -require 'config' -local match = string.match -local ngxmatch=ngx.re.match -local unescape=ngx.unescape_uri -local get_headers = ngx.req.get_headers -local optionIsOn = function (options) return options == "on" and true or false end -logpath = logdir -rulepath = RulePath -UrlDeny = optionIsOn(UrlDeny) -PostCheck = optionIsOn(postMatch) -CookieCheck = optionIsOn(cookieMatch) -WhiteCheck = optionIsOn(whiteModule) -PathInfoFix = optionIsOn(PathInfoFix) -attacklog = optionIsOn(attacklog) -CCDeny = optionIsOn(CCDeny) -Redirect=optionIsOn(Redirect) -function getClientIp() - IP = ngx.req.get_headers()["X-Real-IP"] - if IP == nil then - IP = ngx.var.remote_addr - end - if IP == nil then - IP = "unknown" - end - return IP -end -function write(logfile,msg) - local fd = io.open(logfile,"ab") - if fd == nil then return end - fd:write(msg) - fd:flush() - fd:close() -end -function log(method,url,data,ruletag) - if attacklog then - local realIp = getClientIp() - local ua = ngx.var.http_user_agent - local servername=ngx.var.server_name - local time=ngx.localtime() - if ua then - line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..data.."\" \""..ua.."\" \""..ruletag.."\"\n" - else - line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..data.."\" - \""..ruletag.."\"\n" - end - local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log" - write(filename,line) - end -end -------------------------------------规则读取函数------------------------------------------------------------------- -function read_rule(var) - file = io.open(rulepath..'/'..var,"r") - if file==nil then - return - end - t = {} - for line in file:lines() do - table.insert(t,line) - end - file:close() - return(t) -end - -urlrules=read_rule('url') -argsrules=read_rule('args') -uarules=read_rule('user-agent') -wturlrules=read_rule('whiteurl') -postrules=read_rule('post') -ckrules=read_rule('cookie') - - -function say_html() - if Redirect then - ngx.header.content_type = "text/html" - ngx.status = ngx.HTTP_FORBIDDEN - ngx.say(html) - ngx.exit(ngx.status) - end -end - -function whiteurl() - if WhiteCheck then - if wturlrules ~=nil then - for _,rule in pairs(wturlrules) do - if ngxmatch(ngx.var.uri,rule,"isjo") then - return true - end - end - end - end - return false -end -function fileExtCheck(ext) - local items = Set(black_fileExt) - ext=string.lower(ext) - if ext then - for rule in pairs(items) do - if ngx.re.match(ext,rule,"isjo") then - log('POST',ngx.var.request_uri,"-","file attack with ext "..ext) - say_html() - end - end - end - return false -end -function Set (list) - local set = {} - for _, l in ipairs(list) do set[l] = true end - return set -end -function args() - for _,rule in pairs(argsrules) do - local args = ngx.req.get_uri_args() - for key, val in pairs(args) do - if type(val)=='table' then - if val ~= false then - data=table.concat(val, " ") - end - else - data=val - end - if data and type(data) ~= "boolean" and rule ~="" and ngxmatch(unescape(data),rule,"isjo") then - log('GET',ngx.var.request_uri,"-",rule) - say_html() - return true - end - end - end - return false -end - - -function url() - if UrlDeny then - for _,rule in pairs(urlrules) do - if rule ~="" and ngxmatch(ngx.var.request_uri,rule,"isjo") then - log('GET',ngx.var.request_uri,"-",rule) - say_html() - return true - end - end - end - return false -end - -function ua() - local ua = ngx.var.http_user_agent - if ua ~= nil then - for _,rule in pairs(uarules) do - if rule ~="" and ngxmatch(ua,rule,"isjo") then - log('UA',ngx.var.request_uri,"-",rule) - say_html() - return true - end - end - end - return false -end -function body(data) - for _,rule in pairs(postrules) do - if rule ~="" and data~="" and ngxmatch(unescape(data),rule,"isjo") then - log('POST',ngx.var.request_uri,data,rule) - say_html() - return true - end - end - return false -end -function cookie() - local ck = ngx.var.http_cookie - if CookieCheck and ck then - for _,rule in pairs(ckrules) do - if rule ~="" and ngxmatch(ck,rule,"isjo") then - log('Cookie',ngx.var.request_uri,"-",rule) - say_html() - return true - end - end - end - return false -end - -function denycc() - if CCDeny then - local uri=ngx.var.uri - CCcount=tonumber(string.match(CCrate,'(.*)/')) - CCseconds=tonumber(string.match(CCrate,'/(.*)')) - local token = getClientIp()..uri - local limit = ngx.shared.limit - local req,_=limit:get(token) - if req then - if req > CCcount then - ngx.exit(503) - return true - else - limit:incr(token,1) - end - else - limit:set(token,1,CCseconds) - end - end - return false -end - -function get_boundary() - local header = get_headers()["content-type"] - if not header then - return nil - end - - if type(header) == "table" then - header = header[1] - end - - local m = match(header, ";%s*boundary=\"([^\"]+)\"") - if m then - return m - end - - return match(header, ";%s*boundary=([^\",;]+)") -end - -function whiteip() - if next(ipWhitelist) ~= nil then - for _,ip in pairs(ipWhitelist) do - if getClientIp()==ip then - return true - end - end - end - return false -end - -function blockip() - if next(ipBlocklist) ~= nil then - for _,ip in pairs(ipBlocklist) do - if getClientIp()==ip then - ngx.exit(403) - return true - end - end - end - return false -end diff --git a/install.sh b/install.sh deleted file mode 100644 index 40f187c..0000000 --- a/install.sh +++ /dev/null @@ -1,47 +0,0 @@ -mkdir -p /data/src -cd /data/src -if [ ! -x "LuaJIT-2.0.0.tar.gz" ]; then -wget http://luajit.org/download/LuaJIT-2.0.0.tar.gz -fi -tar zxvf LuaJIT-2.0.0.tar.gz -cd LuaJIT-2.0.0 -make -make install PREFIX=/usr/local/lj2 -ln -s /usr/local/lj2/lib/libluajit-5.1.so.2 /lib64/ -cd /data/src -if [ ! -x "v0.2.17rc2.zip" ]; then -wget https://github.com/simpl/ngx_devel_kit/archive/v0.2.17rc2.zip -fi -unzip v0.2.17rc2 -if [ ! -x "v0.7.4.zip" ]; then -wget https://github.com/chaoslawful/lua-nginx-module/archive/v0.7.4.zip -fi -unzip v0.7.4 -cd /data/src -if [ ! -x "pcre-8.10.tar.gz" ]; then -wget http://blog.s135.com/soft/linux/nginx_php/pcre/pcre-8.10.tar.gz -fi -tar zxvf pcre-8.10.tar.gz -cd pcre-8.10/ -./configure -make && make install -cd .. -if [ ! -x "nginx-1.2.4.tar.gz" ]; then -wget 'http://nginx.org/download/nginx-1.2.4.tar.gz' -fi -tar -xzvf nginx-1.2.4.tar.gz -cd nginx-1.2.4/ -export LUAJIT_LIB=/usr/local/lj2/lib/ -export LUAJIT_INC=/usr/local/lj2/include/luajit-2.0/ -./configure --user=daemon --group=daemon --prefix=/usr/local/nginx/ --with-http_stub_status_module --with-http_sub_module --with-http_gzip_static_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --add-module=../ngx_devel_kit-0.2.17rc2/ --add-module=../lua-nginx-module-0.7.4/ -make -j8 -make install -#rm -rf /data/src -cd /usr/local/nginx/conf/ -wget https://github.com/loveshell/ngx_lua_waf/archive/master.zip --no-check-certificate -unzip master.zip -mv ngx_lua_waf-master/* /usr/local/nginx/conf/ -rm -rf ngx_lua_waf-master -rm -rf /data/src -mkdir -p /data/logs/hack -chmod -R 775 /data/logs/hack diff --git a/iputils.lua b/iputils.lua new file mode 100644 index 0000000..e5d1dd2 --- /dev/null +++ b/iputils.lua @@ -0,0 +1,207 @@ +local ipairs, tonumber, tostring, type = ipairs, tonumber, tostring, type +local bit = require("bit") +local tobit = bit.tobit +local lshift = bit.lshift +local band = bit.band +local bor = bit.bor +local xor = bit.bxor +local byte = string.byte +local str_find = string.find +local str_sub = string.sub + +local lrucache = nil + +local _M = { + _VERSION = '0.02', +} + +local mt = { __index = _M } + + +-- Precompute binary subnet masks... +local bin_masks = {} +for i=1,32 do + bin_masks[tostring(i)] = lshift(tobit((2^i)-1), 32-i) +end +-- ... and their inverted counterparts +local bin_inverted_masks = {} +for i=1,32 do + local i = tostring(i) + bin_inverted_masks[i] = xor(bin_masks[i], bin_masks["32"]) +end + +local log_err +if ngx then + log_err = function(...) + ngx.log(ngx.ERR, ...) + end +else + log_err = function(...) + print(...) + end +end + + +local function enable_lrucache(size) + local size = size or 4000 -- Cache the last 4000 IPs (~1MB memory) by default + local lrucache_obj, err = require("resty.lrucache").new(4000) + if not lrucache_obj then + return nil, "failed to create the cache: " .. (err or "unknown") + end + lrucache = lrucache_obj + return true +end +_M.enable_lrucache = enable_lrucache + + +local function split_octets(input) + local pos = 0 + local prev = 0 + local octs = {} + + for i=1, 4 do + pos = str_find(input, ".", prev, true) + if pos then + if i == 4 then + -- Should not have a match after 4 octets + return nil, "Invalid IP" + end + octs[i] = str_sub(input, prev, pos-1) + elseif i == 4 then + -- Last octet, get everything to the end + octs[i] = str_sub(input, prev, -1) + break + else + return nil, "Invalid IP" + end + prev = pos +1 + end + + return octs +end + + +local function ip2bin(ip) + if lrucache then + local get = lrucache:get(ip) + if get then + return get[1], get[2] + end + end + + if type(ip) ~= "string" then + return nil, "IP must be a string" + end + + local octets = split_octets(ip) + if not octets or #octets ~= 4 then + return nil, "Invalid IP" + end + + -- Return the binary representation of an IP and a table of binary octets + local bin_octets = {} + local bin_ip = 0 + + for i,octet in ipairs(octets) do + local bin_octet = tonumber(octet) + if not bin_octet or bin_octet > 255 then + return nil, "Invalid octet: "..tostring(octet) + end + bin_octet = tobit(bin_octet) + bin_octets[i] = bin_octet + bin_ip = bor(lshift(bin_octet, 8*(4-i) ), bin_ip) + end + + if lrucache then + lrucache:set(ip, {bin_ip, bin_octets}) + end + return bin_ip, bin_octets +end +_M.ip2bin = ip2bin + + +local function split_cidr(input) + local pos = str_find(input, "/", 0, true) + if not pos then + return {input} + end + return {str_sub(input, 1, pos-1), str_sub(input, pos+1, -1)} +end + + +local function parse_cidr(cidr) + local mask_split = split_cidr(cidr, '/') + local net = mask_split[1] + local mask = mask_split[2] or "32" + local mask_num = tonumber(mask) + if not mask_num or (mask_num > 32 or mask_num < 1) then + return nil, "Invalid prefix: /"..tostring(mask) + end + + local bin_net, err = ip2bin(net) -- Convert IP to binary + if not bin_net then + return nil, err + end + local bin_mask = bin_masks[mask] -- Get masks + local bin_inv_mask = bin_inverted_masks[mask] + + local lower = band(bin_net, bin_mask) -- Network address + local upper = bor(lower, bin_inv_mask) -- Broadcast address + return lower, upper +end +_M.parse_cidr = parse_cidr + + +local function parse_cidrs(cidrs) + local out = {} + local i = 1 + for _,cidr in ipairs(cidrs) do + local lower, upper = parse_cidr(cidr) + if not lower then + log_err("Error parsing '", cidr, "': ", upper) + else + out[i] = {lower, upper} + i = i+1 + end + end + return out +end +_M.parse_cidrs = parse_cidrs + + +local function ip_in_cidrs(ip, cidrs) + local bin_ip, bin_octets = ip2bin(ip) + if not bin_ip then + return nil, bin_octets + end + + for _,cidr in ipairs(cidrs) do + if bin_ip >= cidr[1] and bin_ip <= cidr[2] then + return true + end + end + return false +end +_M.ip_in_cidrs = ip_in_cidrs + + +local function binip_in_cidrs(bin_ip_ngx, cidrs) + if 4 ~= #bin_ip_ngx then + return false, "invalid IP address" + end + + local bin_ip = 0 + for i=1,4 do + bin_ip = bor(lshift(bin_ip, 8), tobit(byte(bin_ip_ngx, i))) + end + + for _,cidr in ipairs(cidrs) do + if bin_ip >= cidr[1] and bin_ip <= cidr[2] then + return true + end + end + return false +end +_M.binip_in_cidrs = binip_in_cidrs + +return _M diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..aa42dd3 --- /dev/null +++ b/test.lua @@ -0,0 +1,37 @@ +-- +-- Created by IntelliJ IDEA. +-- User: guang +-- Date: 16/9/22 +-- Time: 下午5:59 +-- To change this template use File | Settings | File Templates. +-- + +local _M = {} +_M.version = '0.1.1' + + +local mt = {__index=_M} + +function hello() + print("hello world") +end + +local config = {'hello', 'world' } + + +function _M.new(self, name) + name = name or 0 + return setmetatable({name=name}, mt) +end + +function _M.get_name(self) + print(self.name) +end + +--function _M.get_version() +-- local name = _M.name() +-- print(name) +--end + +return _M + diff --git a/test2.lua b/test2.lua new file mode 100644 index 0000000..c042e75 --- /dev/null +++ b/test2.lua @@ -0,0 +1,66 @@ +-- +-- Created by IntelliJ IDEA. +-- User: guang +-- Date: 16/9/22 +-- Time: 下午6:25 +-- To change this template use File | Settings | File Templates. +-- + + +--local lua_waf = require "core" +local lua_waf = require "test" +local waf = lua_waf:new("test") +local _cidr_cache = {} + +print(waf.name) +local iputils = require "iputils" + +function cidr_match(ip, cidr_pattern) + local t = {} + local n = 1 + + if (type(cidr_pattern) ~= "table") then + cidr_pattern = { cidr_pattern } + end + + for _, v in ipairs(cidr_pattern) do + -- try to grab the parsed cidr from out module cache + local cidr = _cidr_cache[v] + + -- if it wasn't there, compute and cache the value + if (not cidr) then + local lower, upper = iputils.parse_cidr(v) + cidr = { lower, upper } + _cidr_cache[v] = cidr + end + + t[n] = cidr + n = n + 1 + end + + return iputils.ip_in_cidrs(ip, t), ip +end + +a = cidr_match('192.168.128.230', {'192.168.128.0/24', '127.0.0.1'}) + +print(a) + +a = cidr_match('172.16.1.1', {'172.16.1.2'}) +print(a) + +--for k, v in pairs(waf["config"]) do +-- print(k, v) +--end +-- +--waf:set_option("active", true) +-- +--for k, v in pairs(waf["config"]) do +-- pritt(k, v) +--end +--print(waf.config.active) +-- +-- waf:deny_cc() +-- waf2:deny_cc() +--waf:log("hello world") +--waf2:log("world") +--waf:get_name() diff --git a/waf.lua b/waf.lua deleted file mode 100644 index bc9821c..0000000 --- a/waf.lua +++ /dev/null @@ -1,85 +0,0 @@ -local content_length=tonumber(ngx.req.get_headers()['content-length']) -local method=ngx.req.get_method() -local ngxmatch=ngx.re.match -if whiteip() then -elseif blockip() then -elseif denycc() then -elseif ngx.var.http_Acunetix_Aspect then - ngx.exit(444) -elseif ngx.var.http_X_Scan_Memo then - ngx.exit(444) -elseif whiteurl() then -elseif ua() then -elseif url() then -elseif args() then -elseif cookie() then -elseif PostCheck then - if method=="POST" then - local boundary = get_boundary() - if boundary then - local len = string.len - local sock, err = ngx.req.socket() - if not sock then - return - end - ngx.req.init_body(128 * 1024) - sock:settimeout(0) - local content_length = nil - content_length=tonumber(ngx.req.get_headers()['content-length']) - local chunk_size = 4096 - if content_length < chunk_size then - chunk_size = content_length - end - local size = 0 - while size < content_length do - local data, err, partial = sock:receive(chunk_size) - data = data or partial - if not data then - return - end - ngx.req.append_body(data) - if body(data) then - return true - end - size = size + len(data) - local m = ngxmatch(data,[[Content-Disposition: form-data;(.+)filename="(.+)\\.(.*)"]],'ijo') - if m then - fileExtCheck(m[3]) - filetranslate = true - else - if ngxmatch(data,"Content-Disposition:",'isjo') then - filetranslate = false - end - if filetranslate==false then - if body(data) then - return true - end - end - end - local less = content_length - size - if less < chunk_size then - chunk_size = less - end - end - ngx.req.finish_body() - else - ngx.req.read_body() - local args = ngx.req.get_post_args() - if not args then - return - end - for key, val in pairs(args) do - if type(val) == "table" then - data=table.concat(val, ", ") - else - data=val - end - if data and type(data) ~= "boolean" and body(data) then - return true - end - end - end - end -else - return -end diff --git a/wafconf/args b/wafconf/args deleted file mode 100644 index d5bf8e8..0000000 --- a/wafconf/args +++ /dev/null @@ -1,22 +0,0 @@ -\.\./ -\:\$ -\$\{ -select.+(from|limit) -(?:(union(.*?)select)) -having|rongjitest -sleep\((\s*)(\d*)(\s*)\) -benchmark\((.*)\,(.*)\) -base64_decode\( -(?:from\W+information_schema\W) -(?:(?:current_)user|database|schema|connection_id)\s*\( -(?:etc\/\W*passwd) -into(\s+)+(?:dump|out)file\s* -group\s+by.+\( -xwork.MethodAccessor -(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( -xwork\.MethodAccessor -(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ -java\.lang -\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ -\<(iframe|script|body|img|layer|div|meta|style|base|object|input) -(onmouseover|onerror|onload)\= diff --git a/wafconf/cookie b/wafconf/cookie deleted file mode 100644 index 30554ca..0000000 --- a/wafconf/cookie +++ /dev/null @@ -1,20 +0,0 @@ -\.\./ -\:\$ -\$\{ -select.+(from|limit) -(?:(union(.*?)select)) -having|rongjitest -sleep\((\s*)(\d*)(\s*)\) -benchmark\((.*)\,(.*)\) -base64_decode\( -(?:from\W+information_schema\W) -(?:(?:current_)user|database|schema|connection_id)\s*\( -(?:etc\/\W*passwd) -into(\s+)+(?:dump|out)file\s* -group\s+by.+\( -xwork.MethodAccessor -(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( -xwork\.MethodAccessor -(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ -java\.lang -\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ diff --git a/wafconf/post b/wafconf/post deleted file mode 100644 index 87d0946..0000000 --- a/wafconf/post +++ /dev/null @@ -1,19 +0,0 @@ -select.+(from|limit) -(?:(union(.*?)select)) -having|rongjitest -sleep\((\s*)(\d*)(\s*)\) -benchmark\((.*)\,(.*)\) -base64_decode\( -(?:from\W+information_schema\W) -(?:(?:current_)user|database|schema|connection_id)\s*\( -(?:etc\/\W*passwd) -into(\s+)+(?:dump|out)file\s* -group\s+by.+\( -xwork.MethodAccessor -(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( -xwork\.MethodAccessor -(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ -java\.lang -\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ -\<(iframe|script|body|img|layer|div|meta|style|base|object|input) -(onmouseover|onerror|onload)\= diff --git a/wafconf/url b/wafconf/url deleted file mode 100644 index 31130d3..0000000 --- a/wafconf/url +++ /dev/null @@ -1,6 +0,0 @@ -\.(svn|htaccess|bash_history) -\.(bak|inc|old|mdb|sql|backup|java|class)$ -(vhost|bbs|host|wwwroot|www|site|root|hytop|flashfxp).*\.rar -(phpmyadmin|jmx-console|jmxinvokerservlet) -java\.lang -/(attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|inc|forumdata|upload|includes|cache|avatar)/(\\w+).(php|jsp) diff --git a/wafconf/user-agent b/wafconf/user-agent deleted file mode 100644 index f929be2..0000000 --- a/wafconf/user-agent +++ /dev/null @@ -1 +0,0 @@ -(HTTrack|harvest|audit|dirbuster|pangolin|nmap|sqln|-scan|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|PycURL|zmeu|BabyKrokodil|netsparker|httperf|bench| SF/) diff --git a/wafconf/whiteurl b/wafconf/whiteurl deleted file mode 100644 index 4e3c654..0000000 --- a/wafconf/whiteurl +++ /dev/null @@ -1 +0,0 @@ -^/123/$