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..7d4efb8 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1474616676553 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config.lua b/config.lua index 67d330c..ee3056b 100644 --- a/config.lua +++ b/config.lua @@ -1,23 +1,26 @@ -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 +-- +-- Created by IntelliJ IDEA. +-- User: guang +-- Date: 16/9/23 +-- Time: 下午4:28 +-- To change this template use File | Settings | File Templates. +-- --- black_file_ext = {"php", "jsp"} --- attack_log = false --- attach_log_dir = "/data/logs/waf/" +local _M = {} +_M.version = '0.1.0' --- 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" +_M.defaults = { + debug = true, + active = false, + cc_deny = true, + cc_rate = "100/600", + cc_deny_seconds = 600, + cc_deny_code = 404, + log_path = "/tmp/nginx_waf.log", + ip_check= true, + ip_white_list = nil, + ip_black_list = nil, +} + +return _M diff --git a/core.lua b/core.lua new file mode 100644 index 0000000..590992d --- /dev/null +++ b/core.lua @@ -0,0 +1,95 @@ +-- +-- Created by IntelliJ IDEA. +-- User: guang +-- Date: 16/9/22 +-- Time: 下午7:13 +-- To change this template use File | Settings | File Templates. +-- + + +local _M = {} +_M.version = '0.1.0' +log_inited = {} + +local get_headers = ngx.req.get_headers +local config = require "config" +local mt = {__index=_M } + +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 = {} + 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 limit = ngx.shared.limit + local req, _ = limit:get(token) + + if req then + if req > max_visit then + ngx.exit(self.config.cc_deny_code) + return true + elseif req == max_visit then + self:log("[Block] " .. token) + limit:incr(token, 1) + else + limit:incr(token, 1) + end + else + limit:set(token, 1, count_period) + end +end + +function _M.log(self, msg) + if log_inited[self.config.log_path] == nil then + log_inited[self.config.log_path] = io.open(self.config.log_path, 'ab') + end + self.fd = log_inited[self.config.log_path] + + self.fd:write(msg .. '\n') + self.fd:flush() +end + +function _M.run(self) + ngx.log(ngx.WARN, 'Start running waf') + if self.config.cc_deny and self:deny_cc() then + end +end + +return _M 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/README.md b/old/README.md similarity index 100% rename from README.md rename to old/README.md diff --git a/old/config.lua b/old/config.lua new file mode 100644 index 0000000..67d330c --- /dev/null +++ b/old/config.lua @@ -0,0 +1,23 @@ +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/entry.lua b/old/entry.lua similarity index 88% rename from entry.lua rename to old/entry.lua index f4aaa4b..7bcea95 100644 --- a/entry.lua +++ b/old/entry.lua @@ -9,8 +9,6 @@ ip_check = true ip_white_list = {} ip_black_list = {} ---------- Init project ---------------- -require 'init' --------- 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 diff --git a/init.lua b/old/init.lua similarity index 100% rename from init.lua rename to old/init.lua diff --git a/old/logger.lua b/old/logger.lua new file mode 100644 index 0000000..e69de29 diff --git a/old/waf.lua b/old/waf.lua new file mode 100644 index 0000000..e69de29 diff --git a/wafconf/args b/old/wafconf/args similarity index 100% rename from wafconf/args rename to old/wafconf/args diff --git a/wafconf/cookie b/old/wafconf/cookie similarity index 100% rename from wafconf/cookie rename to old/wafconf/cookie diff --git a/wafconf/post b/old/wafconf/post similarity index 100% rename from wafconf/post rename to old/wafconf/post diff --git a/wafconf/url b/old/wafconf/url similarity index 100% rename from wafconf/url rename to old/wafconf/url diff --git a/wafconf/user_agent b/old/wafconf/user_agent similarity index 100% rename from wafconf/user_agent rename to old/wafconf/user_agent diff --git a/wafconf/white_url b/old/wafconf/white_url similarity index 100% rename from wafconf/white_url rename to old/wafconf/white_url diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..480f809 --- /dev/null +++ b/test.lua @@ -0,0 +1,41 @@ +-- +-- 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 util = require "resty.waf.util" + +local mt = {__index=_M} + +function hello() + print("hello world") +end + +local config = {'hello', 'world' } + +local _a = {} + + +function _M:new() + return setmetatable({}, mt) +end + +function _M:name() + local name = {'guang', 'hong', 'wei' } + name_new = util.table_copy(name) + print(table.concat(name_new, ',')) +end + +function _M.get_version() + local name = _M.name() + print(name) +end + +return _a + diff --git a/test2.lua b/test2.lua new file mode 100644 index 0000000..5cdd09d --- /dev/null +++ b/test2.lua @@ -0,0 +1,31 @@ +-- +-- 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 iputils = require "iputils" + +local waf = lua_waf:new("test") +local waf2 = lua_waf:new("jj") + +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 + print(k, v) +end +print(waf.config.active) + +-- waf:deny_cc() +-- waf2:deny_cc() +waf:log("hello world") +waf2:log("world") +print(iputils.ip2bin("192.168.1.1"))