1
|
-- reject-bad-actors.lua
|
2
|
--
|
3
|
-- Summary: block client requests matching any criteria in reject list
|
4
|
--
|
5
|
--
|
6
|
-- Copyright (c) 2021, Glenn Strauss (gstrauss () gluelogic.com)
|
7
|
-- All rights reserved.
|
8
|
--
|
9
|
-- License: 3-clause BSD
|
10
|
--
|
11
|
--
|
12
|
-- * check if reject.mcdb database contains remote IP
|
13
|
-- * check if reject.mcdb database contains Referer
|
14
|
-- * check if reject.mcdb database contains User-Agent
|
15
|
--
|
16
|
-- single mcdb combines reject list of remote IP, Referer, and User-Agent
|
17
|
-- (e.g. blocks if IP in reject list is in Referer)
|
18
|
-- remote IP, Referer, and User-Agent not expected to look like one another
|
19
|
-- chance of false positive is low
|
20
|
--
|
21
|
-- prep:
|
22
|
-- * replace all "/path/to" in instructions and script with actual path to files
|
23
|
-- * build mcdb
|
24
|
-- https://github.com/gstrauss/mcdb/blob/master/README
|
25
|
-- https://github.com/gstrauss/mcdb/blob/master/INSTALL
|
26
|
-- * install mcdb or use qualified path to mcdbctl when creating database
|
27
|
-- * build lua-mcdb and copy mcdb.so to /path/to/
|
28
|
-- https://github.com/gstrauss/mcdb/blob/master/contrib/lua-mcdb/README
|
29
|
-- * create reject database
|
30
|
-- 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 -
|
31
|
-- * note: database performs exact matches. If case-insensitive is required,
|
32
|
-- then lowercase input when database is created (lc($_)) and modify code
|
33
|
-- below to lowercase request item before querying database
|
34
|
--
|
35
|
-- lighttpd.conf
|
36
|
-- server.modules += ("mod_magnet")
|
37
|
-- magnet.attract-raw-url-to = ( "/path/to/reject-bad-actors.lua" )
|
38
|
|
39
|
-- get database file handle (cached in global)
|
40
|
-- note: when database changes, 'touch' file containing this script in order to
|
41
|
-- trigger lighttpd mod_magnet_cache to reload this script and re-open the mcdb
|
42
|
-- (lighttpd etags must be enabled (default) for modified script to be noticed)
|
43
|
-- (alternative: skip caching open mcdb in _G to have mcdb opened each request)
|
44
|
local reject = _G.rejectdb
|
45
|
if (not reject) then
|
46
|
-- open database file
|
47
|
local db = "/path/to/reject.mcdb"
|
48
|
local mcdb = package.loaded["mcdb"]
|
49
|
if (not mcdb) then
|
50
|
-- update search path with path to mcdb.so
|
51
|
-- (todo: should skip if already present)
|
52
|
--package.cpath = '/path/to/?.so'
|
53
|
package.cpath = package.cpath .. ';/path/to/?.so'
|
54
|
mcdb = require("mcdb")
|
55
|
end
|
56
|
local rejectdb, errstr = mcdb.init(db)
|
57
|
if rejectdb == nil then
|
58
|
io.stderr:write('mcdb.init("' .. db .. '"): ' .. errstr .. '\n')
|
59
|
return 0 -- fail open (not closed); allow request to proceed
|
60
|
end
|
61
|
_G.rejectdb = rejectdb
|
62
|
reject = rejectdb
|
63
|
end
|
64
|
|
65
|
-- check remote IP
|
66
|
local ip = lighty.env["request.remote-ip"]
|
67
|
if (reject(ip) ~= nil) then -- exact match on IP string (normalized)
|
68
|
return 403 -- HTTP 403 Forbidden
|
69
|
end
|
70
|
|
71
|
-- check User-Agent
|
72
|
local ua = lighty.request["user-agent"]
|
73
|
if (ua ~= nil and reject(ua) ~= nil) then
|
74
|
return 410 -- HTTP 410 Gone
|
75
|
end
|
76
|
|
77
|
-- check Referer
|
78
|
local ref = lighty.request["referer"]
|
79
|
if (ref ~= nil) then
|
80
|
-- normalize Referer to some extent (optional)
|
81
|
--ref = ref:lower() -- lowercase
|
82
|
--ref = ref:gsub("%.+$", "") -- remove trailing dots
|
83
|
-- suffix match from longest to shortest name, splitting on dot
|
84
|
local dot = 1
|
85
|
while (dot) do
|
86
|
if (reject(ref) ~= nil) then
|
87
|
return 403 -- HTTP 403 Forbidden
|
88
|
end
|
89
|
dot = ref:find(".", 1, true)
|
90
|
if dot then ref = ref:sub(dot+1) end
|
91
|
end
|
92
|
end
|
93
|
|
94
|
-- allow request to proceed
|
95
|
return 0
|