diff --git a/lib/resty/waf.lua b/lib/resty/waf.lua index 0b22841d..38bab551 100644 --- a/lib/resty/waf.lua +++ b/lib/resty/waf.lua @@ -13,7 +13,6 @@ local storage = require "resty.waf.storage" local transform_t = require "resty.waf.transform" local translate = require "resty.waf.translate" local util = require "resty.waf.util" - local table_insert = table.insert local table_sort = table.sort local string_lower = string.lower @@ -88,8 +87,8 @@ local function _parse_collection(self, collection, var) return collection end - local key = parse[1] - local value = parse[2] + local key = parse[1]--"all" + local value = parse[2]-- 1 -- if this var has an ignore, we need to copy this collection table -- as we're going to be removing some of its elements, so we can no @@ -116,10 +115,12 @@ end -- all event logs will be written out at the completion of the transaction if either: -- 1. the transaction was altered (e.g. a rule matched with an ACCEPT or DENY action), or -- 2. the event_log_altered_only option is unset -local function _log_event(self, rule, value, ctx) +local function _log_event(self, rule, value, ctx, match_var, match_var_name) local t = { id = rule.id, - match = value + match = value, + match_var = match_var, + match_var_name= match_var_name, } if rule.msg then @@ -209,7 +210,7 @@ local function _do_transform(self, collection, transform) return collection -- dont transform if the collection was nil, i.e. a specific arg key dne end - --_LOG_"doing transform of type " .. transform .. " on collection value " .. tostring(collection) + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "doing transform of type " .. transform .. " on collection value " .. tostring(collection)) end return transform_t.lookup[transform](self, collection) end end @@ -225,10 +226,10 @@ local function _build_collection(self, rule, var, collections, ctx, opts) local collection_key = var.collection_key local collection - --_LOG_"Checking for collection_key " .. collection_key + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Checking for collection_key " .. collection_key) end if not var.storage and not ctx.transform_key[collection_key] then - --_LOG_"Collection cache miss" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Collection cache miss") end collection = _parse_collection(self, collections[var.type], var) if opts.transform then @@ -238,10 +239,10 @@ local function _build_collection(self, rule, var, collections, ctx, opts) ctx.transform[collection_key] = collection ctx.transform_key[collection_key] = true elseif var.storage then - --_LOG_"Forcing cache miss" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Forcing cache miss") end collection = _parse_collection(self, collections[var.type], var) else - --_LOG_"Collection cache hit!" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Collection cache hit!") end collection = ctx.transform[collection_key] end @@ -278,23 +279,22 @@ local function _process_rule(self, rule, collections, ctx) end local collection = _build_collection(self, rule, var, collections, ctx, opts) - if not collection then - --_LOG_"No values for this collection" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "No values for this collection") end offset = rule.offset_nomatch else if opts.parsepattern then - --_LOG_"Parsing dynamic pattern: " .. pattern + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Parsing dynamic pattern: " .. pattern) end pattern = util.parse_dynamic_value(self, pattern, collections) end - local match, value + local match, value, match_var if var.unconditional then match = true value = 1 else - match, value = operators.lookup[rule.operator](self, collection, pattern, ctx) + match, value, match_var = operators.lookup[rule.operator](self, collection, pattern, ctx) end if rule.op_negated then @@ -302,12 +302,11 @@ local function _process_rule(self, rule, collections, ctx) end if match then - --_LOG_"Match of rule " .. rule.id + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Match of rule " .. rule.id) end -- store this match as the most recent match collections.MATCHED_VAR = value or '' collections.MATCHED_VAR_NAME = var.type - -- also add the match to our list of matches for the transaction if value then local match_n = ctx.match_n + 1 @@ -336,7 +335,8 @@ local function _process_rule(self, rule, collections, ctx) -- log the event if rule.actions.disrupt ~= "CHAIN" and not opts.nolog then - _log_event(self, rule, value, ctx) + + _log_event(self, rule, value, ctx, match_var, collections.MATCHED_VAR_NAME) end -- wrapper for the rules action @@ -351,7 +351,7 @@ local function _process_rule(self, rule, collections, ctx) end end - --_LOG_"Returning offset " .. tostring(offset) + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Returning offset " .. tostring(offset)) end return offset end @@ -383,12 +383,12 @@ local function _merge_rulesets(self) local ignored = self._ignore_ruleset for k, v in ipairs(added) do - --_LOG_"Adding ruleset " .. v + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Adding ruleset " .. v) end t[v] = true end for k, v in pairs(added_s) do - --_LOG_"Adding ruleset string " .. k + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Adding ruleset string " .. k) end if not _ruleset_defs[k] then local rs, err = util.parse_ruleset(v) @@ -396,7 +396,7 @@ local function _merge_rulesets(self) if err then logger.fatal_fail("Could not load " .. k) else - --_LOG_"Doing offset calculation of " .. k + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Doing offset calculation of " .. k) end _calculate_offset(rs) _ruleset_defs[k] = rs @@ -410,7 +410,7 @@ local function _merge_rulesets(self) end for k, v in ipairs(ignored) do - --_LOG_"Ignoring ruleset " .. v + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Ignoring ruleset " .. v) end t[v] = nil end end @@ -428,7 +428,7 @@ end -- main entry point function _M.exec(self, opts) if self._mode == "INACTIVE" then - --_LOG_"Operational mode is INACTIVE, not running" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Operational mode is INACTIVE, not running") end return end @@ -462,7 +462,7 @@ function _M.exec(self, opts) -- see https://groups.google.com/forum/#!topic/openresty-en/LVR9CjRT5-Y -- also https://github.com/p0pr0ck5/lua-resty-waf/issues/229 if ctx.altered == true and self._mode == 'ACTIVE' then - --_LOG_"Transaction was already altered, not running!" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Transaction was already altered, not running!") end if phase == 'log' then self:write_log_events(true, ctx) @@ -509,10 +509,10 @@ function _M.exec(self, opts) self._storage_redis_setkey = {} end - --_LOG_"Beginning run of phase " .. phase + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Beginning run of phase " .. phase) end for _, ruleset in ipairs(self._active_rulesets) do - --_LOG_"Beginning ruleset " .. ruleset + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Beginning ruleset " .. ruleset) end local rs = _ruleset_defs[ruleset] @@ -523,7 +523,7 @@ function _M.exec(self, opts) if err then logger.fatal_fail(err) else - --_LOG_"Doing offset calculation of " .. ruleset + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Doing offset calculation of " .. ruleset) end _calculate_offset(rs) _ruleset_defs[ruleset] = rs @@ -538,7 +538,7 @@ function _M.exec(self, opts) while rule do if not util.table_has_key(rule.id, self._ignore_rule) then - --_LOG_"Processing rule " .. rule.id + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Processing rule " .. rule.id) end local returned_offset = _process_rule(self, rule, collections, ctx) if returned_offset then @@ -547,7 +547,7 @@ function _M.exec(self, opts) offset = nil end else - --_LOG_"Ignoring rule " .. rule.id + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Ignoring rule " .. rule.id) end local rule_nomatch = rule.offset_nomatch @@ -807,12 +807,12 @@ function _M.write_log_events(self, has_ctx, ctx) end if ctx.altered ~= true and self._event_log_altered_only then - --_LOG_"Not logging a request that wasn't altered" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Not logging a request that wasn't altered") end return end if ctx.log_entries_n == 0 then - --_LOG_"Not logging a request that had no rule alerts" + if self._debug == true then ngx.log(self._debug_log_level, '[', self.transaction_id, '] ', "Not logging a request that had no rule alerts") end return end diff --git a/lib/resty/waf/operators.lua b/lib/resty/waf/operators.lua index de3413b4..5ea21426 100644 --- a/lib/resty/waf/operators.lua +++ b/lib/resty/waf/operators.lua @@ -8,7 +8,6 @@ local iputils = require "resty.iputils" local libinject = require "resty.libinjection" local logger = require "resty.waf.log" local util = require "resty.waf.util" - local string_find = string.find local string_gsub = string.gsub local string_sub = string.sub @@ -24,12 +23,12 @@ local _cidr_cache = {} _M.version = base.version function _M.equals(a, b) - local equals, value - + local equals, value, reason = a if type(a) == "table" then for _, v in ipairs(a) do equals, value = _M.equals(v, b) if equals then + reason = v break end end @@ -41,16 +40,17 @@ function _M.equals(a, b) end end - return equals, value + return equals, value, reason end function _M.greater(a, b) - local greater, value + local greater, value, reason = a if type(a) == "table" then for _, v in ipairs(a) do greater, value = _M.greater(v, b) if greater then + reason = v break end end @@ -62,16 +62,17 @@ function _M.greater(a, b) end end - return greater, value + return greater, value, reason end function _M.less(a, b) - local less, value + local less, value, reason = a if type(a) == "table" then for _, v in ipairs(a) do less, value = _M.less(v, b) if less then + reason = v break end end @@ -83,16 +84,17 @@ function _M.less(a, b) end end - return less, value + return less, value, reason end function _M.greater_equals(a, b) - local greater_equals, value + local greater_equals, value, reason = a if type(a) == "table" then for _, v in ipairs(a) do greater_equals, value = _M.greater_equals(v, b) if greater_equals then + reason = v break end end @@ -104,16 +106,17 @@ function _M.greater_equals(a, b) end end - return greater_equals, value + return greater_equals, value, reason end function _M.less_equals(a, b) - local less_equals, value + local less_equals, value, reason = a if type(a) == "table" then for _, v in ipairs(a) do less_equals, value = _M.less_equals(v, b) if less_equals then + reason = v break end end @@ -125,17 +128,18 @@ function _M.less_equals(a, b) end end - return less_equals, value + return less_equals, value, reason end function _M.exists(needle, haystack) - local exists, value + local exists, value, reason = needle if type(needle) == "table" then for _, v in ipairs(needle) do exists, value = _M.exists(v, haystack) if exists then + reason = v break end end @@ -147,17 +151,18 @@ function _M.exists(needle, haystack) end end - return exists, value + return exists, value, reason end function _M.contains(haystack, needle) - local contains, value + local contains, value, reason = needle if type(needle) == "table" then for _, v in ipairs(needle) do contains, value = _M.contains(haystack, v) if contains then + reason = v break end end @@ -169,17 +174,18 @@ function _M.contains(haystack, needle) end end - return contains, value + return contains, value, reason end function _M.str_find(waf, subject, pattern) - local from, to, match, value + local from, to, match, value, reason = subject if type(subject) == "table" then for _, v in ipairs(subject) do match, value = _M.str_find(waf, v, pattern) if match then + reason = v break end end @@ -192,18 +198,19 @@ function _M.str_find(waf, subject, pattern) end end - return match, value + return match, value, reason end function _M.regex(waf, subject, pattern) local opts = waf._pcre_flags - local captures, err, match + local captures, err, match, reason = subject if type(subject) == "table" then for _, v in ipairs(subject) do match, captures = _M.regex(waf, v, pattern) if match then + reason = v break end end @@ -219,18 +226,19 @@ function _M.regex(waf, subject, pattern) end end - return match, captures + return match, captures, reason end function _M.refind(waf, subject, pattern) local opts = waf._pcre_flags - local from, to, err, match + local from, to, err, match, reason = subject if type(subject) == "table" then for _, v in ipairs(subject) do match, from = _M.refind(waf, v, pattern) if match then + reason = v break end end @@ -246,12 +254,12 @@ function _M.refind(waf, subject, pattern) end end - return match, from + return match, from, reason end function _M.ac_lookup(needle, haystack, ctx) local id = ctx.id - local match, _ac, value + local match, _ac, value, reason = needle -- dictionary creation is expensive, so we use the id of -- the rule as the key to cache the created dictionary @@ -267,6 +275,7 @@ function _M.ac_lookup(needle, haystack, ctx) match, value = _M.ac_lookup(v, haystack, ctx) if match then + reason = v break end end @@ -279,12 +288,13 @@ function _M.ac_lookup(needle, haystack, ctx) end end - return match, value + return match, value, reason end function _M.cidr_match(ip, cidr_pattern) local t = {} local n = 1 + local reason = ip if type(cidr_pattern) ~= "table" then cidr_pattern = { cidr_pattern } @@ -305,15 +315,15 @@ function _M.cidr_match(ip, cidr_pattern) n = n + 1 end - return iputils.ip_in_cidrs(ip, t), ip + return iputils.ip_in_cidrs(ip, t), ip, reason end function _M.rbl_lookup(waf, ip, rbl_srv, ctx) local nameservers = ctx.nameservers - + local reason = ip if type(nameservers) ~= 'table' then -- user probably didnt configure nameservers via set_option - return false, nil + return false, nil, reason end local resolver, err = dns:new({ @@ -322,7 +332,7 @@ function _M.rbl_lookup(waf, ip, rbl_srv, ctx) if not resolver then logger.warn(waf, err) - return false, nil + return false, nil, reason end -- id for unit test @@ -332,83 +342,89 @@ function _M.rbl_lookup(waf, ip, rbl_srv, ctx) if not rbl_query then -- we were handed something that didn't look like an IPv4 - return false, nil + return false, nil, reason end local answers, err = resolver:query(rbl_query) if not answers then logger.warn(waf, err) - return false, nil + return false, nil, reason end if answers.errcode == 3 then -- errcode 3 means no lookup, so return false - return false, nil + return false, nil, reason elseif answers.errcode then -- we had some other type of err that we should know about logger.warn(waf, "rbl lookup failure: " .. answers.errstr .. " (" .. answers.errcode .. ")") - return false, nil + return false, nil, reason else -- we got a dns response, for now we're only going to return the first entry local i, answer = next(answers) if answer and type(answer) == 'table' then - return true, answer.address or answer.cname + return true, answer.address or answer.cname, reason else -- we didnt have any valid answers - return false, nil + return false, nil, reason end end end function _M.detect_sqli(input) + local reason = input if type(input) == 'table' then for _, v in ipairs(input) do local match, value = _M.detect_sqli(v) if match then - return match, value + reason = v + return match, value, reason end end else -- yes this is really just one line -- libinjection.sqli has the same return values that lookup.operators expects - return libinject.sqli(input) + return libinject.sqli(input), reason end - return false, nil + return false, nil, reason end function _M.detect_xss(input) + local reason = input if type(input) == 'table' then for _, v in ipairs(input) do local match, value = _M.detect_xss(v) if match then - return match, value + reason = v + return match, value, reason end end else -- this function only returns a boolean value -- so we'll wrap the return values ourselves if libinject.xss(input) then - return true, input + return true, input, reason else - return false, nil + return false, nil, reason end end - return false, nil + return false, nil, reason end function _M.str_match(input, pattern) + local reason = input if type(input) == 'table' then for _, v in ipairs(input) do local match, value = _M.str_match(v, pattern) if match then - return match, value + reason = v + return match, value, reason end end else @@ -438,14 +454,14 @@ function _M.str_match(input, pattern) k = k + char[input:sub(k, k):byte()] end - return false, nil + return false, nil, reason end - return false, nil + return false, nil, reason end function _M.verify_cc(waf, input, pattern) - local match, value + local match, value, reason = input match = false if type(input) == 'table' then @@ -453,6 +469,7 @@ function _M.verify_cc(waf, input, pattern) match, value = _M.verify_cc(waf, v, pattern) if match then + reason = v break end end @@ -462,7 +479,7 @@ function _M.verify_cc(waf, input, pattern) do local m = _M.refind(waf, input, pattern) - if not m then return false, nil end + if not m then return false, nil, reason end end -- remove all non digits @@ -493,7 +510,7 @@ function _M.verify_cc(waf, input, pattern) end end - return match, value + return match, value, reason end _M.lookup = { diff --git a/t/acceptance/logging/03_log_event.t b/t/acceptance/logging/03_log_event.t index 3edaca9a..7f09ceaf 100644 --- a/t/acceptance/logging/03_log_event.t +++ b/t/acceptance/logging/03_log_event.t @@ -84,4 +84,3 @@ Accept: */* --- no_error_log [error] "match":"foo2","id":"12346" -