mirror of
https://github.com/onionltd/EndGame.git
synced 2025-04-18 22:55:55 -04:00
332 lines
10 KiB
Plaintext
332 lines
10 KiB
Plaintext
#right now there is a lot of logging to error_log so during an attack those logs will fill the disk eventually.
|
|
#a good idea would be to use a syslog server and log to a socket instead of a file for IO optimization
|
|
#logging could also be disabled in production
|
|
|
|
#depending on cluster setup some things can be changed here.
|
|
#keepalive 128; or proxy_bind on multiple local ips can be used to mitigate local port exhaustion
|
|
#most likely with this setup it's not the case
|
|
#if this runs on the same machine as the application server UNIX sockets should be used instead of TCP
|
|
upstream tor {
|
|
server 127.0.0.1:9060;
|
|
server 127.0.0.1:9070;
|
|
}
|
|
access_by_lua_no_postpone on;
|
|
lua_package_path "/etc/nginx/resty/?.lua;;";
|
|
|
|
init_by_lua_block {
|
|
allowed_hosts = { "mainonion",
|
|
"masterbalanceonion"
|
|
}
|
|
|
|
function in_array(tab, val)
|
|
for index, value in ipairs(tab) do
|
|
if value == val then
|
|
return true
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function split(str, sep)
|
|
local result = {}
|
|
local regex = ("([^%s]+)"):format(sep)
|
|
for each in str:gmatch(regex) do
|
|
table.insert(result, each)
|
|
end
|
|
return result
|
|
end
|
|
|
|
local function calc_circuit(proxyheaderip)
|
|
local cg = split(proxyheaderip, ":")
|
|
local g1 = cg[5]
|
|
local g2 = cg[6]
|
|
|
|
local glen = string.len(g1)
|
|
if (glen < 4) then
|
|
for i = (4 - glen),1,-1 do
|
|
g1 = "0" .. g1
|
|
::loop_label_1::
|
|
end
|
|
end
|
|
glen = string.len(g2)
|
|
if (glen < 4) then
|
|
for i = (4 - glen),1,-1 do
|
|
g2 = "0" .. g2
|
|
::loop_label_2::
|
|
end
|
|
end
|
|
|
|
local d1 = (string.sub(g1,1,1) .. string.sub(g1,2,2))
|
|
local d2 = (string.sub(g1,3,3) .. string.sub(g1,4,4))
|
|
local d3 = (string.sub(g2,1,1) .. string.sub(g2,2,2))
|
|
local d4 = (string.sub(g2,3,3) .. string.sub(g2,4,4))
|
|
local circuit_id = ((((bit.lshift(tonumber(d1, 16), 24)) + (bit.lshift(tonumber(d2, 16), 16))) + (bit.lshift(tonumber(d3, 16), 8))) + tonumber(d4, 16))
|
|
return circuit_id
|
|
end
|
|
|
|
function kill_circuit(premature, clientip, headerip)
|
|
local circuitid = calc_circuit(headerip)
|
|
local sockfile = "unix:/etc/tor/c1"
|
|
local response = "Closing circuit " .. circuitid .. " "
|
|
local sock = ngx.socket.tcp()
|
|
sock:settimeout(1000)
|
|
local ok, err = sock:connect(sockfile)
|
|
if not ok then
|
|
ngx.log(ngx.ERR, "failed to connect to tor: " .. err)
|
|
return
|
|
end
|
|
ngx.log(ngx.ERR, "connected to tor")
|
|
|
|
local bytes, err = sock:send("authenticate \"torauthpassword\"\n")
|
|
if not bytes then
|
|
ngx.log(ngx.ERR, "failed authenticate to tor: " .. err)
|
|
return
|
|
end
|
|
local data, err, partial = sock:receive()
|
|
if not data then
|
|
ngx.log(ngx.ERR, "failed receive data from tor: " .. err)
|
|
return
|
|
end
|
|
local response = response .. " " .. data
|
|
|
|
local bytes, err = sock:send("closecircuit " .. circuitid .. "\n")
|
|
if not bytes then
|
|
ngx.log(ngx.ERR, "failed send data to tor: " .. err)
|
|
return
|
|
end
|
|
local data, err, partial = sock:receive()
|
|
if not data then
|
|
ngx.log(ngx.ERR, "failed receive data from tor: " .. err)
|
|
return
|
|
end
|
|
local response = response .. " " .. data
|
|
|
|
ngx.log(ngx.ERR, response)
|
|
sock:close()
|
|
return
|
|
end
|
|
}
|
|
|
|
#rate limits should be set to the maximum number of resources (css/images/iframes) a page will load. those should be kept to a minimum for optimization reasons
|
|
#limiting by proxy_protocol_addr won't work with V2 onions and maybe should be disabled.
|
|
#limiting by cookie_<name> works regarless and must be used, otherwise an attacker can solve a captcha by hand and add it to a script/bot
|
|
|
|
limit_req_zone $proxy_protocol_addr zone=circuits:50m rate=6r/s;
|
|
limit_req_zone $cookie_dcap zone=capcookie:50m rate=6r/s;
|
|
|
|
#proxy_protocol only makes sense with V3 onions (exportcircuitid) otherwise it will break things.
|
|
#kill_circuit won't be used without it
|
|
server {
|
|
listen unix:/etc/tor/nginx1 proxy_protocol bind;
|
|
allow unix:;
|
|
deny all;
|
|
|
|
#access_log /var/log/nginx/front_access.log;
|
|
|
|
if ($http_x_tor2web) {
|
|
return 401;
|
|
}
|
|
|
|
error_page 401 @tor2web;
|
|
|
|
location @tor2web {
|
|
echo_status 401;
|
|
default_type text/html;
|
|
echo <h1>It seems you are connecting over a Tor2Web Proxy.</h1><p>This is unsafe being that you are giving the proxy a privileged position where it can modify and/or inject content into the webpages you visit as well as track what you do.</p>;
|
|
echo <p>When visiting please use the Tor Browser and go to the offical onion address. This keeps you private and safe.</body></html>;
|
|
}
|
|
|
|
more_clear_headers 'Server:*';
|
|
more_clear_headers 'X-Page-Speed:*';
|
|
more_clear_headers 'Vary*';
|
|
more_clear_headers 'captcha-fails*';
|
|
|
|
#what do do when rate limit is triggered, blacklist the cookie (if exists) and kill circuit
|
|
location @ratelimit {
|
|
error_log /var/log/nginx/front_error.log;
|
|
access_by_lua_block {
|
|
local pa = "no_proxy"
|
|
if ngx.var.proxy_protocol_addr ~= nil then
|
|
pa = ngx.var.proxy_protocol_addr
|
|
end
|
|
local cook = require "resty.cookie"
|
|
local cookie, err = cook:new()
|
|
if not cookie then
|
|
ngx.log(ngx.ERR, err)
|
|
return
|
|
end
|
|
local field, err = cookie:get("dcap")
|
|
if field then
|
|
|
|
local blocked_cookies = ngx.shared.blocked_cookies
|
|
blocked_cookies:set(field, 1, 3600)
|
|
end
|
|
|
|
ngx.log(ngx.ERR, "Rate limited " .. 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)
|
|
}
|
|
}
|
|
|
|
#what do do when waf is triggered, just show the error page and kill circuit for now.
|
|
#naxsi seems to kick in before everything else except rate limiter but if it does trash traffic won't make it to the application servers anyway
|
|
#doesn't make sense to blacklist cookie as it will annoy users
|
|
|
|
location @waf {
|
|
error_log /var/log/nginx/front_error.log;
|
|
default_type text/html;
|
|
content_by_lua_block {
|
|
ngx.say("<head><title>Error</title></head>")
|
|
ngx.say("<body bgcolor=\"white\">")
|
|
ngx.say("<center><h1>Error</h1></center>")
|
|
ngx.say("<hr><center><p>Your browser sent a request that this server could not understand.</p></center>")
|
|
ngx.say("<center><p>Most likely your input contains invalid characters (\" , `, etc.) that except for passwords should not be used.</p></center>")
|
|
ngx.say("<center><p>This may also happen if you are trying to send contact information or external links.</p></center>")
|
|
ngx.say("<center><p>Please go back, check your input and try again.</p></center></body>")
|
|
|
|
proxyip = "no_proxy"
|
|
torip = ngx.var.remote_addr
|
|
if ngx.var.proxy_protocol_addr ~= nil then
|
|
proxyip = ngx.var.proxy_protocol_addr
|
|
end
|
|
|
|
ngx.log(ngx.ERR, "WAF triggered " .. torip .. "|" .. proxyip)
|
|
if proxyip ~= "no_proxy" then
|
|
local ok, err = ngx.timer.at(0, kill_circuit, torip, proxyip)
|
|
if not ok then
|
|
ngx.log(ngx.ERR, "failed to create timer: ", err)
|
|
return
|
|
end
|
|
end
|
|
}
|
|
}
|
|
|
|
location @502 {
|
|
default_type text/html;
|
|
content_by_lua_block {
|
|
ngx.say("<head><title>502 Timeout</title></head>")
|
|
ngx.say("<body bgcolor=\"white\">")
|
|
ngx.say("<center><h1>502 Timeout</h1></center>")
|
|
ngx.say("<hr><center><p>It seems this endgame front doesn't have a stable connection to the backend right now.</p></center>")
|
|
ngx.say("<center><p>To fix it you can try to reload the page. If that doesn't work, and you end back here, get a new circuit.</p></center>")
|
|
ngx.say("<center><p>If getting a new circuit doesn't work. Try to get a brand new Tor identity.</p></center>")
|
|
ngx.say("<center><p>If getting a new Tor identity doesn't work come back later.</p></center></body>")
|
|
}
|
|
}
|
|
|
|
|
|
location /kill {
|
|
access_by_lua_block {
|
|
proxyip = "no_proxy"
|
|
torip = ngx.var.remote_addr
|
|
if ngx.var.proxy_protocol_addr ~= nil then
|
|
proxyip = ngx.var.proxy_protocol_addr
|
|
end
|
|
|
|
ngx.log(ngx.ERR, "Kill area visited" .. torip .. "|" .. proxyip)
|
|
|
|
local cook = require "resty.cookie"
|
|
local cookie, err = cook:new()
|
|
if not cookie then
|
|
ngx.log(ngx.ERR, err)
|
|
return
|
|
end
|
|
|
|
local field, err = cookie:get("dcap")
|
|
if field then
|
|
local blocked_cookies = ngx.shared.blocked_cookies
|
|
blocked_cookies:set(field, 1, 3600)
|
|
end
|
|
|
|
if proxyip ~= "no_proxy" then
|
|
local ok, err = ngx.timer.at(0, kill_circuit, torip, proxyip)
|
|
if not ok then
|
|
ngx.log(ngx.ERR, "failed to create timer: ", err)
|
|
return
|
|
end
|
|
end
|
|
ngx.exit(444)
|
|
}
|
|
}
|
|
location / {
|
|
#access_log /var/log/nginx/front_access.log;
|
|
error_log /var/log/nginx/front_error.log;
|
|
|
|
#rate limits per circuit ID (won't work with V2 and maybe should be disabled)
|
|
limit_req zone=circuits burst=10 nodelay;
|
|
error_page 503 =503 @ratelimit;
|
|
|
|
#rate limits based on captcha cookie. if an attacker or bot solves the capcha by hand and inputs the cookie in a script
|
|
#the cookie will be blacklisted by all fronts (eventually) and subsequent requests dropped.
|
|
|
|
limit_req zone=capcookie burst=10 nodelay;
|
|
error_page 503 =503 @ratelimit;
|
|
|
|
error_page 502 =502 @502;
|
|
|
|
#check if access captca is solved and other things
|
|
access_by_lua_file lua/cap.lua;
|
|
|
|
SecRulesEnabled;
|
|
#LearningMode;
|
|
DeniedUrl "@waf";
|
|
CheckRule "$SQL >= 8" BLOCK;
|
|
CheckRule "$RFI >= 8" BLOCK;
|
|
CheckRule "$TRAVERSAL >= 4" BLOCK;
|
|
CheckRule "$EVADE >= 4" BLOCK;
|
|
CheckRule "$XSS >= 8" BLOCK;
|
|
include "/etc/nginx/naxsi_whitelist.rules";
|
|
error_log /etc/nginx/naxsi.log;
|
|
proxy_set_header Host $host;
|
|
socks_pass socks5://tor;
|
|
socks_set_host backendurl;
|
|
socks_set_header Host $host;
|
|
socks_redirect off;
|
|
socks_http_version 1.1;
|
|
socks_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
|
#proxy_pass http://proxypassurl;
|
|
|
|
header_filter_by_lua_block {
|
|
local cookie, err = cook:new()
|
|
if not cookie then
|
|
ngx.log(ngx.ERR, err)
|
|
return
|
|
end
|
|
local block_cookie = 0
|
|
|
|
if ngx.resp.get_headers()['captcha-fails'] ~= nil then
|
|
local field, err = cookie:get("dcap")
|
|
if field then
|
|
local failed = ngx.shared.failed
|
|
local fl = failed:get(field)
|
|
if fl ~= nil then
|
|
fl = fl + 1
|
|
else
|
|
fl = 1
|
|
end
|
|
failed:set(field, fl, 3600)
|
|
if fl > 3 then
|
|
block_cookie = 1
|
|
failed:delete(field)
|
|
end
|
|
end
|
|
end
|
|
|
|
if block_cookie > 0 then
|
|
local field, err = cookie:get("dcap")
|
|
if field then
|
|
local blocked_cookies = ngx.shared.blocked_cookies
|
|
blocked_cookies:set(field, 1, 3600)
|
|
end
|
|
end
|
|
}
|
|
}
|
|
}
|