From 602c13c8d767bb0f723b66522f6b88bdbc263f81 Mon Sep 17 00:00:00 2001 From: spag Date: Sun, 3 Feb 2013 18:29:27 +0100 Subject: perimeter class added --- misc/freeswitch/scripts/common/perimeter.lua | 214 +++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 misc/freeswitch/scripts/common/perimeter.lua (limited to 'misc/freeswitch/scripts/common/perimeter.lua') diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua new file mode 100644 index 0000000..d930514 --- /dev/null +++ b/misc/freeswitch/scripts/common/perimeter.lua @@ -0,0 +1,214 @@ +-- Gemeinschaft 5 module: perimeter class +-- (c) AMOOMA GmbH 2013 +-- + +module(...,package.seeall) + + +Perimeter = {} + + +function Perimeter.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.log = arg.log; + self.class = 'perimeter' + self.database = arg.database; + self.domain = arg.domain; + self.sources = {}; + + self.checks_available = { + check_frequency = self.check_frequency, + check_username_scan = self.check_username_scan, + check_bad_headers = self.check_bad_headers, + }; + + return object; +end + + +function Perimeter.setup(self, event) + require 'common.configuration_table'; + local config = common.configuration_table.get(self.database, 'perimeter'); + + self.contact_count_threshold = 10; + self.contact_span_threshold = 2; + self.name_changes_threshold = 2; + self.blacklist_file = '/var/opt/gemeinschaft/firewall/blacklist'; + self.blacklist_file_comment = '# PERIMETER_BAN - points: {points}, generated: {date}'; + self.blacklist_file_entry = '{received_ip} udp 5060'; + self.ban_command = 'sudo /sbin/service shorewall refresh'; + self.ban_threshold = 20; + self.ban_tries = 1; + + if config and config.general then + for key, value in pairs(config.general) do + self[key] = value; + end + end + + self.checks = config.checks; + self.bad_headers = config.bad_headers; + + self.log:info('[perimeter] PERIMETER - setup perimeter defense'); +end + + +function Perimeter.record_load(self, event) + if not self.sources[event.key] then + self.sources[event.key] = { + contact_first = event.timestamp, + contact_last = event.timestamp, + contact_count = 0, + span_contact_count = 0, + span_start = event.timestamp, + points = 0, + banned = 0, + } + end + + return self.sources[event.key]; +end + + +function Perimeter.format_date(self, value) + local epoch = tonumber(tostring(value/1000000):match('^(%d-)%.')); + return os.date('%Y-%m-%d %X', tonumber(epoch)) .. '.' .. (value-(epoch*1000000)); +end + + +function Perimeter.record_update(self, event) + event.record.contact_last = event.timestamp; + event.record.contact_count = event.record.contact_count + 1; + event.record.points = event.points or event.record.points; + event.record.span_start = event.span_start or event.record.span_start; + event.record.span_contact_count = (event.span_contact_count or event.record.span_contact_count) + 1; + event.record.users = event.users or event.record.users; +end + + +function Perimeter.check(self, event) + event.record = self:record_load(event); + -- self.log:debug('[', event.sequence, '] PERIMETER_CHECK - received: ', event.received_ip, ':', event.received_port, ', contacts: ', event.record.contact_count, ', since: ', self:format_date(event.record.contact_first), ', points: ', event.record.points); + + if event.record.banned <= self.ban_tries then + for check_name, check_points in pairs(self.checks) do + if self.checks_available[check_name] then + local result = self.checks_available[check_name](self, event); + if tonumber(result) then + event.points = (event.points or event.record.points) + result * check_points; + end + end + end + end + + if (event.points or event.record.points) > self.ban_threshold and event.record.banned <= self.ban_tries then + if event.record.banned > 0 and event.record.banned == self.ban_tries then + self.log:warning('[', event.sequence, '] PERIMETER_BAN_FUTILE - points: ', event.points,', event: ', event.class, ', ip: ', event.received_ip, ', to: ', event.to_user, '@', event.to_host); + else + self.log:notice('[', event.sequence, '] PERIMETER_BAN - threshold reached: ', event.points,', event: ', event.class, ', ip: ', event.received_ip, ', to: ', event.to_user, '@', event.to_host); + if event.record.banned == 0 then + self:append_blacklist_file(event); + end + self:execute_ban(event); + end + + event.record.banned = event.record.banned + 1; + event.span_start = event.timestamp; + event.span_contact_count = 0; + event.points = 0; + end + + self:record_update(event); +end + + +function Perimeter.check_frequency(self, event) + if event.record.span_contact_count >= self.contact_count_threshold then + self.log:info('[', event.sequence, '] PERIMETER_FREQUENCY_CHECK - contacts: ', event.record.span_contact_count, ' in < ', (event.timestamp - event.record.span_start)/1000000, ' sec, threshold: ', self.contact_count_threshold, ' in ', self.contact_span_threshold, ' sec'); + event.span_contact_count = 0; + event.span_start = event.timestamp; + return 1; + elseif (event.timestamp - event.record.span_start) > (self.contact_span_threshold * 1000000) then + event.span_contact_count = 0; + event.span_start = event.timestamp; + end +end + + +function Perimeter.check_username_scan(self, event) + if not event.to_user then + return; + end + + if not event.record.users then + event.users = { event.to_user }; + return; + end + + if #event.record.users >= self.name_changes_threshold then + self.log:info('[', event.sequence, '] PERIMETER_USER_SCAN - user names: ', #event.record.users, ', threshold: ', self.name_changes_threshold); + event.users = {}; + return 1; + else + for index=1, #event.record.users do + if event.record.users[index] == tostring(event.to_user) then + return + end + end + + if not event.users then + event.users = event.record.users or {}; + end + table.insert(event.users, tostring(event.to_user)); + end +end + + +function Perimeter.check_bad_headers(self, event) + local points = nil; + for name, pattern in pairs(self.bad_headers) do + local success, result = pcall(string.find, event[name], pattern); + if success and result then + self.log:info('[', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern); + points = (points or 0) + 1; + end + end + + return points; +end + + +function Perimeter.append_blacklist_file(self, event) + local blacklist = io.open(self.blacklist_file, 'a'); + if not blacklist then + self.log:error('[', event.sequence, '] PERIMETER_APPEND_BLACKLIST - could not open file: ', self.blacklist_file); + return false; + end + + event.date = self:format_date(event.timestamp); + + if self.blacklist_file_comment then + blacklist:write(self:expand_variables(self.blacklist_file_comment, event), '\n'); + end + + self.log:debug('[', event.sequence, '] PERIMETER_APPEND_BLACKLIST - ip: ', event.received_ip); + blacklist:write(self:expand_variables(self.blacklist_file_entry, event), '\n'); + blacklist:close(); +end + + +function Perimeter.execute_ban(self, event) + local command = self:expand_variables(self.ban_command, event); + self.log:debug('[', event.sequence, '] PERIMETER_EXECUTE_BAN - ip: ', event.received_ip, ', command: ', command); + local result = os.execute(command); +end + + +function Perimeter.expand_variables(self, line, variables) + return (line:gsub('{([%a%d%._]+)}', function(captured) + return variables[captured] or ''; + end)) +end -- cgit v1.2.3 From 077234ab170416d8aac9bf5887aa2093b2d9c5c9 Mon Sep 17 00:00:00 2001 From: spag Date: Sun, 3 Feb 2013 19:31:18 +0100 Subject: better logging --- misc/freeswitch/scripts/common/perimeter.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'misc/freeswitch/scripts/common/perimeter.lua') diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua index d930514..5623b40 100644 --- a/misc/freeswitch/scripts/common/perimeter.lua +++ b/misc/freeswitch/scripts/common/perimeter.lua @@ -91,7 +91,7 @@ end function Perimeter.check(self, event) event.record = self:record_load(event); - -- self.log:debug('[', event.sequence, '] PERIMETER_CHECK - received: ', event.received_ip, ':', event.received_port, ', contacts: ', event.record.contact_count, ', since: ', self:format_date(event.record.contact_first), ', points: ', event.record.points); + -- self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_CHECK - received: ', event.received_ip, ':', event.received_port, ', contacts: ', event.record.contact_count, ', since: ', self:format_date(event.record.contact_first), ', points: ', event.record.points); if event.record.banned <= self.ban_tries then for check_name, check_points in pairs(self.checks) do @@ -106,9 +106,9 @@ function Perimeter.check(self, event) if (event.points or event.record.points) > self.ban_threshold and event.record.banned <= self.ban_tries then if event.record.banned > 0 and event.record.banned == self.ban_tries then - self.log:warning('[', event.sequence, '] PERIMETER_BAN_FUTILE - points: ', event.points,', event: ', event.class, ', ip: ', event.received_ip, ', to: ', event.to_user, '@', event.to_host); + self.log:warning('[', event.key, '/', event.sequence, '] PERIMETER_BAN_FUTILE - points: ', event.points,', event: ', event.class, ', from: ', event.from_user, '@', event.from_host, ', to: ', event.to_user, '@', event.to_host); else - self.log:notice('[', event.sequence, '] PERIMETER_BAN - threshold reached: ', event.points,', event: ', event.class, ', ip: ', event.received_ip, ', to: ', event.to_user, '@', event.to_host); + self.log:notice('[', event.key, '/', event.sequence, '] PERIMETER_BAN - threshold reached: ', event.points,', event: ', event.class, ', from: ', event.from_user, '@', event.from_host, ', to: ', event.to_user, '@', event.to_host); if event.record.banned == 0 then self:append_blacklist_file(event); end @@ -127,7 +127,7 @@ end function Perimeter.check_frequency(self, event) if event.record.span_contact_count >= self.contact_count_threshold then - self.log:info('[', event.sequence, '] PERIMETER_FREQUENCY_CHECK - contacts: ', event.record.span_contact_count, ' in < ', (event.timestamp - event.record.span_start)/1000000, ' sec, threshold: ', self.contact_count_threshold, ' in ', self.contact_span_threshold, ' sec'); + self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_FREQUENCY_CHECK - contacts: ', event.record.span_contact_count, ' in < ', (event.timestamp - event.record.span_start)/1000000, ' sec, threshold: ', self.contact_count_threshold, ' in ', self.contact_span_threshold, ' sec'); event.span_contact_count = 0; event.span_start = event.timestamp; return 1; @@ -149,7 +149,7 @@ function Perimeter.check_username_scan(self, event) end if #event.record.users >= self.name_changes_threshold then - self.log:info('[', event.sequence, '] PERIMETER_USER_SCAN - user names: ', #event.record.users, ', threshold: ', self.name_changes_threshold); + self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_USER_SCAN - user names: ', #event.record.users, ', threshold: ', self.name_changes_threshold); event.users = {}; return 1; else @@ -172,7 +172,7 @@ function Perimeter.check_bad_headers(self, event) for name, pattern in pairs(self.bad_headers) do local success, result = pcall(string.find, event[name], pattern); if success and result then - self.log:info('[', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern); + self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern); points = (points or 0) + 1; end end @@ -184,7 +184,7 @@ end function Perimeter.append_blacklist_file(self, event) local blacklist = io.open(self.blacklist_file, 'a'); if not blacklist then - self.log:error('[', event.sequence, '] PERIMETER_APPEND_BLACKLIST - could not open file: ', self.blacklist_file); + self.log:error('[', event.key, '/', event.sequence, '] PERIMETER_APPEND_BLACKLIST - could not open file: ', self.blacklist_file); return false; end @@ -194,7 +194,7 @@ function Perimeter.append_blacklist_file(self, event) blacklist:write(self:expand_variables(self.blacklist_file_comment, event), '\n'); end - self.log:debug('[', event.sequence, '] PERIMETER_APPEND_BLACKLIST - ip: ', event.received_ip); + self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_APPEND_BLACKLIST - file: ', self.blacklist_file); blacklist:write(self:expand_variables(self.blacklist_file_entry, event), '\n'); blacklist:close(); end @@ -202,7 +202,7 @@ end function Perimeter.execute_ban(self, event) local command = self:expand_variables(self.ban_command, event); - self.log:debug('[', event.sequence, '] PERIMETER_EXECUTE_BAN - ip: ', event.received_ip, ', command: ', command); + self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_EXECUTE_BAN - command: ', command); local result = os.execute(command); end -- cgit v1.2.3 From f1ccee494495e4149d0b892743b0d60612d8d790 Mon Sep 17 00:00:00 2001 From: spag Date: Sun, 3 Feb 2013 20:04:16 +0100 Subject: reset username list after successful registration --- misc/freeswitch/scripts/common/perimeter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'misc/freeswitch/scripts/common/perimeter.lua') diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua index 5623b40..5de86bf 100644 --- a/misc/freeswitch/scripts/common/perimeter.lua +++ b/misc/freeswitch/scripts/common/perimeter.lua @@ -143,7 +143,7 @@ function Perimeter.check_username_scan(self, event) return; end - if not event.record.users then + if not event.record.users or tostring(event.auth_result) == 'SUCCESS' or tostring(event.auth_result) == 'RENEWED' then event.users = { event.to_user }; return; end -- cgit v1.2.3 From 0db4c7250feab288018d42cbd5c7d12657d60e3a Mon Sep 17 00:00:00 2001 From: spag Date: Mon, 4 Feb 2013 09:39:42 +0100 Subject: check calls --- misc/freeswitch/scripts/common/perimeter.lua | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'misc/freeswitch/scripts/common/perimeter.lua') diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua index 5de86bf..0670fee 100644 --- a/misc/freeswitch/scripts/common/perimeter.lua +++ b/misc/freeswitch/scripts/common/perimeter.lua @@ -42,6 +42,8 @@ function Perimeter.setup(self, event) self.ban_command = 'sudo /sbin/service shorewall refresh'; self.ban_threshold = 20; self.ban_tries = 1; + self.checks = { register = {}, call = {} }; + self.bad_headers = { register = {}, call = {} }; if config and config.general then for key, value in pairs(config.general) do @@ -49,8 +51,10 @@ function Perimeter.setup(self, event) end end - self.checks = config.checks; - self.bad_headers = config.bad_headers; + self.checks.register = config.checks_register or {}; + self.checks.call = config.checks_call or {}; + self.bad_headers.register = config.bad_headers_register; + self.bad_headers.call = config.bad_headers_call; self.log:info('[perimeter] PERIMETER - setup perimeter defense'); end @@ -66,7 +70,7 @@ function Perimeter.record_load(self, event) span_start = event.timestamp, points = 0, banned = 0, - } + }; end return self.sources[event.key]; @@ -90,11 +94,14 @@ end function Perimeter.check(self, event) - event.record = self:record_load(event); - -- self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_CHECK - received: ', event.received_ip, ':', event.received_port, ', contacts: ', event.record.contact_count, ', since: ', self:format_date(event.record.contact_first), ', points: ', event.record.points); - + if not event or not event.key then + self.log:warning('[perimeter] PERIMETER_CHECK - no event/key'); + return; + end + + event.record = self:record_load(event); if event.record.banned <= self.ban_tries then - for check_name, check_points in pairs(self.checks) do + for check_name, check_points in pairs(self.checks[event.action]) do if self.checks_available[check_name] then local result = self.checks_available[check_name](self, event); if tonumber(result) then @@ -169,7 +176,7 @@ end function Perimeter.check_bad_headers(self, event) local points = nil; - for name, pattern in pairs(self.bad_headers) do + for name, pattern in pairs(self.bad_headers[event.action]) do local success, result = pcall(string.find, event[name], pattern); if success and result then self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern); -- cgit v1.2.3 From 4594ac1559c6ecd534834b41b31d61c281d3401d Mon Sep 17 00:00:00 2001 From: spag Date: Mon, 4 Feb 2013 10:01:40 +0100 Subject: expand variables --- misc/freeswitch/scripts/common/perimeter.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'misc/freeswitch/scripts/common/perimeter.lua') diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua index 0670fee..d1eecd1 100644 --- a/misc/freeswitch/scripts/common/perimeter.lua +++ b/misc/freeswitch/scripts/common/perimeter.lua @@ -177,6 +177,7 @@ end function Perimeter.check_bad_headers(self, event) local points = nil; for name, pattern in pairs(self.bad_headers[event.action]) do + pattern = self:expand_variables(pattern, event); local success, result = pcall(string.find, event[name], pattern); if success and result then self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern); -- cgit v1.2.3 From 242470bdb4fa67780e7467c6889b7869dff05d1c Mon Sep 17 00:00:00 2001 From: spag Date: Mon, 4 Feb 2013 13:10:52 +0100 Subject: better logging --- misc/freeswitch/scripts/common/perimeter.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'misc/freeswitch/scripts/common/perimeter.lua') diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua index d1eecd1..de3f993 100644 --- a/misc/freeswitch/scripts/common/perimeter.lua +++ b/misc/freeswitch/scripts/common/perimeter.lua @@ -111,6 +111,14 @@ function Perimeter.check(self, event) end end + if tonumber(event.points) and event.points < 0 then + event.points = 0; + end + + if event.points then + self.log:info('[', event.key, '/', event.sequence, '] PERIMETER suspicion rising - points: ', event.points,', ', event.action, '=', event.class, ', from: ', event.from_user, '@', event.from_host, ', to: ', event.to_user, '@', event.to_host, ', user_agent: ', event.user_agent); + end + if (event.points or event.record.points) > self.ban_threshold and event.record.banned <= self.ban_tries then if event.record.banned > 0 and event.record.banned == self.ban_tries then self.log:warning('[', event.key, '/', event.sequence, '] PERIMETER_BAN_FUTILE - points: ', event.points,', event: ', event.class, ', from: ', event.from_user, '@', event.from_host, ', to: ', event.to_user, '@', event.to_host); @@ -134,7 +142,7 @@ end function Perimeter.check_frequency(self, event) if event.record.span_contact_count >= self.contact_count_threshold then - self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_FREQUENCY_CHECK - contacts: ', event.record.span_contact_count, ' in < ', (event.timestamp - event.record.span_start)/1000000, ' sec, threshold: ', self.contact_count_threshold, ' in ', self.contact_span_threshold, ' sec'); + self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_FREQUENCY_CHECK - contacts: ', event.record.span_contact_count, ' in < ', (event.timestamp - event.record.span_start)/1000000, ' sec, threshold: ', self.contact_count_threshold, ' in ', self.contact_span_threshold, ' sec'); event.span_contact_count = 0; event.span_start = event.timestamp; return 1; @@ -156,7 +164,7 @@ function Perimeter.check_username_scan(self, event) end if #event.record.users >= self.name_changes_threshold then - self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_USER_SCAN - user names: ', #event.record.users, ', threshold: ', self.name_changes_threshold); + self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_USER_SCAN - user names: ', #event.record.users, ', threshold: ', self.name_changes_threshold); event.users = {}; return 1; else @@ -180,7 +188,7 @@ function Perimeter.check_bad_headers(self, event) pattern = self:expand_variables(pattern, event); local success, result = pcall(string.find, event[name], pattern); if success and result then - self.log:info('[', event.key, '/', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern); + self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern); points = (points or 0) + 1; end end -- cgit v1.2.3 From af16d6106e5b4c81a8b167397c0fffd728762fa0 Mon Sep 17 00:00:00 2001 From: spag Date: Sun, 10 Feb 2013 17:02:55 +0100 Subject: update intruder --- misc/freeswitch/scripts/common/perimeter.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'misc/freeswitch/scripts/common/perimeter.lua') diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua index de3f993..0815d33 100644 --- a/misc/freeswitch/scripts/common/perimeter.lua +++ b/misc/freeswitch/scripts/common/perimeter.lua @@ -128,6 +128,7 @@ function Perimeter.check(self, event) self:append_blacklist_file(event); end self:execute_ban(event); + event.ban_time = os.time(); end event.record.banned = event.record.banned + 1; @@ -136,6 +137,10 @@ function Perimeter.check(self, event) event.points = 0; end + if event.points then + self:update_intruder(event); + end + self:record_update(event); end @@ -145,6 +150,7 @@ function Perimeter.check_frequency(self, event) self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_FREQUENCY_CHECK - contacts: ', event.record.span_contact_count, ' in < ', (event.timestamp - event.record.span_start)/1000000, ' sec, threshold: ', self.contact_count_threshold, ' in ', self.contact_span_threshold, ' sec'); event.span_contact_count = 0; event.span_start = event.timestamp; + event.contacts_per_second = event.record.span_contact_count / ((event.timestamp - event.record.span_start)/1000000) return 1; elseif (event.timestamp - event.record.span_start) > (self.contact_span_threshold * 1000000) then event.span_contact_count = 0; @@ -222,6 +228,11 @@ function Perimeter.execute_ban(self, event) local result = os.execute(command); end +function Perimeter.update_intruder(self, event) + require 'common.intruder'; + local result = common.intruder.Intruder:new{ log = self.log, database = self.database }:update_blacklist(event); +end + function Perimeter.expand_variables(self, line, variables) return (line:gsub('{([%a%d%._]+)}', function(captured) -- cgit v1.2.3