diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3ce7fdb..dbefb79 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,9 +2,21 @@ + + + + + + + + + + + + + - @@ -26,22 +38,38 @@ - - + + - - + + + + + + + + + + + + + + + + + + - - + + @@ -50,8 +78,8 @@ - - + + @@ -60,8 +88,8 @@ - - + + @@ -70,8 +98,8 @@ - - + + @@ -96,10 +124,11 @@ @@ -110,10 +139,10 @@ DEFINITION_ORDER - @@ -276,6 +305,12 @@ + project + + + + + @@ -421,27 +456,27 @@ - + - + - - - - - - + + + + + + - - + + - - + + @@ -461,7 +496,7 @@ - + @@ -469,18 +504,34 @@ - + - - + + - + - + + + + + + + + + + + + + + + + + @@ -493,45 +544,166 @@ - + - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..75a32bf --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +### 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 a3b4d48..333bd54 100644 --- a/config.lua +++ b/config.lua @@ -11,7 +11,6 @@ _M.version = '0.1.0' _M.defaults = { - debug = true, active = false, cc_deny = true, cc_rate = "100/600", diff --git a/core.lua b/core.lua index 2a64bda..b45b8b9 100644 --- a/core.lua +++ b/core.lua @@ -15,6 +15,7 @@ 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"] @@ -71,11 +72,11 @@ function _M.deny_cc(self) 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 - self:log("[Deny_cc] Block "..token) - limit:incr(token, 1) return true else limit:incr(token, 1) @@ -85,6 +86,32 @@ function _M.deny_cc(self) 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 @@ -110,12 +137,10 @@ function _M.in_white_ip_list(self) local white_ip_list = self.config.white_ip_list if next(white_ip_list) ~= nil then - for _, wip in pairs(white_ip_list) do - if ip == wip or iputils.ip_in_cidrs(ip, wip) 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 end return false @@ -135,15 +160,13 @@ function _M.in_black_ip_list(self) local black_ip_list = self.config.black_ip_list if next(black_ip_list) ~= nil then - for _, bip in pairs(black_ip_list) do - if ip == bip or iputils.ip_in_cidrs(ip, bip) 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 end return false diff --git a/old/README.md b/old/README.md deleted file mode 100644 index 27bf878..0000000 --- a/old/README.md +++ /dev/null @@ -1,28 +0,0 @@ -## ngx_lua_waf -基于 loveshell [nginx-lua-waf](https://github.com/loveshell/ngx_lua_waf)更改 - -### 使用方法: -1. nginx安装lua模块,不再详述 -2. 下载模块 - cd /data/server/nginx/conf - git clone https://github.com/ibuler/ngx_lua_waf.git waf -2. nginx.conf 添加参数 - lua_package_path /data/server/nginx/conf/waf/?.lua; # 模块位置 - lua_shared_dict limit 10m; # 设置lua使用内存, 根据访问量设置合适值 -3. location或server设置访问控制 - access_by_lua_file /data/server/nginx/conf/waf/entry.lua; # 可以copy一份到不同的配置中,单独修改其配置文件 - -## 文件说明 -- config.lua 默认配置文件 -- entry.lua access控制入口样例文件 -- init.lua 函数所在文件,都会调用该文件 -- wafconf 暂时没有使用,将来开发继续完成 - - -### 参数说明 -- debug: 调试阶段开始debug,显示debug信息 -- cc_deny: 开启cc_deny,控制访问量 -- cc_rate: 10/60 意思为 60s内访问10次,超过频率会被block掉 -- cc_deny_seconds: 达到阈值后,禁止访问的时间 - - diff --git a/old/config.lua b/old/config.lua deleted file mode 100644 index 67d330c..0000000 --- a/old/config.lua +++ /dev/null @@ -1,23 +0,0 @@ -debug = false --- rule_path = "/data/server/nginx/conf/waf/wafconf/" --- url_check = false --- url_write_check = false --- args_check = false --- ua_check = false --- ua_write_check = false --- cookie_check = false --- post_check = false - --- black_file_ext = {"php", "jsp"} --- attack_log = false --- attach_log_dir = "/data/logs/waf/" - --- redirect = false --- redirect_url = "http://www.baidu.com" -ip_check = false -ip_white_list = {} -- {'192.168.1.*', '127.0.0.1'} -ip_black_list = {} -- {'0.0.0.0', '106.2.34.29'} - -cc_deny = false -cc_rate = "100/60" -cc_deny_seconds = "600" diff --git a/old/entry.lua b/old/entry.lua deleted file mode 100644 index 7bcea95..0000000 --- a/old/entry.lua +++ /dev/null @@ -1,16 +0,0 @@ ---------- Global default config ------- -require 'config' ---------- Local config setting -------- -debug = true - -cc_deny = false -cc_rate = '10/60' -ip_check = true -ip_white_list = {} -ip_black_list = {} - ---------- Access control limit -------- -if ip_check and (whiteIP(ip_white_list, debug) or blackIP(ip_black_list, debug)) then -elseif cc_deny and denyCC(cc_rate, cc_deny_seconds, debug) then -else return -end diff --git a/old/init.lua b/old/init.lua deleted file mode 100644 index 7853b02..0000000 --- a/old/init.lua +++ /dev/null @@ -1,313 +0,0 @@ -local match = string.match -local ngx_match = ngx.re.match -local unescape = ngx.unescape_uri -local get_headers = ngx.req.get_headers - -function getClientIp() - IP = 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, tag) - if attack_log 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.."\" \""..tag.."\"\n" - else - line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..data.."\" - \""..tag.."\"\n" - end - local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log" - write(filename, line) - end -end - ------------------------------------- 规则读取函数 ----------------------------------------- --- function readRule(var) --- file = io.open(rule_path..'/'..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 - --- url_rules = readRule('url') --- white_url_rules = readRule('white_url') --- args_rules = readRule('args') --- ua_rules = readRule('user_agent') --- post_rules = readRule('post') --- cookie_rules = readRule('cookie') - - -function debugSay(msg, debug) - if debug then - ngx.header.content_type = "text/html" - ngx.status = ngx.HTTP_FORBIDDEN - ngx.say(msg) - ngx.exit(ngx.status) - end -end - - --- function whiteURLCheck() --- if white_url_rules ~= nil then --- for _, rule in pairs(white_url_rules) do --- if ngx_match(ngx.var.uri, rule, "isjo") then --- return true --- end --- end --- end --- return false --- end - - --- function fileExtCheck(ext, black_file_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 --- if attack_log then --- log('POST',ngx.var.request_uri,"-","file attack with ext "..ext) --- end - --- if debug then --- debugSay(ngx.var.request_uri.."-".."file attack with ext: "..ext) --- end --- 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 checkArgs() --- for _, rule in pairs(args_rules) 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 ngx_match(unescape(data), rule, "isjo") then --- log('GET', ngx.var.request_uri, "-", rule) --- debugSay(ngx.var.request_uri.."-"..rule) --- 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(cc_rate, cc_deny_seconds, debug) - local uri = ngx.var.uri - cc_count = tonumber(string.match(cc_rate, '(.*)/')) - cc_seconds = tonumber(string.match(cc_rate, '/(.*)')) - local token = getClientIp()..uri - local limit = ngx.shared.limit - local req, _ = limit:get(token) -- 127.0.0.1_/price/v1.0: 10 - local ip = getClientIp() - local block, _ = limit:get(ip) -- 127.0.0.1: 1 - - if block then - if debug then - ngx.say('Deny by waf.') - ngx.exit('200') - return true - else - ngx.exit(404) - end - end - - if req then - if req > cc_count then - limit:set(ip, 1, cc_deny_seconds) - ngx.exit(404) - return true - else - limit:incr(token, 1) - end - else - limit:set(token, 1, cc_seconds) - 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 string.split(str, delimiter) - if str==nil or str=='' or delimiter==nil then - return nil - end - - local result = {} - for match in (str..delimiter):gmatch("(.-)"..delimiter) do - table.insert(result, match) - end - return result -end - - -function innet(ip, network) - local star = '' - for i in string.gmatch(network, '%*') do - star = star..i - end - - local ip = string.split(ip, '%.') - local network = string.split(network, '%.') - if ip == nil or network == nil then - return false - end - - local ip_prefix = {} - local network_prefix = {} - for i=1, 4-string.len(star) do - ip_prefix[i] = ip[i] - network_prefix[i] = network[i] - end - - ip_prefix = table.concat(ip_prefix, '.') - network_prefix = table.concat(network_prefix, '.') - - if ip_prefix == network_prefix then - return true - else - return false - end -end - -function whiteIP(ip_white_list, debug) - if next(ip_white_list) ~= nil then - ip = getClientIp() - for _, wip in pairs(ip_white_list) do - if ip == wip or innet(ip, wip) then - if debug then - ngx.say(ip.." in white list
") - end - return true - end - end - end - return false -end - -function blackIP(ip_black_list, debug) - if next(ip_black_list) ~= nil then - ip = getClientIp() - for _, bip in pairs(ip_black_list) do - if ip == bip or ip == "0.0.0.0" or innet(ip, bip) then - if debug then - ngx.say(ip.." in black list
") - end - ngx.exit(403) - return true - end - end - end - return false -end diff --git a/old/logger.lua b/old/logger.lua deleted file mode 100644 index e69de29..0000000 diff --git a/old/waf.lua b/old/waf.lua deleted file mode 100644 index e69de29..0000000 diff --git a/old/wafconf/args b/old/wafconf/args deleted file mode 100644 index d5bf8e8..0000000 --- a/old/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/old/wafconf/cookie b/old/wafconf/cookie deleted file mode 100644 index 30554ca..0000000 --- a/old/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/old/wafconf/post b/old/wafconf/post deleted file mode 100644 index 87d0946..0000000 --- a/old/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/old/wafconf/url b/old/wafconf/url deleted file mode 100644 index 67e621f..0000000 --- a/old/wafconf/url +++ /dev/null @@ -1,6 +0,0 @@ -\.(svn|htaccess|bash_history|git) -\.(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/old/wafconf/user_agent b/old/wafconf/user_agent deleted file mode 100644 index f929be2..0000000 --- a/old/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/old/wafconf/white_url b/old/wafconf/white_url deleted file mode 100644 index 4e3c654..0000000 --- a/old/wafconf/white_url +++ /dev/null @@ -1 +0,0 @@ -^/123/$ diff --git a/test2.lua b/test2.lua index 7514b3a..c042e75 100644 --- a/test2.lua +++ b/test2.lua @@ -10,8 +10,44 @@ --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