Project

General

Profile

AbsoLUAtion » reject-bad-actors.lua

block client requests matching entries in mcdb reject list - gstrauss, 2021-03-18 10:52

 
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
(4-4/5)