"ddos challenge" style script #4
3 changed files with 43 additions and 8 deletions
|
|
@ -207,6 +207,8 @@ pow_difficulty = 4 -- Difficulty levels:
|
||||||
|
|
||||||
This creates real computational cost for attackers - a bot making 1000 requests/sec would need to spend 1000-3000 seconds of CPU time with difficulty 4.
|
This creates real computational cost for attackers - a bot making 1000 requests/sec would need to spend 1000-3000 seconds of CPU time with difficulty 4.
|
||||||
|
|
||||||
|
**Security**: The server verifies the proof-of-work by computing `SHA-256(challenge + nonce)` and checking that it has the required leading zeros. Bots cannot bypass this by submitting random nonces.
|
||||||
|
|
||||||
## Path-Based Protection
|
## Path-Based Protection
|
||||||
|
|
||||||
You can configure the module to protect only specific paths, which is useful for:
|
You can configure the module to protect only specific paths, which is useful for:
|
||||||
|
|
|
||||||
|
|
@ -468,14 +468,24 @@ local function challengeCallback(cfg, state)
|
||||||
local challenge_exists = state.tokens_dict:get("pow:" .. challenge)
|
local challenge_exists = state.tokens_dict:get("pow:" .. challenge)
|
||||||
|
|
||||||
if challenge_exists then
|
if challenge_exists then
|
||||||
-- Verify the hash using resty.sha256 or just trust client for now
|
-- Verify the proof-of-work server-side
|
||||||
-- In production, you'd want to verify: sha256(challenge + nonce) starts with N zeros
|
local resty_sha256 = require("resty.sha256")
|
||||||
-- For simplicity, we'll trust the client solved it correctly
|
local str = require("resty.string")
|
||||||
-- A more secure implementation would verify server-side
|
|
||||||
challenge_passed = true
|
|
||||||
|
|
||||||
|
local sha256 = resty_sha256:new()
|
||||||
|
sha256:update(challenge .. nonce)
|
||||||
|
local digest = sha256:final()
|
||||||
|
local hash_hex = str.to_hex(digest)
|
||||||
|
|
||||||
|
-- Check if hash starts with required number of zeros
|
||||||
|
local required_zeros = string.rep("0", state.pow_difficulty)
|
||||||
|
if hash_hex:sub(1, state.pow_difficulty) == required_zeros then
|
||||||
|
challenge_passed = true
|
||||||
-- Clean up the challenge
|
-- Clean up the challenge
|
||||||
state.tokens_dict:delete("pow:" .. challenge)
|
state.tokens_dict:delete("pow:" .. challenge)
|
||||||
|
else
|
||||||
|
ngx.log(ngx.WARN, "PoW verification failed: hash doesn't have enough leading zeros")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -493,6 +493,26 @@ function TestDDoSProtectionChallengeQuestion:teardown()
|
||||||
teardownNgx()
|
teardownNgx()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Helper function to compute a valid PoW nonce for testing
|
||||||
|
local function computeValidNonce(challenge, difficulty)
|
||||||
|
local resty_sha256 = require("resty.sha256")
|
||||||
|
local str = require("resty.string")
|
||||||
|
local required_zeros = string.rep("0", difficulty)
|
||||||
|
|
||||||
|
for nonce = 0, 1000000 do
|
||||||
|
local sha256 = resty_sha256:new()
|
||||||
|
sha256:update(challenge .. tostring(nonce))
|
||||||
|
local digest = sha256:final()
|
||||||
|
local hash_hex = str.to_hex(digest)
|
||||||
|
|
||||||
|
if hash_hex:sub(1, difficulty) == required_zeros then
|
||||||
|
return tostring(nonce)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
error("Could not find valid nonce after 1M attempts")
|
||||||
|
end
|
||||||
|
|
||||||
-- Tests for proof-of-work challenge type
|
-- Tests for proof-of-work challenge type
|
||||||
TestDDoSProtectionChallengePow = {}
|
TestDDoSProtectionChallengePow = {}
|
||||||
|
|
||||||
|
|
@ -562,13 +582,16 @@ function TestDDoSProtectionChallengePow:testValidPowPassesChallenge()
|
||||||
local test_challenge = 'test_pow_challenge'
|
local test_challenge = 'test_pow_challenge'
|
||||||
ngx.shared.aproxy_tokens:set('pow:' .. test_challenge, true, 300)
|
ngx.shared.aproxy_tokens:set('pow:' .. test_challenge, true, 300)
|
||||||
|
|
||||||
|
-- Compute a valid nonce that satisfies the PoW requirement
|
||||||
|
local valid_nonce = computeValidNonce(test_challenge, 4)
|
||||||
|
|
||||||
setupFakeRequest('/__aproxy_challenge_verify', {})
|
setupFakeRequest('/__aproxy_challenge_verify', {})
|
||||||
ngx.var.remote_addr = '192.168.4.2'
|
ngx.var.remote_addr = '192.168.4.2'
|
||||||
ngx.var.request_method = 'POST'
|
ngx.var.request_method = 'POST'
|
||||||
ngx._post_args = {
|
ngx._post_args = {
|
||||||
return_to = '/api/test',
|
return_to = '/api/test',
|
||||||
challenge = test_challenge,
|
challenge = test_challenge,
|
||||||
nonce = '12345' -- In reality, this would be computed
|
nonce = valid_nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
onRequest()
|
onRequest()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue