Last active
July 23, 2021 08:26
-
-
Save ChenYingChou/0e0605bd10e7144c4c913affd05cccc6 to your computer and use it in GitHub Desktop.
Check system logs and add DNS and SSH attackers to the firewall blacklist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
//@ts-check | |
const fs = require('fs') | |
const path = require('path') | |
const dns = require('dns') | |
const util = require('util') | |
const dateformat = require('dateformat') | |
const Tail = require('tail').Tail | |
const resolve4 = util.promisify(dns.resolve4) | |
const writeFile = util.promisify(fs.writeFile) | |
function log_msg() { | |
const stime = dateformat(new Date(), 'mm-dd HH:MM:ss.l') | |
console.log(util.format(stime, ...arguments)) | |
} | |
// https://nodejs.org/api/util.html#util_util_inspect_object_options | |
Object.assign(util.inspect.defaultOptions, { | |
breakLength: Infinity, | |
maxArrayLength: 50, | |
depth: 10, | |
}) | |
// change to directory of program | |
process.chdir(path.dirname(process.argv[1])) | |
process.title = 'watch-log' | |
/** | |
* @typedef {Object} Config | |
* @property {string} watchList - Add the blocked IP to the file, default `/root/watch-blacklist.txt` | |
* @property {string} blacklist - Blacklist of iptables recent module, default `/proc/net/xt_recent/blacklist` | |
* @property {number} minIPSize - Minimum size of `ipSet`, default `100` | |
* @property {number} maxIPSize - maximum size of `ipSet`, default `1000` | |
* @property {number} lastBucketSize - Read `ipSet` from last bucket size of `watchList`, default `2000` bytes | |
* @property {Object<string, Object<string, string>>} targets | |
*/ | |
/** @type {Config} */ | |
const cfg = Object.assign({ | |
watchList: '/root/watch-blacklist.txt', | |
blacklist: '/proc/net/xt_recent/blacklist', | |
minIPSize: 100, | |
maxIPSize: 1000, | |
lastBucketSize: 2000, | |
}, require('./watch-log.json')) | |
/** @type {Set<string>} */ | |
let ipSet = (filename => { | |
let fd | |
let lines | |
try { | |
fd = fs.openSync(filename, 'r') | |
const stat = fs.fstatSync(fd) | |
const position = Math.max(stat.size - cfg.lastBucketSize, 0) | |
const size = stat.size - position | |
const buffer = Buffer.alloc(size) | |
const bytes = fs.readSync(fd, buffer, 0, size, position) | |
lines = Buffer.from(buffer, 0, bytes).toString().trim().split(/\s*\n\s*/) | |
lines = lines.length > cfg.minIPSize ? lines.slice(-cfg.minIPSize) : lines.slice(1) | |
} catch (error) { | |
if (fd) log_msg(`!! ${error}`) | |
} | |
if (fd) fs.closeSync(fd) | |
return new Set(lines) | |
})(cfg.watchList) | |
/** | |
* @param {string} ip | |
*/ | |
function add_ip(ip) { | |
if (!ip || ipSet.has(ip)) return false | |
ipSet.add(ip) | |
if (ipSet.size > cfg.maxIPSize) { | |
const list = Array.from(ipSet) | |
ipSet = new Set(list.slice(-cfg.minIPSize)) | |
} | |
return true | |
} | |
/** | |
* @param {string} hostname - IP4 or hostname | |
* @returns {Promise<string>|string} IP4 address or `null` | |
*/ | |
function query_ip(hostname) { | |
if (/^\d.*\d$/.test(hostname)) return hostname // IP4 address | |
if (add_ip(hostname) === false) return null // `hostname` already resolved | |
return resolve4(hostname).then(addressList => addressList[0]).catch(err => { | |
log_msg(`!! query_ip> ${err}`) | |
return '' | |
}) | |
} | |
let watchStream = fs.createWriteStream(cfg.watchList, { 'flags': 'a' }) | |
const tailList = ((targets) => { | |
/** @type {Tail[]} */ | |
let result = [] | |
Object.entries(targets).forEach(([filename, obj]) => { | |
const keys = Object.keys(obj) | |
if (keys.length === 0) return | |
const rxList = Object.entries(obj).map(([key, value]) => { | |
return new RegExp(`${key}\\[\\d+\\]: ${value}`) | |
}) | |
const tail = new Tail(filename) | |
tail.on('line', /** @param {string} data */(data) => { | |
rxList.forEach(async (rx, index) => { | |
const mx = data.match(rx) | |
if (mx) { | |
const ip = await query_ip(mx[1]) | |
if (add_ip(ip) === false) return | |
const key = keys[index] | |
log_msg(`${key}: ${ip}`) | |
watchStream.write(`${ip}\n`) | |
writeFile(cfg.blacklist, `+${ip}`, {encoding:'ascii', flag:'a'}).catch(err => { | |
log_msg(`!! ${cfg.blacklist}> ${err}`) | |
}) | |
} | |
}) | |
}) | |
tail.on('error', (error) => { | |
log_msg(`!! ${filename}> ${error}`) | |
}) | |
result.push(tail) | |
}) | |
return result | |
})(cfg.targets) | |
function finish() { | |
watchStream.end() | |
for (const tail of tailList) { | |
tail.unwatch() | |
} | |
} | |
if (tailList.length === 0) { | |
log_msg('!! No watched targets') | |
finish() | |
} | |
process.on('SIGINT', finish) | |
process.on('SIGQUIT', finish) | |
process.on('SIGTERM', finish) | |
process.on('SIGHUP', () => { | |
if (watchStream) { | |
watchStream.end() | |
watchStream = fs.createWriteStream(cfg.watchList, {flags: 'a'}) | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"watchList": "/root/ip-blacklist.txt", | |
"blacklist": "/proc/net/xt_recent/blacklist", | |
"minIPSize": 100, | |
"maxIPSize": 1000, | |
"lastBucketSize": 2000, | |
"targets": { | |
"/var/log/messages": { | |
"named": "client(?: @0x[0-9a-f]+)? (\\d+\\.\\d+\\.\\d+\\.\\d+)#\\d+.* denied" | |
}, | |
"/var/log/secure": { | |
"sshd": ".* [Ii]nvalid user .*? from (\\d+\\.\\d+\\.\\d+\\.\\d+)", | |
"vsftpd": ".* authentication failure .* rhost=([\\w\\.-]+)" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment