|
-- reject-bad-actors.lua
|
|
--
|
|
-- Summary: block client requests matching any criteria in reject list
|
|
--
|
|
--
|
|
-- Copyright (c) 2021, Glenn Strauss (gstrauss () gluelogic.com)
|
|
-- All rights reserved.
|
|
--
|
|
-- License: 3-clause BSD
|
|
--
|
|
--
|
|
-- * check if reject.mcdb database contains remote IP
|
|
-- * check if reject.mcdb database contains Referer
|
|
-- * check if reject.mcdb database contains User-Agent
|
|
--
|
|
-- single mcdb combines reject list of remote IP, Referer, and User-Agent
|
|
-- (e.g. blocks if IP in reject list is in Referer)
|
|
-- remote IP, Referer, and User-Agent not expected to look like one another
|
|
-- chance of false positive is low
|
|
--
|
|
-- prep:
|
|
-- * replace all "/path/to" in instructions and script with actual path to files
|
|
-- * build mcdb
|
|
-- https://github.com/gstrauss/mcdb/blob/master/README
|
|
-- https://github.com/gstrauss/mcdb/blob/master/INSTALL
|
|
-- * install mcdb or use qualified path to mcdbctl when creating database
|
|
-- * build lua-mcdb and copy mcdb.so to /path/to/
|
|
-- https://github.com/gstrauss/mcdb/blob/master/contrib/lua-mcdb/README
|
|
-- * create reject database
|
|
-- perl -e 'while (<>) { chomp; next unless length($_); printf "+%d,0:%s->\n", length($_), $_; } print "\n";' /path/to/rejects/list-one-per-line | mcdbctl make /path/to/reject.mcdb -
|
|
-- * note: database performs exact matches. If case-insensitive is required,
|
|
-- then lowercase input when database is created (lc($_)) and modify code
|
|
-- below to lowercase request item before querying database
|
|
--
|
|
-- lighttpd.conf
|
|
-- server.modules += ("mod_magnet")
|
|
-- magnet.attract-raw-url-to = ( "/path/to/reject-bad-actors.lua" )
|
|
|
|
-- get database file handle (cached in global)
|
|
-- note: when database changes, 'touch' file containing this script in order to
|
|
-- trigger lighttpd mod_magnet_cache to reload this script and re-open the mcdb
|
|
-- (lighttpd etags must be enabled (default) for modified script to be noticed)
|
|
-- (alternative: skip caching open mcdb in _G to have mcdb opened each request)
|
|
local reject = _G.rejectdb
|
|
if (not reject) then
|
|
-- open database file
|
|
local db = "/path/to/reject.mcdb"
|
|
local mcdb = package.loaded["mcdb"]
|
|
if (not mcdb) then
|
|
-- update search path with path to mcdb.so
|
|
-- (todo: should skip if already present)
|
|
--package.cpath = '/path/to/?.so'
|
|
package.cpath = package.cpath .. ';/path/to/?.so'
|
|
mcdb = require("mcdb")
|
|
end
|
|
local rejectdb, errstr = mcdb.init(db)
|
|
if rejectdb == nil then
|
|
io.stderr:write('mcdb.init("' .. db .. '"): ' .. errstr .. '\n')
|
|
return 0 -- fail open (not closed); allow request to proceed
|
|
end
|
|
_G.rejectdb = rejectdb
|
|
reject = rejectdb
|
|
end
|
|
|
|
-- check remote IP
|
|
local ip = lighty.env["request.remote-ip"]
|
|
if (reject(ip) ~= nil) then -- exact match on IP string (normalized)
|
|
return 403 -- HTTP 403 Forbidden
|
|
end
|
|
|
|
-- check User-Agent
|
|
local ua = lighty.request["user-agent"]
|
|
if (ua ~= nil and reject(ua) ~= nil) then
|
|
return 410 -- HTTP 410 Gone
|
|
end
|
|
|
|
-- check Referer
|
|
local ref = lighty.request["referer"]
|
|
if (ref ~= nil) then
|
|
-- normalize Referer to some extent (optional)
|
|
--ref = ref:lower() -- lowercase
|
|
--ref = ref:gsub("%.+$", "") -- remove trailing dots
|
|
-- suffix match from longest to shortest name, splitting on dot
|
|
local dot = 1
|
|
while (dot) do
|
|
if (reject(ref) ~= nil) then
|
|
return 403 -- HTTP 403 Forbidden
|
|
end
|
|
dot = ref:find(".", 1, true)
|
|
if dot then ref = ref:sub(dot+1) end
|
|
end
|
|
end
|
|
|
|
-- allow request to proceed
|
|
return 0
|