mirror of
https://github.com/onionltd/EndGame.git
synced 2025-06-26 15:20:38 -04:00
Init commit
This commit is contained in:
commit
770d6da100
40 changed files with 6875 additions and 0 deletions
282
lua/cap.lua
Normal file
282
lua/cap.lua
Normal file
|
@ -0,0 +1,282 @@
|
|||
-- encryption key and salt must be shared across fronts. salt must be 8 chars
|
||||
local key = "encryption_key"
|
||||
local salt = "salt1234"
|
||||
-- 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"
|
||||
|
||||
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
|
||||
|
||||
-- check cookie support similar to testcookie
|
||||
if ngx.var.request_method == "GET" then
|
||||
local args = ngx.req.get_uri_args()
|
||||
if args['tca'] == "1" then
|
||||
local field, err = cookie:get("dcap")
|
||||
if err or not field then
|
||||
ngx.exit(403)
|
||||
end
|
||||
-- if cookie cannot be decrypted most likely it has been messed with
|
||||
local cookdata = aes_128_cbc_sha512x1:decrypt(fromhex(field))
|
||||
if not cookdata 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
|
||||
cooktest = split(cookdata, "|")[1]
|
||||
if cooktest ~= "cap_not_solved" and cooktest ~= "captcha_solved" then
|
||||
ngx.exit(403)
|
||||
end
|
||||
end
|
||||
|
||||
-- try to set cookie. max-age is irrelevant as it can be faked and check is done against cookie content anyway. should be set to a large value otherwise it will annoy users
|
||||
local field, err = cookie:get("dcap")
|
||||
if err then
|
||||
local tstamp = ngx.now()
|
||||
local plaintext = "cap_not_solved|" .. tstamp .. "|1"
|
||||
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,
|
||||
max_age = 21600,
|
||||
samesite = "Strict"
|
||||
})
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, err)
|
||||
return
|
||||
end
|
||||
ngx.header.content_type = 'text/html'
|
||||
ngx.say("<head> \
|
||||
<meta http-equiv=\"refresh\" content=\"1\"> \
|
||||
</head><a href=\"/\">One moment...</p>")
|
||||
ngx.flush()
|
||||
ngx.exit(200)
|
||||
end
|
||||
end
|
||||
|
||||
-- captcha generator functions
|
||||
require "caphtml_d"
|
||||
|
||||
local field, err = cookie:get("dcap")
|
||||
if not field or field == nil then
|
||||
displaycap()
|
||||
ngx.flush()
|
||||
ngx.exit(200)
|
||||
end
|
||||
|
||||
-- check if cookie is blacklisted by rate limiter. if it is show the client a message and exit. can get creative with this.
|
||||
local blocked_cookies = ngx.shared.blocked_cookies
|
||||
local bct, btcflags = blocked_cookies:get(field)
|
||||
if bct then
|
||||
ngx.log(ngx.ERR, "Cookie " .. field .. " blacklisted.")
|
||||
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
|
||||
|
||||
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
|
||||
if (tonumber(cookdata[2]) + session_timeout) < ngx.now() then
|
||||
expired = true
|
||||
caperror = "Session expired"
|
||||
displaycap()
|
||||
ngx.flush()
|
||||
ngx.exit(200)
|
||||
end
|
||||
if cookdata[1] == "captcha_solved" and not expired then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- 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."
|
||||
displaycap()
|
||||
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"
|
||||
displaycap()
|
||||
ngx.flush()
|
||||
ngx.exit(200)
|
||||
end
|
||||
|
||||
if sentcap == cookdata[3] then
|
||||
local newcookdata = ""
|
||||
cookdata[1] = "captcha_solved"
|
||||
for k,v in pairs(cookdata) do
|
||||
newcookdata = newcookdata .. "|" .. v
|
||||
end
|
||||
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,
|
||||
max_age = 21600,
|
||||
samesite = "Strict"
|
||||
})
|
||||
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
|
||||
|
||||
else
|
||||
caperror = "Session invalid or expired"
|
||||
displaycap()
|
||||
ngx.flush()
|
||||
ngx.exit(200)
|
||||
end
|
||||
end
|
||||
|
||||
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 not cookdata then
|
||||
displaycap()
|
||||
ngx.flush()
|
||||
ngx.exit(200)
|
||||
end
|
||||
|
||||
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()
|
||||
ngx.flush()
|
||||
ngx.exit(200)
|
||||
end
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue