EndGame/lua/cap.lua

294 lines
11 KiB
Lua
Raw Normal View History

2020-05-26 07:36:40 +00:00
-- encryption key and salt must be shared across fronts. salt must be 8 chars
local key = "encryption_key"
2021-03-28 19:42:45 +00:00
local salt = "1saltkey"
2020-05-26 07:36:40 +00:00
-- for how long the captcha is valid. 120 sec is for testing, 3600 1 hour should be production.
local session_timeout = 3600
aes = require "resty.aes"
str = require "resty.string"
cook = require "resty.cookie"
2021-03-28 19:42:45 +00:00
random = require "resty.random"
2020-05-26 07:36:40 +00:00
aes_128_cbc_sha512x1 = aes:new(key, salt, aes.cipher(128,"cbc"), aes.hash.sha512, 1)
local cookie, err = cook:new()
if not cookie then
ngx.log(ngx.ERR, err)
return
end
function fromhex(str)
return (str:gsub('..', function (cc)
return string.char(tonumber(cc, 16))
end))
end
function tohex(str)
return (str:gsub('.', function (c)
return string.format('%02X', string.byte(c))
end))
end
caperror = nil
-- check proxy_protocol_addr if present kill circuit if needed
local pa = "no_proxy"
if ngx.var.proxy_protocol_addr ~= nil then
pa = ngx.var.proxy_protocol_addr
end
-- if "Host" header is invalid / missing kill circuit and return nothing
if in_array(allowed_hosts, ngx.var.http_host) == nil then
ngx.log(ngx.ERR, "Wrong host (" .. ngx.var.http_host .. ") " .. ngx.var.remote_addr .. "|" .. pa)
if pa ~= "no_proxy" then
local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
end
ngx.exit(444)
end
-- only GET and POST requests are allowed the others are not used. HEAD for recon checker
if ngx.var.request_method ~= "POST" and ngx.var.request_method ~= "GET" and ngx.var.request_method ~= "HEAD" then
ngx.log(ngx.ERR, "Wrong request (" .. ngx.var.request_method .. ") " .. ngx.var.remote_addr .. "|" .. pa)
if pa ~= "no_proxy" then
local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
end
ngx.exit(444)
end
-- requests without user-agent are usually invalid
if ngx.var.http_user_agent == nil then
ngx.log(ngx.ERR, "Missing user agent " .. ngx.var.remote_addr .. "|" .. pa)
if pa ~= "no_proxy" then
local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
end
ngx.exit(444)
end
-- POST without referer is invalid. some poorly configured clients may complain about this
if ngx.var.request_method == "POST" and ngx.var.http_referer == nil then
ngx.log(ngx.ERR, "Post without referer " .. ngx.var.remote_addr .. "|" .. pa)
if pa ~= "no_proxy" then
local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
end
ngx.exit(444)
end
2021-03-28 19:42:45 +00:00
-- check if cookie is blacklisted by rate limiter. if it is show the client a message and exit. can get creative with this.
local field, err = cookie:get("dcap")
local blocked_cookies = ngx.shared.blocked_cookies
local bct, btcflags = blocked_cookies:get(field)
if bct then
ngx.header.content_type = 'text/plain'
ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.")
ngx.flush()
ngx.exit(200)
end
2020-05-26 07:36:40 +00:00
-- check cookie support similar to testcookie
if ngx.var.request_method == "GET" then
local field, err = cookie:get("dcap")
if err then
2021-03-28 19:42:45 +00:00
--local tstamp = ngx.now() + slidingscalefunction
local tstamp = ngx.now() + 10
local plaintext = "queue|" .. tstamp .. "|1|" .. random.token(random.number(10,20))
2020-05-26 07:36:40 +00:00
local ciphertext = tohex(aes_128_cbc_sha512x1:encrypt(plaintext))
local ok, err = cookie:set({
key = "dcap", value = ciphertext, path = "/",
domain = ngx.var.host, httponly = true,
2021-03-28 19:42:45 +00:00
max_age = session_timeout,
samesite = "Lax"
2020-05-26 07:36:40 +00:00
})
if not ok then
ngx.log(ngx.ERR, err)
return
end
2021-03-28 19:42:45 +00:00
ngx.header.content_type = 'text/html'
local file = io.open("/etc/nginx/queue.html")
local queue, err = file:read("*a")
file:close()
ngx.say(queue)
2020-05-26 07:36:40 +00:00
ngx.flush()
ngx.exit(200)
2021-03-28 19:42:45 +00:00
else
plaintext = aes_128_cbc_sha512x1:decrypt(fromhex(field))
if not plaintext then
ngx.header.content_type = 'text/plain'
ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.")
ngx.flush()
ngx.exit(200)
end
cookdata = split(plaintext,"|")
local expired = nil
if (cookdata[1] == "queue") then
--if (tonumber(cookdata[2])) > ngx.now() or (tonumber(cookdata[2])) > tonumber(cookdata[2]) + slidingscalefunction then
if (tonumber(cookdata[2])) > ngx.now() or (tonumber(cookdata[2])) > ngx.now() + 40 then
if pa ~= "no_proxy" then
local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
end
local blocked_cookies = ngx.shared.blocked_cookies
blocked_cookies:set(field, 1, 3600)
ngx.exit(444)
end
2020-05-26 07:36:40 +00:00
2021-03-28 19:42:45 +00:00
require "caphtml_d"
2020-05-26 07:36:40 +00:00
2021-03-28 19:42:45 +00:00
local expired = nil
2020-05-26 07:36:40 +00:00
2021-03-28 19:42:45 +00:00
displaycap(session_timeout)
ngx.flush()
ngx.exit(200)
elseif (cookdata[1] == "cap_not_solved") then
if (tonumber(cookdata[2]) + 60) > ngx.now() then
if pa ~= "no_proxy" then
local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
end
ngx.header.content_type = 'text/html'
ngx.say("<h1>THINK OF WHAT YOU HAVE DONE!</h1>")
ngx.say("<p>That captcha was generated just for you. And look at what you did. Ignoring the captcha... not even giving an incorrect answer to his meaningless existence. You couldn't even give him false hope. Shame on you.</p>")
ngx.say("<p>Don't immedately refresh for a new captcha! Try and fail. You must now wait about a minute for a new captcha to load.</p>")
ngx.flush()
ngx.exit(200)
end
-- captcha generator functions
require "caphtml_d"
local expired = nil
if (tonumber(cookdata[2]) + session_timeout) < ngx.now() then
expired = true
caperror = "Session expired"
end
if cookdata[1] ~= "captcha_solved" or expired then
displaycap(session_timeout)
ngx.flush()
ngx.exit(200)
end
end
end
2020-05-26 07:36:40 +00:00
end
if ngx.var.request_method == "POST" then
local field, err = cookie:get("dcap")
if err then
ngx.exit(403)
end
if field then
plaintext = aes_128_cbc_sha512x1:decrypt(fromhex(field))
if not plaintext then
ngx.header.content_type = 'text/plain'
ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.")
ngx.flush()
ngx.exit(200)
end
cookdata = split(plaintext,"|")
local expired = nil
2021-03-28 19:42:45 +00:00
if (cookdata[1] == "cap_not_solved") then
if (tonumber(cookdata[2]) + session_timeout) < ngx.now() then
expired = true
require "caphtml_d"
caperror = "Session expired"
displaycap(session_timeout)
ngx.flush()
ngx.exit(200)
end
elseif (cookdata[1] == "captcha_solved") then
2020-05-26 07:36:40 +00:00
return
end
end
2021-03-28 19:42:45 +00:00
require "caphtml_d"
2020-05-26 07:36:40 +00:00
-- resty has a library for parsing POST data but it's not really needed
ngx.req.read_body()
local dataraw = ngx.req.get_body_data()
if dataraw == nil then
caperror = "You didn't submit anything. Try again."
2021-03-28 19:42:45 +00:00
displaycap(session_timeout)
2020-05-26 07:36:40 +00:00
ngx.flush()
ngx.exit(200)
end
local data = ngx.req.get_body_data()
data = split(data, "&")
local sentcap = ""
for index, value in ipairs(data) do
sentcap = sentcap .. split(value,"=")[2]
end
if field then
plaintext = aes_128_cbc_sha512x1:decrypt(fromhex(field))
if not plaintext then
ngx.header.content_type = 'text/plain'
ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.")
ngx.flush()
ngx.exit(200)
end
cookdata = split(plaintext,"|")
if (tonumber(cookdata[2]) + 60) < ngx.now() then
caperror = "Captcha expired"
2021-03-28 19:42:45 +00:00
displaycap(session_timeout)
2020-05-26 07:36:40 +00:00
ngx.flush()
ngx.exit(200)
end
2021-03-28 19:42:45 +00:00
if string.lower(sentcap) == string.lower(cookdata[3]) then
2020-05-26 07:36:40 +00:00
local newcookdata = ""
cookdata[1] = "captcha_solved"
for k,v in pairs(cookdata) do
newcookdata = newcookdata .. "|" .. v
end
2021-03-28 19:42:45 +00:00
newcookdata = newcookdata .. "|" .. random.token(random.number(10,20))
2020-05-26 07:36:40 +00:00
local tstamp = ngx.now()
local ciphertext = tohex(aes_128_cbc_sha512x1:encrypt(newcookdata))
local ok, err = cookie:set({
key = "dcap", value = ciphertext, path = "/",
domain = ngx.var.host, httponly = true,
2021-03-28 19:42:45 +00:00
max_age = session_timeout,
samesite = "Lax"
2020-05-26 07:36:40 +00:00
})
if not ok then
ngx.say("cookie error")
return
end
local redirect_to = ngx.var.uri
if ngx.var.query_string ~= nil then
redirect_to = redirect_to .. "?" .. ngx.var.query_string
end
return ngx.redirect(redirect_to)
else
caperror = "You Got That Wrong. Try again"
end
2021-03-28 19:42:45 +00:00
displaycap(session_timeout)
2020-05-26 07:36:40 +00:00
ngx.flush()
ngx.exit(200)
end
2021-03-28 19:42:45 +00:00
end