fix IP usage in csmod.Allow(ip) and repair captcha for bouncing (#53)

---------

Co-authored-by: Laurence Jones <laurence.jones@live.co.uk>
This commit is contained in:
AlteredCoder 2024-01-15 10:49:12 +01:00 committed by GitHub
parent cb0d72bdf1
commit 535f009e59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 21 additions and 30 deletions

View file

@ -495,7 +495,7 @@ function csmod.allowIp(ip)
end end
function csmod.AppSecCheck() function csmod.AppSecCheck(ip)
local httpc = http.new() local httpc = http.new()
httpc:set_timeouts(runtime.conf["APPSEC_CONNECT_TIMEOUT"], runtime.conf["APPSEC_SEND_TIMEOUT"], runtime.conf["APPSEC_PROCESS_TIMEOUT"]) httpc:set_timeouts(runtime.conf["APPSEC_CONNECT_TIMEOUT"], runtime.conf["APPSEC_SEND_TIMEOUT"], runtime.conf["APPSEC_PROCESS_TIMEOUT"])
@ -503,7 +503,7 @@ function csmod.AppSecCheck()
local headers = ngx.req.get_headers() local headers = ngx.req.get_headers()
-- overwrite headers with crowdsec appsec require headers -- overwrite headers with crowdsec appsec require headers
headers["x-crowdsec-appsec-ip"] = ngx.var.remote_addr headers["x-crowdsec-appsec-ip"] = ip
headers["x-crowdsec-appsec-host"] = ngx.var.http_host headers["x-crowdsec-appsec-host"] = ngx.var.http_host
headers["x-crowdsec-appsec-verb"] = ngx.var.request_method headers["x-crowdsec-appsec-verb"] = ngx.var.request_method
headers["x-crowdsec-appsec-uri"] = uri headers["x-crowdsec-appsec-uri"] = uri
@ -602,7 +602,7 @@ function csmod.Allow(ip)
-- that user configured the remediation component to always check on the appSec (even if there is a decision for the IP) -- that user configured the remediation component to always check on the appSec (even if there is a decision for the IP)
if ok == true or runtime.conf["ALWAYS_SEND_TO_APPSEC"] == true then if ok == true or runtime.conf["ALWAYS_SEND_TO_APPSEC"] == true then
if runtime.conf["APPSEC_ENABLED"] == true and ngx.var.no_appsec ~= "1" then if runtime.conf["APPSEC_ENABLED"] == true and ngx.var.no_appsec ~= "1" then
local appsecOk, appsecRemediation, err = csmod.AppSecCheck() local appsecOk, appsecRemediation, err = csmod.AppSecCheck(ip)
if err ~= nil then if err ~= nil then
ngx.log(ngx.ERR, "AppSec check: " .. err) ngx.log(ngx.ERR, "AppSec check: " .. err)
end end
@ -630,13 +630,13 @@ function csmod.Allow(ip)
if captcha_ok then -- if captcha can be use (configuration is valid) if captcha_ok then -- if captcha can be use (configuration is valid)
-- we check if the IP need to validate its captcha before checking it against crowdsec local API -- we check if the IP need to validate its captcha before checking it against crowdsec local API
local previous_uri, flags = ngx.shared.crowdsec_cache:get("captcha_"..ngx.var.remote_addr) local previous_uri, flags = ngx.shared.crowdsec_cache:get("captcha_"..ip)
local source, state_id, err = flag.GetFlags(flags) local source, state_id, err = flag.GetFlags(flags)
if previous_uri ~= nil and state_id == flag.VERIFY_STATE then if previous_uri ~= nil and state_id == flag.VERIFY_STATE then
ngx.req.read_body() ngx.req.read_body()
local captcha_res = ngx.req.get_post_args()[csmod.GetCaptchaBackendKey()] or 0 local captcha_res = ngx.req.get_post_args()[csmod.GetCaptchaBackendKey()] or 0
if captcha_res ~= 0 then if captcha_res ~= 0 then
local valid, err = csmod.validateCaptcha(captcha_res, ngx.var.remote_addr) local valid, err = csmod.validateCaptcha(captcha_res, ip)
if err ~= nil then if err ~= nil then
ngx.log(ngx.ERR, "Error while validating captcha: " .. err) ngx.log(ngx.ERR, "Error while validating captcha: " .. err)
end end
@ -646,11 +646,11 @@ function csmod.Allow(ip)
-- we will not propose a captcha until the 'CAPTCHA_EXPIRATION'. -- we will not propose a captcha until the 'CAPTCHA_EXPIRATION'.
-- But for the Application security component, we serve the captcha each time the user trigger it. -- But for the Application security component, we serve the captcha each time the user trigger it.
if source == flag.APPSEC_SOURCE then if source == flag.APPSEC_SOURCE then
ngx.shared.crowdsec_cache:delete("captcha_"..ngx.var.remote_addr) ngx.shared.crowdsec_cache:delete("captcha_"..ip)
else else
local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ngx.var.remote_addr, previous_uri, runtime.conf["CAPTCHA_EXPIRATION"], bit.bor(flag.VALIDATED_STATE, source) ) local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ip, previous_uri, runtime.conf["CAPTCHA_EXPIRATION"], bit.bor(flag.VALIDATED_STATE, source) )
if not succ then if not succ then
ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ngx.var.remote_addr .. "' in cache: "..err) ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ip .. "' in cache: "..err)
end end
if forcible then if forcible then
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config") ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
@ -661,23 +661,23 @@ function csmod.Allow(ip)
ngx.redirect(previous_uri) ngx.redirect(previous_uri)
return return
else else
ngx.log(ngx.ALERT, "Invalid captcha from " .. ngx.var.remote_addr) ngx.log(ngx.ALERT, "Invalid captcha from " .. ip)
end end
end end
end end
end end
if not ok then if not ok then
if remediation == "ban" then if remediation == "ban" then
ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ngx.var.remote_addr .. "' with '"..remediation.."' (by " .. flag.Flags[remediationSource] .. ")") ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."' (by " .. flag.Flags[remediationSource] .. ")")
ban.apply() ban.apply()
return return
end end
-- if the remediation is a captcha and captcha is well configured -- if the remediation is a captcha and captcha is well configured
if remediation == "captcha" and captcha_ok and ngx.var.uri ~= "/favicon.ico" then if remediation == "captcha" and captcha_ok and ngx.var.uri ~= "/favicon.ico" then
local previous_uri, flags = ngx.shared.crowdsec_cache:get("captcha_"..ngx.var.remote_addr) local previous_uri, flags = ngx.shared.crowdsec_cache:get("captcha_"..ip)
source, state_id, err = flag.GetFlags(flags) local source, state_id, err = flag.GetFlags(flags)
-- we check if the IP is already in cache for captcha and not yet validated -- we check if the IP is already in cache for captcha and not yet validated
if previous_uri == nil or remediationSource == flag.APPSEC_SOURCE then if previous_uri == nil or state_id ~= flag.VALIDATED_STATE or remediationSource == flag.APPSEC_SOURCE then
ngx.header.content_type = "text/html" ngx.header.content_type = "text/html"
ngx.header.cache_control = "no-cache" ngx.header.cache_control = "no-cache"
ngx.say(csmod.GetCaptchaTemplate()) ngx.say(csmod.GetCaptchaTemplate())
@ -691,14 +691,14 @@ function csmod.Allow(ip)
end end
end end
end end
local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ngx.var.remote_addr, uri , 60, bit.bor(flag.VERIFY_STATE, remediationSource)) local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ip, uri , 60, bit.bor(flag.VERIFY_STATE, remediationSource))
if not succ then if not succ then
ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ngx.var.remote_addr .. "' in cache: "..err) ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ip .. "' in cache: "..err)
end end
if forcible then if forcible then
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config") ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
end end
ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ngx.var.remote_addr .. "' with '"..remediation.."'") ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."'")
end end
end end
end end

View file

@ -25,28 +25,19 @@ function M.GetFlags(flags)
return source, state, err return source, state, err
end end
if bit.band(flags, M.BOUNCER_SOURCE) then if bit.band(flags, M.BOUNCER_SOURCE) ~= 0 then
source = M.BOUNCER_SOURCE source = M.BOUNCER_SOURCE
elseif bit.band(flags, M.APPSEC_SOURCE) then elseif bit.band(flags, M.APPSEC_SOURCE) ~= 0 then
source = M.APPSEC_SOURCE source = M.APPSEC_SOURCE
end end
if bit.band(flags, M.VERIFY_STATE) then if bit.band(flags, M.VERIFY_STATE) ~= 0 then
state = M.VERIFY_STATE state = M.VERIFY_STATE
elseif bit.band(flags, M.VALIDATED_STATE) then elseif bit.band(flags, M.VALIDATED_STATE) ~= 0 then
state = M.VALIDATED_STATE state = M.VALIDATED_STATE
end end
return source, state, err return source, state, err
end end
function M.GetStateID(state)
for k, v in pairs(M.State) do
if v == state then
return tonumber(k)
end
end
return nil
end
return M return M