├── dist.ini ├── README.md └── lib └── resty └── tarpit.lua /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-tarpit 2 | abstract = OpenResty time inflation 3 | author = Robert Paprocki (p0pr0ck5) 4 | is_original = yes 5 | license = gpl3 6 | lib_dir = lib 7 | repo_link = https://github.com/p0pr0ck5/lua-resty-tarpit 8 | main_module = lib/resty/tarpit.lua 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Name 2 | 3 | lua-resty-tarpit - capture and delay unwanted requests 4 | 5 | ##Status 6 | 7 | lua-resty-tarpit is in early development and is considered production ready. 8 | 9 | ##Description 10 | 11 | lua-resty-tarpit provides rate-limit protection for sensitive resources. It leverages Nginx's non-blocking archtitecture to artificially increase response latency for resources that are repeatedly accessed. This functionality is designed to protect resources that are publicly accessible, but vulnerable to some form of brute-force attack (e.g., web application admnistrative login pages). It was inspired by the TARPIT iptables module. 12 | 13 | ##Installation 14 | 15 | Clone the lua-resty-tarpit repo into Nginx/OpenResty's Lua package path. Module setup and configuration is detailed in the synopsis. 16 | 17 | ##Synopsis 18 | 19 | ```lua 20 | http { 21 | lua_shared_dict tarpit 10m; 22 | } 23 | 24 | server { 25 | location /login { # or whatever resource you want to protect 26 | access_by_lua ' 27 | local t = require "tarpit" 28 | t.tarpit( 29 | 5, -- request limit 30 | 5, -- reset timer 31 | 1, -- delay time 32 | ) 33 | '; 34 | } 35 | } 36 | ``` 37 | 38 | ##Limitations 39 | 40 | lua-resty-tarpit is undergoing continual development and improvement, and as such, may be limited in its functionality and performance. Currently known limitations can be found within the GitHub issue tracker for this repo. 41 | 42 | ##License 43 | 44 | This program is free software: you can redistribute it and/or modify 45 | it under the terms of the GNU General Public License as published by 46 | the Free Software Foundation, either version 3 of the License, or 47 | (at your option) any later version. 48 | 49 | This program is distributed in the hope that it will be useful, 50 | but WITHOUT ANY WARRANTY; without even the implied warranty of 51 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 52 | GNU General Public License for more details. 53 | 54 | You should have received a copy of the GNU General Public License 55 | along with this program. If not, see 56 | 57 | ##Bugs 58 | 59 | Please report bugs by creating a ticket with the GitHub issue tracker. 60 | -------------------------------------------------------------------------------- /lib/resty/tarpit.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local cjson = require "cjson" 4 | 5 | _M.version = "0.1" 6 | 7 | local function _do_tarpit(t) 8 | ngx.sleep(t) 9 | ngx.exit(418) -- teapot, tarpit, same thing 10 | end 11 | 12 | local function _step_down(premature, key, reset) 13 | local tarpit = ngx.shared.tarpit 14 | local res = tarpit:get(key) 15 | local t = cjson.decode(res) 16 | 17 | ngx.update_time() 18 | 19 | -- reduce the state if the key hasnt been touched in 'delay' seconds 20 | if ((ngx.now() - t.mostrecent) > reset) then 21 | if (t.state > 0) then t.state = t.state - 1 end 22 | t.statestart = ngx.now() 23 | t.staterequests = 0 24 | tarpit:set(key, cjson.encode(t)) 25 | end 26 | 27 | -- keep looping this until we've decremented our state to 0 28 | if (t.state > 0) then ngx.timer.at(reset, _step_down, key, reset) end 29 | end 30 | 31 | -- request_limit: how many requests can be sent before the delay is increased 32 | -- reset: how long in seconds until the state is reset 33 | -- delay: initial time to stall the request 34 | function _M.tarpit(request_limit, reset, delay) 35 | ngx.update_time() 36 | local _to_tarpit = false 37 | local tarpit = ngx.shared.tarpit 38 | local client = ngx.var.remote_addr 39 | local resource = ngx.var.uri 40 | local key = client .. ":" .. resource 41 | 42 | if not tarpit then 43 | ngx.exit(ngx.OK) -- silently bail if the user hasn't setup the tarpit shm 44 | end 45 | 46 | -- TODO: look into lua-resty-lock 47 | local t = {} 48 | local res = tarpit:get(key) 49 | 50 | if not res then 51 | t.staterequests = 0 52 | t.state = 0 53 | t.statestart = ngx.now() 54 | t.mostrecent = ngx.now() 55 | else 56 | t = cjson.decode(res) 57 | end 58 | 59 | -- mark this request 60 | t.staterequests = t.staterequests + 1 61 | t.mostrecent = ngx:now() 62 | 63 | -- figure out if we need to tarpit this request 64 | if (t.staterequests > request_limit or t.state > 0) then 65 | _to_tarpit = true 66 | end 67 | 68 | -- do we need to bump states? 69 | if (t.staterequests > request_limit) then 70 | t.state = t.state + 1 71 | t.statestart = ngx.now() 72 | t.staterequests = 0 73 | -- start the state decrement counter 74 | -- we only need this to run after the first state bump 75 | if t.state == 1 then ngx.timer.at(delay, _step_down, key, reset) end 76 | end 77 | 78 | -- save it up and send em to the grave 79 | tarpit:set(client .. ":" .. resource, cjson.encode(t)) 80 | if (_to_tarpit) then 81 | _do_tarpit(delay * t.state) 82 | end 83 | end 84 | 85 | return _M 86 | --------------------------------------------------------------------------------