Initial commit
This commit is contained in:
120
autoban.mjs
Normal file
120
autoban.mjs
Normal file
@@ -0,0 +1,120 @@
|
||||
import { readFileSync, realpathSync } from 'node:fs';
|
||||
import { parse_socket_state } from './parser.mjs';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
|
||||
// Configuration
|
||||
const network_interface = 'ens3';
|
||||
const update_frequency = 1; // Hz
|
||||
const seen_threshold = 5; // times
|
||||
const max_age = 20; // seconds
|
||||
const max_ban_time = 300; // seconds
|
||||
const dry_run = true;
|
||||
|
||||
// Use IGNORE_IPS in environment for comma separated list of IPs that will never be banned
|
||||
const ignore_ips = new Set(process.env.IGNORE_IPS?.split(',').map(e => e.trim()));
|
||||
|
||||
// Application
|
||||
|
||||
const peer_map = new Map();
|
||||
const ban_map = new Map();
|
||||
|
||||
const ban_script = realpathSync('./ban.sh');
|
||||
const unban_script = realpathSync('./unban.sh');
|
||||
|
||||
function sudo(argv) {
|
||||
if (dry_run) {
|
||||
console.log('sudo', ['-n', ...argv]);
|
||||
} else {
|
||||
spawn('sudo', ['-n', ...argv], { stdio: 'inherit', detached: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ban(ip) {
|
||||
const ts = performance.now();
|
||||
if (ignore_ips.has(ip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = ban_map.get(ip);
|
||||
|
||||
if (existing) {
|
||||
existing.ts = ts;
|
||||
} else {
|
||||
ban_map.set(ip, { ip, ts });
|
||||
console.log(new Date().toISOString(), '**BAN**', ip);
|
||||
sudo([ban_script, network_interface, ip]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function check_ban_list() {
|
||||
const ts = performance.now();
|
||||
for (const entry of ban_map.values()) {
|
||||
const age = (ts - entry.ts) / 1000;
|
||||
if (age > max_ban_time) {
|
||||
ban_map.delete(entry.ip);
|
||||
console.log(new Date().toISOString(), 'UNBAN', entry.ip);
|
||||
sudo([unban_script, network_interface, ip]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function run_at_rate(rate, cb) {
|
||||
let monotime = performance.now();
|
||||
|
||||
function run_once() {
|
||||
monotime += 1000 / rate;
|
||||
cb();
|
||||
const remaining = Math.max(0, monotime - performance.now());
|
||||
setTimeout(run_once, remaining);
|
||||
}
|
||||
run_once();
|
||||
}
|
||||
|
||||
|
||||
run_at_rate(update_frequency, () => {
|
||||
|
||||
// Update once
|
||||
const socket_state = readFileSync('/proc/net/tcp', 'utf-8');
|
||||
for (const entry of parse_socket_state(socket_state)) {
|
||||
if ((entry.st & 0x3) === 0x3) {
|
||||
const ts = performance.now();
|
||||
const existing = peer_map.get(entry.rem_address);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
existing.ts = ts;
|
||||
} else {
|
||||
peer_map.set(entry.rem_address, { entry, ts, count: 1 });
|
||||
console.log(new Date().toISOString(), 'ADD', entry.rem_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Purge old
|
||||
const ts = performance.now();
|
||||
for (const entry of peer_map.values()) {
|
||||
const age = (ts - entry.ts) / 1000;
|
||||
if (age > max_age) {
|
||||
peer_map.delete(entry.entry.rem_address);
|
||||
console.log(new Date().toISOString(), 'DROP', entry.entry.rem_address);
|
||||
}
|
||||
}
|
||||
|
||||
// Check limits
|
||||
for (const entry of peer_map.values()) {
|
||||
if (entry.count > seen_threshold) {
|
||||
ban(entry.entry.rem_address);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
check_ban_list();
|
||||
|
||||
|
||||
});
|
||||
1
ban.sh
Normal file
1
ban.sh
Normal file
@@ -0,0 +1 @@
|
||||
iptables-nft -C INPUT -i "$1" -s "$2" -j DROP 2>/dev/null || iptables-nft -A INPUT -i "$1" -s "$2" -j DROP
|
||||
9
notes.txt
Normal file
9
notes.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
sudo chown root:root /usr/local/bin/ban.sh /usr/local/bin/unban.sh
|
||||
sudo chmod 700 /usr/local/bin/ban.sh /usr/local/bin/unban.sh # only root can edit
|
||||
sudo chattr +i /usr/local/bin/ban.sh /usr/local/bin/unban.sh # make files immutable
|
||||
|
||||
|
||||
sudoers:
|
||||
|
||||
ubuntu ALL=(root) NOPASSWD: /home/ubuntu/Projekt/ids/ban.sh, /home/ubuntu/Projekt/ids/unban.sh
|
||||
42
parser.mjs
Normal file
42
parser.mjs
Normal file
@@ -0,0 +1,42 @@
|
||||
export function parse_ipv4(value) {
|
||||
const D = parseInt(value.slice(0, 2), 16);
|
||||
const C = parseInt(value.slice(2, 4), 16);
|
||||
const B = parseInt(value.slice(4, 6), 16);
|
||||
const A = parseInt(value.slice(6, 8), 16);
|
||||
return `${A}.${B}.${C}.${D}`;
|
||||
}
|
||||
|
||||
export function parse_socket_state(socket_state) {
|
||||
const ss_lines = socket_state.split('\n').map(line => line.trim());
|
||||
const fields = ss_lines[0].split(/\s+/);
|
||||
const result = [];
|
||||
|
||||
const field_parsers = {
|
||||
'st': (value => parseInt(value, 16)),
|
||||
'local_address': parse_ipv4,
|
||||
'rem_address': parse_ipv4,
|
||||
};
|
||||
|
||||
for (const line of ss_lines.slice(1)) {
|
||||
const values = line.split(/\s+/);
|
||||
const v_iter = values[Symbol.iterator]();
|
||||
const pending = {};
|
||||
for (const column of fields.slice(0, -1)) {
|
||||
pending[column] = v_iter.next().value;
|
||||
}
|
||||
pending[fields.at(-1)] = [...v_iter].join(' ');
|
||||
|
||||
for (const [name, parser] of Object.entries(field_parsers)) {
|
||||
const local_value = pending[name];
|
||||
if (local_value) {
|
||||
pending[name] = parser(local_value);
|
||||
}
|
||||
}
|
||||
|
||||
result.push(pending);
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
39
sample-proc-net-tcp.txt
Normal file
39
sample-proc-net-tcp.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
||||
0: 01234560:0050 01234560:0000 0A 00000000:00000000 00:00000000 00000000 0 0 42854 1 0000000000000000 100 0 0 10 0
|
||||
1: 01234560:0016 01234560:0000 0A 00000000:00000000 00:00000000 00000000 0 0 60668 1 0000000000000000 100 0 0 10 0
|
||||
2: 01234560:01BB 01234560:0000 0A 00000000:00000000 00:00000000 00000000 0 0 42856 26 0000000000000000 100 0 0 10 0
|
||||
3: 01234560:0B7A 01234560:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21963 1 0000000000000000 100 0 0 10 0
|
||||
4: 3123456F:0035 01234560:0000 0A 00000000:00000000 00:00000000 00000000 101 0 15827 1 0000000000000000 100 0 0 10 5
|
||||
5: 0123456F:0CEA 01234560:0000 0A 00000000:00000000 00:00000000 00000000 113 0 23714 1 0000000000000000 100 0 0 10 0
|
||||
6: 0123456F:0BB8 01234560:0000 0A 00000000:00000000 00:00000000 00000000 0 0 60686 1 0000000000000000 100 0 0 10 0
|
||||
7: 0123456F:18EB 01234560:0000 0A 00000000:00000000 00:00000000 00000000 114 0 22650 1 0000000000000000 100 0 0 10 0
|
||||
8: E1234565:01BB F1234561:AAA8 03 00000000:00000000 01:00000B07 00000005 0 0 0 0 0000000000000000
|
||||
9: E1234565:01BB 41234561:1A9B 03 00000000:00000000 01:0000046E 00000005 0 0 0 0 0000000000000000
|
||||
10: E1234565:01BB 51234561:FBA9 03 00000000:00000000 01:000003AE 00000004 0 0 0 0 0000000000000000
|
||||
11: E1234565:01BB 41234561:B82C 03 00000000:00000000 01:00000174 00000003 0 0 0 0 0000000000000000
|
||||
12: E1234565:01BB 91234561:4541 03 00000000:00000000 01:00000074 00000003 0 0 0 0 0000000000000000
|
||||
13: E1234565:01BB 61234561:D859 03 00000000:00000000 01:00000347 00000004 0 0 0 0 0000000000000000
|
||||
14: E1234565:01BB 21234561:1C96 03 00000000:00000000 01:00000274 00000003 0 0 0 0 0000000000000000
|
||||
15: 0123456F:CB3A 0123456F:0BB8 01 00000000:00000000 00:00000000 00000000 33 0 341759 1 0000000000000000 20 4 30 10 -1
|
||||
16: E1234565:01BB 81234561:3A2E 03 00000000:00000000 01:00000B6E 00000005 0 0 0 0 0000000000000000
|
||||
17: E1234565:01BB 21234561:9277 03 00000000:00000000 01:000001D4 00000005 0 0 0 0 0000000000000000
|
||||
18: 0123456F:0BB8 0123456F:CB3A 01 00000000:00000000 02:0000054D 00000000 0 0 340925 2 0000000000000000 20 4 31 10 -1
|
||||
19: E1234565:01BB 11234561:77CF 03 00000000:00000000 01:00000B21 00000005 0 0 0 0 0000000000000000
|
||||
20: E1234565:01BB E1234561:F3AA 03 00000000:00000000 01:000003BA 00000005 0 0 0 0 0000000000000000
|
||||
21: E1234565:01BB 71234561:4165 03 00000000:00000000 01:00000721 00000005 0 0 0 0 0000000000000000
|
||||
22: E1234565:01BB 91234561:5A44 03 00000000:00000000 01:00000114 00000004 0 0 0 0 0000000000000000
|
||||
23: E1234565:01BB D1234561:E389 03 00000000:00000000 01:000003EE 00000005 0 0 0 0 0000000000000000
|
||||
24: E1234565:01BB 91234561:CF3B 03 00000000:00000000 01:00000000 00000005 0 0 0 0 0000000000000000
|
||||
25: E1234565:01BB F1234561:B713 03 00000000:00000000 01:00000241 00000003 0 0 0 0 0000000000000000
|
||||
26: 0123456C:8522 0123456C:0BB8 01 00000000:00000000 02:0000054A 00000000 0 0 340927 2 0000000000000000 20 4 30 10 -1
|
||||
27: E1234565:01BB 31234561:399F 03 00000000:00000000 01:00000000 00000005 0 0 0 0 0000000000000000
|
||||
28: E1234565:01BB B1234561:E266 03 00000000:00000000 01:0000055E 00000004 0 0 0 0 0000000000000000
|
||||
29: E1234565:01BB 61234563:512B 01 00000000:00000000 00:00000000 00000000 33 0 339859 1 0000000000000000 24 4 29 8 7
|
||||
30: E1234565:01BB D1234561:930C 03 00000000:00000000 01:00000024 00000003 0 0 0 0 0000000000000000
|
||||
31: E1234565:01BB B1234561:21A3 03 00000000:00000000 01:0000008A 00000003 0 0 0 0 0000000000000000
|
||||
32: E1234565:0B7A F123456E:759B 01 00000000:00000000 02:000AFBFD 00000000 0 0 368411 4 0000000000000000 23 6 19 10 -1
|
||||
33: E1234565:01BB 91234561:A9FA 03 00000000:00000000 01:00000650 00000005 0 0 0 0 0000000000000000
|
||||
34: E1234565:01BB D1234561:92DD 03 00000000:00000000 01:00000637 00000005 0 0 0 0 0000000000000000
|
||||
35: E1234565:01BB 51234561:D9C3 03 00000000:00000000 01:00000005 00000001 0 0 0 0 0000000000000000
|
||||
36: E1234565:01BB 91234561:6A2F 03 00000000:00000000 01:00000BEA 00000005 0 0 0 0 0000000000000000
|
||||
37: E1234565:01BB F1234561:1544 03 00000000:00000000 01:000004D0 00000005 0 0 0 0 0000000000000000
|
||||
3
setup.sh
Normal file
3
setup.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
chmod 700 ban.sh unban.sh
|
||||
chown root:root ban.sh unban.sh
|
||||
#chattr +i ban.sh unban.sh # make files immutable
|
||||
Reference in New Issue
Block a user