diff --git a/security/abuseipdb/Makefile b/security/abuseipdb/Makefile new file mode 100644 index 0000000000..742cd1b8cc --- /dev/null +++ b/security/abuseipdb/Makefile @@ -0,0 +1,7 @@ +PLUGIN_NAME= abuseipdb +PLUGIN_VERSION= 0.1 +PLUGIN_REVISION= 1 +PLUGIN_COMMENT= Block hosts based on incoming rules +PLUGIN_MAINTAINER= netwiz@crc.id.au + +.include "../../Mk/plugins.mk" diff --git a/security/abuseipdb/src/etc/inc/plugins.inc.d/abuseipdb.inc b/security/abuseipdb/src/etc/inc/plugins.inc.d/abuseipdb.inc new file mode 100644 index 0000000000..e717174fb6 --- /dev/null +++ b/security/abuseipdb/src/etc/inc/plugins.inc.d/abuseipdb.inc @@ -0,0 +1,94 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +use OPNsense\Core\Config; +use OPNsense\Firewall\Alias; +use OPNsense\Firewall\Plugin; + +function add_alias_if_not_exist($name, $description) +{ + $model = new Alias(); + + if ($model->getByName($name) != null) { + return; + } + + $new_alias = $model->aliases->alias->Add(); + $new_alias->name = $name; + $new_alias->description = $description; + $new_alias->type = 'external'; + $model->serializeToConfig(); + Config::getInstance()->save(); +} + +function abuseipdb_firewall(Plugin $fw) +{ + global $config; + if ( + isset($config['OPNsense']['abuseipdb']['general']['enabled']) && + $config['OPNsense']['abuseipdb']['general']['enabled'] == 1 + ) { + add_alias_if_not_exist('abuseipdb', 'abuseipdb blocklist'); + $fw->registerFilterRule( + 1, /* priority */ + array( + 'ipprotocol' => 'inet46', + 'descr' => 'abuseipdb blocklist', + 'from' => '', + 'direction' => 'in', + 'type' => 'block', + 'log' => $config['OPNsense']['abuseipdb']['general']['log_denies'], + 'tag' => "", + 'quick' => true + ) + ); + } +} + +function abuseipdb_services() +{ + global $config; + $services = array(); + + if ( + isset($config['OPNsense']['abuseipdb']['general']['enabled']) && + $config['OPNsense']['abuseipdb']['general']['enabled'] == 1 + ) { + $services[] = array( + 'description' => 'abuseipdb Daemon', + 'configd' => array( + 'restart' => array('abuseipdb restart'), + 'start' => array('abuseipdb start'), + 'stop' => array('abuseipdb stop'), + ), + 'name' => 'abuseipdb', + 'pidfile' => '/var/run/abuseipdb.pid' + ); + } + + return $services; +} diff --git a/security/abuseipdb/src/etc/rc.d/abuseipdb b/security/abuseipdb/src/etc/rc.d/abuseipdb new file mode 100755 index 0000000000..44ff3ab313 --- /dev/null +++ b/security/abuseipdb/src/etc/rc.d/abuseipdb @@ -0,0 +1,50 @@ +#!/bin/sh +# PROVIDE: abuseipdb +# REQUIRE: DAEMON NETWORKING syslogd + +. /etc/rc.subr + +name="abuseipdb" +rcvar="abuseipdb_enable" + +load_rc_config $name + +: "${abuseipdb_enable="NO"}" + +start() { + /usr/local/opnsense/scripts/abuseipdb/abuseipdb & + exit $? +} + +stop() { + read pid < /var/run/abuseipdb.pid + kill -TERM $pid +} + +status() { + if [ -f /var/run/abuseipdb.pid ]; then + read pid < /var/run/abuseipdb.pid + ps $pid > /dev/null + if [ "$?" == "0" ]; then + echo "Process is running: $(ps $pid)" + exit 0 + fi + fi + echo "Process is not running..." +} + +case $1 in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + status) + status + ;; +esac diff --git a/security/abuseipdb/src/etc/rc.syshook.d/start/99-abuseipdb b/security/abuseipdb/src/etc/rc.syshook.d/start/99-abuseipdb new file mode 100755 index 0000000000..46cff0f43a --- /dev/null +++ b/security/abuseipdb/src/etc/rc.syshook.d/start/99-abuseipdb @@ -0,0 +1,3 @@ +#!/bin/sh +# https://docs.opnsense.org/development/backend/autorun.html +/usr/local/etc/rc.d/abuseipdb start diff --git a/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/Api/ServiceController.php b/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/Api/ServiceController.php new file mode 100644 index 0000000000..fc1dad48a7 --- /dev/null +++ b/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/Api/ServiceController.php @@ -0,0 +1,47 @@ +request->isGet()) { + $mdlabuseipdb = new abuseipdb(); + $result['abuseipdb'] = $mdlabuseipdb->getNodes(); + } + return $result; + } + + /** + * update abuseipdb settings + * @return array status + * @throws \OPNsense\Base\ModelException + * @throws \ReflectionException + */ + public function setAction() + { + $result = array("result" => "failed"); + if ($this->request->isPost()) { + // load model and update with provided data + $mdlabuseipdb = new abuseipdb(); + $mdlabuseipdb->setNodes($this->request->getPost("abuseipdb")); + + // perform validation + $valMsgs = $mdlabuseipdb->performValidation(); + foreach ($valMsgs as $field => $msg) { + if (!array_key_exists("validations", $result)) { + $result["validations"] = array(); + } + $result["validations"]["abuseipdb." . $msg->getField()] = $msg->getMessage(); + } + + // serialize model to config and save + if ($valMsgs->count() == 0) { + $mdlabuseipdb->serializeToConfig(); + Config::getInstance()->save(); + $result["result"] = "saved"; + } + } + return $result; + } +} diff --git a/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/Api/SimplifiedSettingsController.php b/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/Api/SimplifiedSettingsController.php new file mode 100644 index 0000000000..3c6928d014 --- /dev/null +++ b/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/Api/SimplifiedSettingsController.php @@ -0,0 +1,43 @@ +view->pick('OPNsense/abuseipdb/index'); + // fetch form data "general" in + $this->view->generalForm = $this->getForm("general"); + } +} diff --git a/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/forms/general.xml b/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/forms/general.xml new file mode 100644 index 0000000000..d65a038a37 --- /dev/null +++ b/security/abuseipdb/src/opnsense/mvc/app/controllers/OPNsense/abuseipdb/forms/general.xml @@ -0,0 +1,54 @@ +
+ + abuseipdb.general.enabled + + checkbox + Enable the service + + + abuseipdb.general.flush_on_start + + checkbox + Flush the abuseipdb Firewall Alias group when starting. If an API Key is set, will pre-populate with a download from abuseipdb.com + + + abuseipdb.general.log_denies + + checkbox + Log packets that are denied by the abuseipdb firewall rule to the firewall logs + + + abuseipdb.general.api_key + + text + + API Key from abuseipdb.com + + + abuseipdb.general.filter_id + + text + + Firewall Rule ID + + + abuseipdb.general.packet_count + + text + + + + abuseipdb.general.packet_timeframe + + text + + How long to track packets in seconds. + + + abuseipdb.general.log_interval + + text + + Log status every X seconds + +
diff --git a/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/ACL/ACL.xml b/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/ACL/ACL.xml new file mode 100644 index 0000000000..6b9138b069 --- /dev/null +++ b/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/ACL/ACL.xml @@ -0,0 +1,9 @@ + + + Services: abuseipdb + + ui/abuseipdb/* + api/abuseipdb/* + + + diff --git a/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/Menu/Menu.xml b/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/Menu/Menu.xml new file mode 100644 index 0000000000..429a405dc7 --- /dev/null +++ b/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/Menu/Menu.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/abuseipdb.php b/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/abuseipdb.php new file mode 100644 index 0000000000..a7068590cc --- /dev/null +++ b/security/abuseipdb/src/opnsense/mvc/app/models/OPNsense/abuseipdb/abuseipdb.php @@ -0,0 +1,37 @@ + + //OPNsense/abuseipdb + + abuseipdb.com firewall / reporter daemon + + + + + + + 1 + Y + + + 0 + Y + + + 0 + Y + + + N + + + Y + + + 10 + Y + + + 120 + Y + + + 60 + N + + + + diff --git a/security/abuseipdb/src/opnsense/mvc/app/views/OPNsense/abuseipdb/index.volt b/security/abuseipdb/src/opnsense/mvc/app/views/OPNsense/abuseipdb/index.volt new file mode 100644 index 0000000000..211f37c318 --- /dev/null +++ b/security/abuseipdb/src/opnsense/mvc/app/views/OPNsense/abuseipdb/index.volt @@ -0,0 +1,59 @@ +{# + +OPNsense® is Copyright © 2014 – 2015 by Deciso B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#} + + + + + +
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_GeneralSettings'])}} +
+ +
+ +
diff --git a/security/abuseipdb/src/opnsense/scripts/abuseipdb/abuseipdb b/security/abuseipdb/src/opnsense/scripts/abuseipdb/abuseipdb new file mode 100755 index 0000000000..557455e0ff --- /dev/null +++ b/security/abuseipdb/src/opnsense/scripts/abuseipdb/abuseipdb @@ -0,0 +1,248 @@ +#!/usr/local/bin/php +seek(PHP_INT_MAX); +$eof = $file->key(); +$file = null; + +## Init known hosts array +$known_ips = array(); +$report_queue = array(); + +## Handle 429 responses from abuseipdb. +$ratelimit_delay = 5; +$ratelimit_delay_max = 180; +$ratelimit_expires = 0; + +## Init the last log time. +$log_last = 0; + +## Prime the blocklist if we have an API Key. +if ( $api_key != "" ) { + get_blocklist($api_key, $flush_on_start); +} + +while (1) { + sleep(1); + + ## If the log file doesn't exist, just sleep. + if ( ! file_exists($log) ) { continue; } + + ## Process from the last EOF marker... + $file = new SplFileObject($log); + $file->seek($eof); + + ## Seek to last eof... + while (!$file->eof()) { + $elements = explode(',', $file->current()); + + ## Check if this is our drop rule.. + if ( $elements[3] == $filter_id ) { + ## Process IPv4 line. + if ( $elements[8] == 4 && ( $elements[16] == 'tcp' || $elements[16] == 'udp' ) ) { + $src = $elements[18]; + $src_port = $elements[20]; + $dest = $elements[19]; + $dest_port = $elements[21]; + $prot = $elements[16]; + } + + ## Process IPv6 line. + if ( $elements[8] == 6 ) { + $src = $elements[15]; + $src_port = $elements[17]; + $dest = $elements[16]; + $dest_port = $elements[18]; + } + + if ( ! $known_ips[$src] ) { + $known_ips[$src] = array(); + } + + if ( filter_var($src, FILTER_VALIDATE_IP) ) { + array_push($known_ips[$src], time()); + } + } + $file->next(); + } + $eof = $file->key(); + $file = null; + + $compare_time = time(); + $known_ips_new = array(); + ## Expire any old timestamps + foreach ( $known_ips as $ip => $timestamps ) { + $index = 0; + foreach ( $timestamps as $timestamp ) { + if ( $timestamp + $hits_time > $compare_time ) { + if ( ! $known_ips_new[$ip] ) { + $known_ips_new[$ip] = array(); + } + array_push($known_ips_new[$ip], $timestamp); + } + } + } + $known_ips = $known_ips_new; + unset($known_ips_new); + + foreach ( $known_ips as $ip => $timestamps ) { + ## Process anything with more than $hits_num entries. + if ( count($timestamps) > $hits_num ) { + ## Add to the firewall alias. + shell_exec("pfctl -q -t abuseipdb -T add $ip"); + syslog(LOG_INFO, "Added $ip to blocklist"); + + if ( $api_key != "" ) { + $duration = $known_ips[$ip][count($known_ips[$ip]) -1] - $known_ips[$ip][0] + 1; + $data = [ + 'ip' => $ip, + 'timestamp' => date('c', $known_ips[$ip][0]), + 'categories' => "14", + 'comment' => "Honeypot hits: " . count($timestamps) . " hits in $duration second(s)" + ]; + array_push($report_queue, $data); + } + unset($known_ips[$ip]); + } + } + + ## Process any reports. + if ( $api_key != "" ) { + if ( time() > $ratelimit_expires ) { + $report_queue_new = array(); + + foreach ( $report_queue as $report ) { + if ( time() > $ratelimit_expires ) { + $headers = ["Key: $api_key", "Accept: application/json"]; + $url = "https://api.abuseipdb.com/api/v2/report"; + list($result, $ret_code) = http_req("POST", $url, $headers, $report); + if ( $ret_code == 200 ) { + syslog(LOG_INFO, "API: Reported " . $report["ip"] . " successfully"); + $ratelimit_expires = 0; + $ratelimit_delay= 5; + } else { + array_push($report_queue_new, $report); + $ratelimit_delay *= 2; + if ( $ratelimit_delay >= $ratelimit_delay_max ) { + $ratelimit_delay = $ratelimit_delay_max; + } + $ratelimit_expires = time() + $ratelimit_delay; + syslog(LOG_NOTICE, "API: Got status code: $ret_code - Waiting " . ( $ratelimit_expires - time()) . " seconds before retry..."); + } + } else { + ## We're still rate limited, just push the report back into the queue + array_push($report_queue_new, $report); + } + } + + $report_queue = $report_queue_new; + unset($report_queue_new); + } + } + + if ( time() > $log_last + $log_interval ) { + $log_last = time(); + if ( count($report_queue) != 0 ) { + syslog(LOG_NOTICE, "Tracking " . count($known_ips) . " hosts, API ratelimit active. " . count($report_queue) . " report(s) outstanding"); + } else { + syslog(LOG_INFO, "Tracking " . count($known_ips) . " hosts"); + } + } +} + +function get_blocklist($api_key, $flush_on_start) { + syslog(LOG_INFO, "Downloading initial blocklist..."); + $data = [ 'confidenceMinimum' => 100, 'limit' => 9999999 ]; + $headers = ["Key: $api_key", "Accept: application/json"]; + $url = "https://api.abuseipdb.com/api/v2/blacklist"; + list($result, $resp_code) = http_req("GET", $url, $headers, $data); + + ## Process the list if we got one back. + if ( $resp_code == 200 ) { + ## Clear the current table... + if ( $flush_on_start == 1 ) { + syslog(LOG_NOTICE, "Clearing current table for initial priming..."); + shell_exec("pfctl -t abuseipdb -T flush"); + } + $addresses = array(); + $blocklist = json_decode($result, true); + foreach ($blocklist["data"] as $entry) { + if ( $entry["ipAddress"] ) { + ## Ensure we have a valid IP and no surprises + if ( filter_var($entry["ipAddress"], FILTER_VALIDATE_IP) ) { + array_push($addresses, $entry["ipAddress"]); + } + } + + if ( count($addresses) >= 500 ) { + shell_exec("pfctl -q -t abuseipdb -T add " . implode(" ", $addresses)); + $addresses = array(); + } + } + + ## Flush any left over entries to pfctl... + if ( count($addresses) != 0 ) { + shell_exec("pfctl -q -t abuseipdb -T add " . implode(" ", $addresses)); + } + syslog(LOG_INFO, "Imported " . count($blocklist["data"]) . " entries on startup..."); + } else { + syslog(LOG_NOTICE, "API: Got reply code: $resp_code. Not importing anything..."); + } +} + +function http_req($method, $url, &$headers, &$data) { + if ( $method == "GET" ) { + $url = sprintf("%s?%s", $url, http_build_query($data)); + } + $ch = curl_init($url); + if ( $method == "POST" ) { + curl_setopt($ch,CURLOPT_POST, true); + curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($data)); + } + curl_setopt($ch,CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + + return array($result, curl_getinfo($ch, CURLINFO_HTTP_CODE)); +} + +function cleanup_on_exit() { + unlink("/var/run/abuseipdb.pid"); + closelog(); + exit; +} + +?> diff --git a/security/abuseipdb/src/opnsense/service/conf/actions.d/actions_abuseipdb.conf b/security/abuseipdb/src/opnsense/service/conf/actions.d/actions_abuseipdb.conf new file mode 100644 index 0000000000..230b66a1a4 --- /dev/null +++ b/security/abuseipdb/src/opnsense/service/conf/actions.d/actions_abuseipdb.conf @@ -0,0 +1,23 @@ +[stop] +command:/usr/local/etc/rc.d/abuseipdb stop; exit 0 +parameters: +type:script_output +message:stop abuseipdb + +[start] +command:/usr/local/etc/rc.d/abuseipdb start; exit 0 +parameters: +type:script_output +message:start abuseipdb + +[restart] +command:/usr/local/etc/rc.d/abuseipdb restart; exit 0 +parameters: +type:script_output +message:restart abuseipdb + +[status] +command:/usr/local/etc/rc.d/abuseipdb status; exit 0 +parameters: +type:script_output +message:abuseipdb status diff --git a/security/abuseipdb/src/opnsense/service/templates/OPNsense/Syslog/local/abuseipdb.conf b/security/abuseipdb/src/opnsense/service/templates/OPNsense/Syslog/local/abuseipdb.conf new file mode 100644 index 0000000000..01635f7e9f --- /dev/null +++ b/security/abuseipdb/src/opnsense/service/templates/OPNsense/Syslog/local/abuseipdb.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [abuseipdb]. +################################################################### +filter f_local_abuseipdb { + program("abuseipdb"); +};