diff --git a/src/bw/lua/bunkerweb/utils.lua b/src/bw/lua/bunkerweb/utils.lua index b4853ee19..f0455cb60 100644 --- a/src/bw/lua/bunkerweb/utils.lua +++ b/src/bw/lua/bunkerweb/utils.lua @@ -289,8 +289,9 @@ end utils.get_reason = function(ctx) -- ngx.ctx if ctx and ctx.bw and ctx.bw.reason then - return ctx.bw.reason, ctx.bw.reason_data or {} + return ctx.bw.reason, ctx.bw.reason_data or {}, ctx.bw.security_mode end + local security_mode = utils.get_security_mode(ctx) -- ngx.var local var_reason = var.reason if var_reason and var_reason ~= "" then @@ -302,11 +303,11 @@ utils.get_reason = function(ctx) reason_data = data end end - return var_reason, reason_data + return var_reason, reason_data, security_mode end -- os.getenv if os.getenv("REASON") == "modsecurity" then - return "modsecurity", {} + return "modsecurity", {}, security_mode end -- datastore ban local ip @@ -321,7 +322,7 @@ utils.get_reason = function(ctx) if ok then banned = ban_data["reason"] end - return banned, {} + return banned, {}, security_mode end -- unknown if ngx.status == utils.get_deny_status() then @@ -330,10 +331,11 @@ utils.get_reason = function(ctx) return nil end -utils.set_reason = function(reason, reason_data, ctx) +utils.set_reason = function(reason, reason_data, ctx, security_mode) if ctx and ctx.bw then ctx.bw.reason = reason or "unknown" ctx.bw.reason_data = reason_data or {} + ctx.bw.security_mode = security_mode end if var.reason then var.reason = reason @@ -572,6 +574,14 @@ utils.get_deny_status = function() return 444 end +utils.get_security_mode = function(ctx) + local security_mode, err = utils.get_variable("SECURITY_MODE", true, ctx) + if not security_mode then + return "block" + end + return security_mode +end + utils.get_session = function(ctx) -- Return session from ctx if already there if ctx.bw.sessions_session then diff --git a/src/common/confs/server-http/access-lua.conf b/src/common/confs/server-http/access-lua.conf index e906a2a7c..f0fc0500d 100644 --- a/src/common/confs/server-http/access-lua.conf +++ b/src/common/confs/server-http/access-lua.conf @@ -13,6 +13,7 @@ access_by_lua_block { local INFO = ngx.INFO local WARN = ngx.WARN local NOTICE = ngx.NOTICE + local OK = ngx.OK local HTTP_MOVED_TEMPORARILY = ngx.HTTP_MOVED_TEMPORARILY local fill_ctx = helpers.fill_ctx local save_ctx = helpers.save_ctx @@ -24,6 +25,7 @@ access_by_lua_block { local set_reason = utils.set_reason local get_deny_status = utils.get_deny_status local save_session = utils.save_session + local get_security_mode = utils.get_security_mode local tostring = tostring -- Don't process internal requests @@ -49,6 +51,9 @@ access_by_lua_block { end logger:log(INFO, "ngx.ctx filled (ret = " .. ret .. ")") + -- Get security mode + local security_mode = get_security_mode(ctx) + -- Process bans as soon as possible if not is_whitelisted(ctx) then local banned, reason, ttl = is_banned(ctx.bw.remote_addr) @@ -56,11 +61,16 @@ access_by_lua_block { logger:log(ERR, "can't check if IP " .. ctx.bw.remote_addr .. " is banned : " .. reason) elseif banned then ctx.bw.is_banned = true - set_reason(reason, {}, ctx) + set_reason(reason, {}, ctx, security_mode) save_ctx(ctx) + if security_mode == "block" then + logger:log(WARN, + "IP " .. ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)") + return exit(get_deny_status()) + end logger:log(WARN, - "IP " .. ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)") - return exit(get_deny_status()) + "detected IP " .. ctx.bw.remote_addr .. " ban with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)") + return exit(OK) else logger:log(INFO, "IP " .. ctx.bw.remote_addr .. " is not banned") end @@ -101,7 +111,10 @@ access_by_lua_block { logger:log(INFO, plugin_id .. ":access() call successful : " .. ret.msg) end if ret.status and not ret.redirect then - if ret.status == get_deny_status() then + if security_mode == "detect" then + logger:log(WARN, "detected deny access from " .. plugin_id .. " : " .. ret.msg) + break + elseif ret.status == get_deny_status() then set_reason(plugin_id, ret.data, ctx) logger:log(WARN, "denied access from " .. plugin_id .. " : " .. ret.msg) else diff --git a/src/common/confs/server-http/log-lua.conf b/src/common/confs/server-http/log-lua.conf index bc3cafc52..a58b4bf2e 100644 --- a/src/common/confs/server-http/log-lua.conf +++ b/src/common/confs/server-http/log-lua.conf @@ -74,9 +74,13 @@ log_by_lua_block { logger:log(INFO, "called log() methods of plugins") -- Display reason at info level - local reason, reason_data = get_reason(ctx) + local reason, reason_data, security_mode = get_reason(ctx) if reason then - logger:log(INFO, "client was denied with reason " .. reason .. " and data = " .. encode(reason_data)) + if security_mode == "block" then + logger:log(INFO, "client was denied with reason " .. reason .. " and data = " .. encode(reason_data)) + else + logger:log(INFO, "detected client deny with reason " .. reason .. " and data = " .. encode(reason_data)) + end end logger:log(INFO, "log phase ended") diff --git a/src/common/core/metrics/metrics.lua b/src/common/core/metrics/metrics.lua index e0ae6a9f2..e8963b91c 100644 --- a/src/common/core/metrics/metrics.lua +++ b/src/common/core/metrics/metrics.lua @@ -52,7 +52,7 @@ function metrics:log(bypass_checks) return self:ret(true, "metrics are disabled") end -- Store blocked requests - local reason, data = get_reason(self.ctx) + local reason, data, security_mode = get_reason(self.ctx) if reason then local country = "local" local err @@ -74,6 +74,7 @@ function metrics:log(bypass_checks) reason = reason, server_name = self.ctx.bw.server_name, data = data, + security_mode = security_mode } -- Get current requests local requests = lru:get("requests") diff --git a/src/common/core/modsecurity/confs/server-http/modsecurity-rules.conf.modsec b/src/common/core/modsecurity/confs/server-http/modsecurity-rules.conf.modsec index d509966ee..efe2174a6 100644 --- a/src/common/core/modsecurity/confs/server-http/modsecurity-rules.conf.modsec +++ b/src/common/core/modsecurity/confs/server-http/modsecurity-rules.conf.modsec @@ -2,7 +2,11 @@ {% set json = import("json") %} {% set service_id = SERVER_NAME.split(" ")[0] %} # process rules with disruptive actions +{% if SECURITY_MODE == "block" %} SecRuleEngine {{ MODSECURITY_SEC_RULE_ENGINE }} +{% else %} +SecRuleEngine DetectionOnly +{% endif %} # allow body checks SecRequestBodyAccess On diff --git a/src/common/settings.json b/src/common/settings.json index 8d20050ee..c576e7927 100644 --- a/src/common/settings.json +++ b/src/common/settings.json @@ -343,5 +343,14 @@ "label": "Use template", "regex": "^.*$", "type": "text" + }, + "SECURITY_MODE": { + "context": "multisite", + "default": "block", + "help": "Defines the response to threats: \"detect\" to monitor and log, or \"block\" to prevent access and log incidents.", + "id": "security-mode", + "label": "Security mode", + "regex": "^(detect|block)$", + "type": "check" } } diff --git a/src/ui/app/templates/reports.html b/src/ui/app/templates/reports.html index 2a21cd8d3..bca10105a 100644 --- a/src/ui/app/templates/reports.html +++ b/src/ui/app/templates/reports.html @@ -40,6 +40,9 @@