This commit is contained in:
Sefinek committed 2025-02-01 20:43:06 +01:00
commit e9381204c6
20 files changed
+1056 -559

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<custom-configuration-file used="true" path="$PROJECT_DIR$/eslint.config.mjs" />
<option name="fix-on-save" value="true" />
</component>
</project>
+86 -44
View File
@@ -1,47 +1,88 @@
<div align="center">
[<a href="README.md">English</a>]
[<a href="README_PL.md">Polski</a>]
</div>
# 🛡️ UFW AbuseIPDB Reporter # 🛡️ UFW AbuseIPDB Reporter
A utility designed to analyze UFW firewall logs and report malicious IP addresses to the [AbuseIPDB](https://www.abuseipdb.com) database. A utility designed to analyze UFW logs and report IP addresses blocked by the firewall to the [AbuseIPDB](https://www.abuseipdb.com) database.
To prevent redundant reporting of the same IP address within a short period, the tool uses a temporary cache file to track previously reported IPs. To prevent excessive reporting of the same IP address within a short period, the tool uses a temporary cache file to track previously reported IP addresses.
If you like this repository or find it useful, I would greatly appreciate it if you could give it a star ⭐. Thanks a lot! This project was originally written in Bash but has been rewritten in [Node.js](https://nodejs.org). All my integration tools are now written in Node, hence this change.
See also this: [sefinek/Cloudflare-WAF-To-AbuseIPDB](https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB) If you were using the old version, please [uninstall it](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/tree/node.js?tab=readme-ov-file#%EF%B8%8F-remove-the-old-version) as it will no longer be supported.
If you like this repository or find it useful, I’d greatly appreciate it if you could give it a star ⭐. Many thanks!
Also, check this out: [sefinek/Cloudflare-WAF-To-AbuseIPDB](https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB)
> [!IMPORTANT] > [!IMPORTANT]
> If you'd like to make changes to any files in this repository, please start by creating a [public fork](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/fork). > - If you'd like to make changes to any files in this repository, please start by creating a [public fork](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/fork).
> - According to AbuseIPDB's policy, UDP traffic should not be reported!
## 📋 Requirements ## 📋 Requirements
- **Operating System:** Linux with UFW firewall installed and configured. 1. [Node.js + npm](https://nodejs.org)
- **AbuseIPDB Account:** An account on the AbuseIPDB service [with a valid API token](https://www.abuseipdb.com/account/api). The API token is required. 2. [PM2](https://www.npmjs.com/package/pm2)
- **Installed packages:** 3. [Git](https://git-scm.com)
- `wget` or `curl`: One of these tools is required to download the [installation script](install.sh) from the GitHub repository and to send requests to the AbuseIPDB API.
- `jq`: A tool for processing and parsing JSON data returned by the AbuseIPDB API.
- `openssl`: Used to encode and decode the API token to secure authentication data.
- `tail`, `awk`, `grep`, `sed`: Standard Unix tools used for text processing and log analysis.
## 🧪 Tested operating systems ## ✅ Features
- **Ubuntu Server:** 20.04 & 22.04 1. A [`config.js`](default.config.js) file enabling easy configuration.
2. A simple installer allowing quick integration deployment.
*If the distribution you're using to run this tool isn't listed here but works correctly, please create a new [Issue](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues) or submit a [Pull request](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/pulls).* 3. Integration with Discord Webhooks (coming soon):
- Alerts in case of script errors
- Daily summaries of reported IP addresses
4. Automatic updates.
## 📥 Installation ## 📥 Installation
### curl
### Automatic (easy & recommenced)
#### Via curl
```bash ```bash
bash <(curl -s https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh) bash <(curl -fsS https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/node.js/install.sh)
``` ```
### wget #### Via wget
```bash ```bash
bash <(wget -qO- https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh) bash <(wget -qO- https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/node.js/install.sh)
``` ```
The installation script will automatically download and configure the tool on your machine. During the installation process, you will be prompted to provide an [AbuseIPDB API token](https://www.abuseipdb.com/account/api). ### Manually
#### Node.js installation (Ubuntu & Debian)
```bash
sudo apt-get install -y curl
curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh && sudo apt-get install -y nodejs
```
#### Git installation
```bash
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update && sudo apt-get -y install git
```
#### Script
```bash
sudo apt-get update && sudo apt-get upgrade
cd ~
git clone https://github.com/sefinek/UFW-AbuseIPDB-Reporter.git
cd UFW-AbuseIPDB-Reporter
npm install
cp default.config.js config.js
sudo chmod 644 /var/log/ufw.log
node .
^C
npm uninstall corepack -g
npm install pm2 -g
sudo mkdir /var/log/ufw-abuseipdb
sudo chown $USER:$USER /var/log/ufw-abuseipdb -R
pm2 start
pm2 startup
[Paste the command generated by pm2 startup]
pm2 save
```
## 🗑️ Uninstall the deprecated version if you have it
```bash
sudo systemctl stop abuseipdb-ufw.service && sudo systemctl disable abuseipdb-ufw.service
sudo rm /etc/systemd/system/abuseipdb-ufw.service
sudo systemctl daemon-reload
sudo rm -r /usr/local/bin/UFW-AbuseIPDB-Reporter
```
## 🖥️ Usage ## 🖥️ Usage
@@ -51,32 +92,33 @@ The tool requires no additional user action after installation. However, it's wo
Servers open to the world are constantly scanned by bots, usually looking for vulnerabilities or other security gaps. Servers open to the world are constantly scanned by bots, usually looking for vulnerabilities or other security gaps.
So don't be surprised if the next day, the number of reports to AbuseIPDB exceeds a thousand. So don't be surprised if the next day, the number of reports to AbuseIPDB exceeds a thousand.
### 🔍 Checking service status ### 🔍 Checking logs
```bash ```bash
sudo systemctl status abuseipdb-ufw.service pm2 logs ufw-abuseipdb
``` ```
To see the current logs generated by the process, use the command: ### 📄 Example reports
```bash #### 1️⃣
journalctl -u abuseipdb-ufw.service -f ```text
``` Blocked by UFW on homeserver01 [80/tcp]
Source port: 23639
TTL: 247
Packet length: 40
TOS: 0x00
### 📄 Example report This report was generated by:
```
Blocked by UFW (TCP on 80)
Source port: 28586
TTL: 116
Packet length: 48
TOS: 0x08
This report (for 46.174.191.31) was generated by:
https://github.com/sefinek/UFW-AbuseIPDB-Reporter https://github.com/sefinek/UFW-AbuseIPDB-Reporter
``` ```
#### 2️⃣
```text
Blocked by UFW on homeserver01 [30049/tcp]. Generated by: https://github.com/sefinek/UFW-AbuseIPDB-Reporter
```
## 🤝 Development ## 🤝 Development
If you want to contribute to the development of this project, feel free to create a new [Pull request](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/pulls). I will definitely appreciate it! If you want to contribute to the development of this project, feel free to create a new [Pull request](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/pulls). I will definitely appreciate it!
## 🔑 GPL-3.0 License ## 🔑 [GPL-3.0 License](LICENSE)
Copyright 2024 © by [Sefinek](https://sefinek.net). All rights reserved. See the [LICENSE](LICENSE) file for more information. Copyright 2024-2025 © by [Sefinek](https://sefinek.net). All rights reserved.
-82
View File
@@ -1,82 +0,0 @@
<div align="center">
[<a href="README.md">English</a>]
[<a href="README_PL.md">Polski</a>]
</div>
# 🛡️ UFW AbuseIPDB Reporter
Narzędzie zaprojektowane do analizowania dzienników zapory sieciowej UFW i zgłaszania złośliwych adresów IP do bazy danych [AbuseIPDB](https://www.abuseipdb.com).
Aby zapobiec nadmiarowemu zgłaszaniu tego samego adresu IP w krótkim okresie, narzędzie wykorzystuje tymczasowy plik pamięci podręcznej do śledzenia wcześniej zgłoszonych adresów IP.
Jeśli podoba Ci się to repozytorium lub uważasz je za przydatne, byłbym bardzo wdzięczny, za przyznanie mu gwiazdki ⭐. Wielkie dzięki!
Zobacz również to: [sefinek/Cloudflare-WAF-To-AbuseIPDB](https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB)
> [!IMPORTANT]
> Jeśli chcesz wprowadzić zmiany do jakichkolwiek pliku z tego repozytorium, zacznij od utworzenia [publicznego forka](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/fork).
## 📋 Wymagania
- **System operacyjny:** Linux z zainstalowanym i skonfigurowanym firewallem UFW.
- **Konto AbuseIPDB:** Wymagane jest konto w serwisie AbuseIPDB [z ważnym tokenem API](https://www.abuseipdb.com/account/api). Token API jest niezbędny.
- **Zainstalowane pakiety:**
- `wget` lub `curl`: Jedno z tych narzędzi jest wymagane do pobrania [skryptu instalacyjnego](install.sh) z repozytorium GitHub oraz do wysyłania zapytań do API AbuseIPDB.
- `jq`: Narzędzie do przetwarzania i parsowania danych w formacie JSON, zwracanych przez API AbuseIPDB.
- `openssl`: Używane do kodowania i dekodowania tokena API, aby zabezpieczyć dane uwierzytelniające.
- `tail`, `awk`, `grep`, `sed`: Standardowe narzędzia Unixowe wykorzystywane do przetwarzania tekstu i analizy logów.
## 🧪 Testowane systemy operacyjne
- **Ubuntu Server:** 20.04 & 22.04
*Jeśli dystrybucja, której używasz do uruchomienia tego narzędzia, nie jest wymieniona tutaj, ale działa poprawnie, utwórz nowy [Issue](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues) lub prześlij [Pull request](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/pulls).*
## 📥 Instalacja
### curl
```bash
bash <(curl -s https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh)
```
### wget
```bash
bash <(wget -qO- https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh)
```
Skrypt instalacyjny automatycznie pobierze i skonfiguruje narzędzie na komputerze użytkownika. Podczas procesu instalacji zostaniesz poproszony o podanie [tokenu API AbuseIPDB](https://www.abuseipdb.com/account/api).
## 🖥️ Użycie
Po pomyślnej instalacji skrypt będzie działać cały czas w tle, monitorując logi UFW i automatycznie zgłaszając złośliwe adresy IP.
Narzędzie nie wymaga dodatkowych działań użytkownika po instalacji. Warto jednak od czasu do czasu sprawdzić jego działanie oraz aktualizować skrypt na bieżąco (wywołując polecenie instalacyjne).
Serwery otwarte na świat są nieustannie skanowane przez boty, które zazwyczaj szukają podatności lub jakichkolwiek innych luk w zabezpieczeniach.
Więc nie zdziw się, jeśli następnego dnia liczba zgłoszeń na AbuseIPDB przekroczy tysiąc.
### 🔍 Sprawdzenie statusu usługi
```bash
sudo systemctl status abuseipdb-ufw.service
```
Aby zobaczyć bieżące logi generowane przez proces, użyj polecenia:
```bash
journalctl -u abuseipdb-ufw.service -f
```
### 📄 Przykładowe zgłoszenie
```
Blocked by UFW (TCP on 80)
Source port: 28586
TTL: 116
Packet length: 48
TOS: 0x08
This report (for 46.174.191.31) was generated by:
https://github.com/sefinek/UFW-AbuseIPDB-Reporter
```
## 🤝 Rozwój
Jeśli chcesz przyczynić się do rozwoju tego projektu, śmiało stwórz nowy [Pull request](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/pulls). Z pewnością to docenię!
## 🔑 Licencja GPL-3.0
Copyright 2024 © by [Sefinek](https://sefinek.net). Wszelkie prawa zastrzeżone. Zobacz plik [LICENSE](LICENSE), aby dowiedzieć się więcej.
+83
View File
@@ -0,0 +1,83 @@
exports.MAIN = {
// This server
UFW_LOG_FILE: '/var/log/ufw.log',
CACHE_FILE: '/tmp/ufw-abuseipdb-reporter.cache',
SERVER_ID: null, // The server name that will be visible in the reports (e.g., 'homeserver1'). If you don't want to define it, leave the value as null.
REFRESHING_IP_ADDRESS: 5 * 60 * 1000, // How often should (every 5 minutes) the script check the server's IP address to avoid accidental self-reports?
// Reporting
ABUSEIPDB_API_KEY: '', // Secret API key for AbuseIPDB.
IP_REPORT_COOLDOWN: 12 * 60 * 60 * 1000, // The minimum time (12 hours) that must pass before reporting the same IP address again.
// Automatic updates
AUTO_UPDATE_SCHEDULE: '0 18 * * *', // Schedule for automatic script updates (CRON format). Default: every day at 18:00
AUTO_UPDATE_ENABLED: true, // Do you want the script to automatically update to the latest version using 'git pull'? (true = enabled, false = disabled)
};
/**
* Generates a report submission to AbuseIPDB.
* @param {Object} logData
* @param {string|null} logData.timestamp
* @param {string|null} logData.In
* @param {string|null} logData.Out
* @param {string|null} logData.srcIp
* @param {string|null} logData.dstIp
* @param {string|null} logData.res
* @param {string|null} logData.tos
* @param {string|null} logData.prec
* @param {string|null} logData.ttl
* @param {string|null} logData.id
* @param {string|null} logData.proto
* @param {string|null} logData.spt
* @param {string|null} logData.dpt
* @param {string|null} logData.len
* @param {string|null} logData.urgp
* @param {string|null} logData.mac
* @param {string|null} logData.window
* @param {boolean} logData.syn
* @param {string|null} fullLog
* @param {string|null} serverName
* @returns {string} A formatted string report.
*/
exports.REPORT_COMMENT = ({ timestamp, In, Out, srcIp, dstIp, res, tos, prec, ttl, id, proto, spt, dpt, len, urgp, mac, window, syn }, fullLog, serverName) =>
`Blocked by UFW ${serverName ? `on ${serverName} ` : ''}[${dpt}/${proto?.toLowerCase()}]
Source port: ${spt || 'N/A'}
TTL: ${ttl || 'N/A'}
Packet length: ${len || 'N/A'}
TOS: ${tos || 'N/A'}
This report was generated by:
https://github.com/sefinek/UFW-AbuseIPDB-Reporter`; // Please do not remove this URL; I would be very grateful! Thank you. 💙
// Alternative version:
// exports.REPORT_COMMENT = ({ timestamp, In, Out, srcIp, dstIp, res, tos, prec, ttl, id, proto, spt, dpt, len, urgp, mac, window, syn }, fullLog, serverName) =>
// `Blocked by UFW ${serverName ? `on ${serverName} ` : ''}[${dpt}/${proto?.toLowerCase()}]. Generated by: https://github.com/sefinek/UFW-AbuseIPDB-Reporter`;
// See: https://www.abuseipdb.com/categories
const categories = {
TCP: {
22: '14,22,18', // Port Scan | SSH | Brute-Force
80: '14,21', // Port Scan | Web App Attack
443: '14,21', // Port Scan | Web App Attack
8080: '14,21', // Port Scan | Web App Attack
25: '14,11', // Port Scan | Email Spam
21: '14,5,18', // Port Scan | FTP Brute-Force | Brute-Force
53: '14,1,2', // Port Scan | DNS Compromise | DNS Poisoning
23: '14,15,18', // Port Scan | Hacking | Brute-Force
3389: '14,15,18', // Port Scan | Hacking | Brute-Force
3306: '14,16', // Port Scan | SQL Injection
6666: '14,8', // Port Scan | Fraud VoIP
6667: '14,8', // Port Scan | Fraud VoIP
6668: '14,8', // Port Scan | Fraud VoIP
6669: '14,8', // Port Scan | Fraud VoIP
9999: '14,6', // Port Scan | Ping of Death
},
UDP: {
53: '14,1,2', // Port Scan | DNS Compromise | DNS Poisoning
123: '14,17', // Port Scan | Spoofing
},
};
exports.DETERMINE_CATEGORIES = ({ proto, dpt }) => categories[proto]?.[dpt] || '14'; // Default: Port Scan
+26
View File
@@ -0,0 +1,26 @@
module.exports = {
apps: [{
name: 'ufw-abuseipdb',
script: './index.js',
// Logging configuration
log_date_format: 'HH:mm:ss.SSS DD.MM.YYYY',
merge_logs: true,
log_file: '/var/log/ufw-abuseipdb/combined.log',
out_file: '/var/log/ufw-abuseipdb/out.log',
error_file: '/var/log/ufw-abuseipdb/error.log',
// Application restart policy settings
wait_ready: true,
autorestart: true,
max_restarts: 10,
min_uptime: 20000,
restart_delay: 5000,
exp_backoff_restart_delay: 3000,
// Environment variables configuration
env: {
NODE_ENV: 'production',
},
}],
};
+55
View File
@@ -0,0 +1,55 @@
import js from '@eslint/js';
import globals from 'globals';
export default [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2024,
globals: {
...globals.node,
...globals.es2024,
},
},
rules: {
'arrow-spacing': ['warn', { before: true, after: true }],
'comma-dangle': ['warn', { arrays: 'always-multiline', objects: 'always-multiline' }],
'comma-spacing': 'warn',
'comma-style': 'error',
'curly': ['error', 'multi-line', 'consistent'],
'dot-location': ['error', 'property'],
'handle-callback-err': 'off',
'indent': ['warn', 'tab'],
'keyword-spacing': 'warn',
'max-nested-callbacks': ['error', { max: 4 }],
'max-statements-per-line': ['error', { max: 2 }],
'no-console': 'off',
'no-empty': 'warn',
'no-empty-function': 'warn',
'no-floating-decimal': 'error',
'no-lonely-if': 'warn',
'no-multi-spaces': 'warn',
'no-multiple-empty-lines': ['warn', { max: 3, maxEOF: 1, maxBOF: 0 }],
'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }],
'no-trailing-spaces': 'warn',
'no-unreachable': 'warn',
'no-unused-vars': 'warn',
'no-use-before-define': ['error', { functions: false, classes: true }],
'no-var': 'error',
'object-curly-spacing': ['warn', 'always'],
'prefer-const': 'error',
'quotes': ['warn', 'single'],
'semi': ['warn', 'always'],
'sort-vars': 'warn',
'space-before-blocks': 'warn',
'space-before-function-paren': ['warn', { anonymous: 'never', named: 'never', asyncArrow: 'always' }],
'space-in-parens': 'warn',
'space-infix-ops': 'warn',
'space-unary-ops': 'warn',
'spaced-comment': 'warn',
'wrap-regex': 'warn',
'yoda': 'error',
},
ignores: ['node_modules', '*min.js', '*bundle*', 'build/*', 'dist/*'],
},
];
+151
View File
@@ -0,0 +1,151 @@
//
// Copyright 2024-2025 (c) by Sefinek All rights reserved.
// https://sefinek.net
//
const fs = require('node:fs');
const chokidar = require('chokidar');
const isLocalIP = require('./utils/isLocalIP.js');
const { reportedIPs, loadReportedIPs, saveReportedIPs, isIPReportedRecently, markIPAsReported } = require('./services/cache.js');
const log = require('./utils/log.js');
const axios = require('./services/axios.js');
const serverAddress = require('./services/fetchServerIP.js');
const config = require('./config.js');
const { version } = require('./package.json');
const { UFW_LOG_FILE, ABUSEIPDB_API_KEY, SERVER_ID, AUTO_UPDATE_ENABLED } = config.MAIN;
let fileOffset = 0;
const reportToAbuseIPDb = async (logData, categories, comment) => {
try {
const { data: res } = await axios.post('https://api.abuseipdb.com/api/v2/report', new URLSearchParams({
ip: logData.srcIp,
categories,
comment,
}), { headers: { 'Key': ABUSEIPDB_API_KEY } });
log(0, `Reported ${logData.srcIp} [${logData.dpt}/${logData.proto}]; ID: ${logData.id}; Categories ${categories}; Abuse: ${res.data.abuseConfidenceScore}%`);
return true;
} catch (err) {
log(2, `Failed to report ${logData.srcIp} [${logData.dpt}/${logData.proto}]; ID: ${logData.id}; ${err.message}\n${JSON.stringify(err.response.data?.errors || err.response.data)}`);
return false;
}
};
const processLogLine = async line => {
if (!line.includes('[UFW BLOCK]')) return log(1, `Ignoring line: ${line}`);
const timestampMatch = line.match(/\[(\d+\.\d+)\]/);
const logData = {
timestampOld: line.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2})?/)?.[0] || null,
timestampNew: (timestampMatch ? parseFloat(timestampMatch[1]) : null) || null,
In: line.match(/IN=([\d.]+)/)?.[1] || null,
Out: line.match(/OUT=([\d.]+)/)?.[1] || null,
srcIp: line.match(/SRC=([\d.]+)/)?.[1] || null,
dstIp: line.match(/DST=([\d.]+)/)?.[1] || null,
res: line.match(/RES=(\S+)/)?.[1] || null,
tos: line.match(/TOS=(\S+)/)?.[1] || null,
prec: line.match(/PREC=(\S+)/)?.[1] || null,
ttl: line.match(/TTL=(\d+)/)?.[1] || null,
id: line.match(/ID=(\d+)/)?.[1] || null,
proto: line.match(/PROTO=(\S+)/)?.[1] || null,
spt: line.match(/SPT=(\d+)/)?.[1] || null,
dpt: line.match(/DPT=(\d+)/)?.[1] || null,
len: line.match(/LEN=(\d+)/)?.[1] || null,
urgp: line.match(/URGP=(\d+)/)?.[1] || null,
mac: line.match(/MAC=([\w:]+)/)?.[1] || null,
window: line.match(/WINDOW=(\d+)/)?.[1] || null,
syn: !!line.includes('SYN'),
};
const { srcIp, proto, dpt } = logData;
if (!srcIp) {
log(1, `Missing SRC in log line: ${line}`);
return;
}
if (serverAddress().includes(srcIp)) {
log(0, `Ignoring own IP address: ${srcIp}`);
return;
}
if (isLocalIP(srcIp)) {
log(0, `Ignoring local/private IP: ${srcIp}`);
return;
}
// Report MUST NOT be of an attack where the source address is likely spoofed i.e. SYN floods and UDP floods.
// TCP connections can only be reported if they complete the three-way handshake. UDP connections cannot be reported.
// More: https://www.abuseipdb.com/reporting-policy
if (proto === 'UDP') {
log(0, `Skipping UDP traffic: SRC=${srcIp} DPT=${dpt}`);
return;
}
if (isIPReportedRecently(srcIp)) {
const lastReportedTime = reportedIPs.get(srcIp);
const elapsedTime = Math.floor(Date.now() / 1000 - lastReportedTime);
const days = Math.floor(elapsedTime / 86400);
const hours = Math.floor((elapsedTime % 86400) / 3600);
const minutes = Math.floor((elapsedTime % 3600) / 60);
const seconds = elapsedTime % 60;
const timeAgo = [
days && `${days}d`,
hours && `${hours}h`,
minutes && `${minutes}m`,
(seconds || !days && !hours && !minutes) && `${seconds}s`,
].filter(Boolean).join(' ');
log(0, `${srcIp} was last reported on ${new Date(lastReportedTime * 1000).toLocaleString()} (${timeAgo} ago)`);
return;
}
const categories = config.DETERMINE_CATEGORIES(logData);
const comment = config.REPORT_COMMENT(logData, line, SERVER_ID);
if (await reportToAbuseIPDb(logData, categories, comment)) {
markIPAsReported(srcIp);
saveReportedIPs();
}
};
(async () => {
log(0, `v${version} (https://github.com/sefinek/UFW-AbuseIPDB-Reporter)`);
loadReportedIPs();
if (!fs.existsSync(UFW_LOG_FILE)) {
log(2, `Log file ${UFW_LOG_FILE} does not exist.`);
return;
}
fileOffset = fs.statSync(UFW_LOG_FILE).size;
chokidar.watch(UFW_LOG_FILE, { persistent: true, ignoreInitial: true })
.on('change', path => {
const stats = fs.statSync(path);
if (stats.size < fileOffset) {
fileOffset = 0;
log(1, 'The file has been truncated, and the offset has been reset.');
}
fs.createReadStream(path, { start: fileOffset, encoding: 'utf8' }).on('data', chunk => {
chunk.split('\n').filter(line => line.trim()).forEach(processLogLine);
}).on('end', () => {
fileOffset = stats.size;
});
});
if (process?.send) process.send('ready');
log(0, `Ready! Now monitoring: ${UFW_LOG_FILE}`);
log(0, '=====================================================================');
// Auto updates
if (AUTO_UPDATE_ENABLED) {
const autoUpdates = require('./services/updates.js');
await autoUpdates.pull();
}
})();
+193 -211
View File
@@ -1,12 +1,9 @@
#!/bin/bash #!/usr/bin/env bash
### ##################################################################
# https://github.com/sefinek/UFW-AbuseIPDB-Reporter # Copyright 2024-2025 (c) by Sefinek All rights reserved. #
## # https://sefinek.net #
##################################################################
VERSION="1.1.2"
DATE="20.12.2024"
REPO="https://github.com/sefinek/UFW-AbuseIPDB-Reporter"
cat << "EOF" cat << "EOF"
_ _ ___ ____ ____ ____ _ _ ___ ____ ____ ____
@@ -21,241 +18,226 @@ cat << "EOF"
|_|_| |_|\__\___|\__, |_| \__,_|\__|_|\___/|_| |_| |_|_| |_|\__\___|\__, |_| \__,_|\__|_|\___/|_| |_|
|___/ |___/
EOF >> Made by sefinek.net || Last update: 18.01.2025 <<
cat <<EOF This installer will configure UFW-AbuseIPDB-Reporter, a tool that analyzes UFW logs and
>> Made by sefinek.net || Version: $VERSION [$DATE] << reports to AbuseIPDB the IP addresses that have violated firewall rules. Join my Discord
server to stay updated on the latest changes and more: https://discord.gg/53DBjTuzgZ
This installer will configure UFW-AbuseIPDB-Reporter, a tool that analyzes ============================================================================================
UFW firewall logs and reports IP addresses to AbuseIPDB. Remember to perform
updates periodically. You can join my Discord server to receive notifications
about the latest changes and more: https://discord.gg/53DBjTuzgZ
================================================================================
EOF EOF
# Function to download a file using either curl or wget # Function to prompt for a Yes/no answer
download_file() { yes_no_prompt() {
local url="$1" local prompt="$1"
local output="$2"
local user_agent="UFW-AbuseIPDB-Reporter/$VERSION (+$REPO)"
if command -v curl >/dev/null 2>&1; then
echo "INFO: Using 'curl' to download the file..."
sudo curl -A "$user_agent" -o "$output" "$url"
elif command -v wget >/dev/null 2>&1; then
echo "INFO: 'curl' is not installed. Using 'wget' to download the file..."
sudo wget --header="User-Agent: $user_agent" -O "$output" "$url"
else
echo "FAIL: Neither 'curl' nor 'wget' is installed! Please install one of these packages and try running the script again."
exit 1
fi
}
# Ask
ask_user() {
local question="$1"
local response
while true; do while true; do
read -rp "$ $question [Yes/no]: " response read -r -p "$prompt [Yes/no]: " answer
case "${response,,}" in case $answer in
yes|y) return 0;; [Yy]*|[Yy]es ) return 0 ;; # Return 0 for Yes
no|n) return 1;; [Nn]*|[Nn]o ) return 1 ;; # Return 1 for No
*) echo "Invalid input. Please answer 'yes' or 'no'." * ) echo "❌ Invalid input. Please answer Yes/no or Y/n." ;;
echo;;
esac esac
done done
} }
# ========================= CHECK FOR MISSING PACKAGES ========================= # Function to check and install missing dependencies
required_packages=(ufw jq openssl) check_dependencies() {
missing_packages=() local dependencies=(curl node git)
local missing=()
for pkg in "${required_packages[@]}"; do for dependency in "${dependencies[@]}"; do
if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then if ! command -v "$dependency" &> /dev/null; then
missing_packages+=("$pkg") missing+=("$dependency")
fi
done
if [ ${#missing_packages[@]} -gt 0 ]; then
echo "WARN: The following packages are not installed: ${missing_packages[*]}"
if ! ask_user "Do you want to install them now?"; then
echo "FAIL: Missing dependencies packages. Installation cannot proceed without them."
exit 1
fi
echo "INFO: Installing missing dependencies: ${missing_packages[*]}"
if ! sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"; then
echo "FAIL: Failed to install the required dependencies. Aborting installation!"
exit 1
fi
echo -e "INFO: All required dependencies have been successfully installed.\n"
else
echo "INFO: Dependencies are already installed on this machine."
fi
# =========================== Check if the service already exists ===========================
if systemctl list-unit-files | grep -q '^abuseipdb-ufw.service'; then
echo "WARN: abuseipdb-ufw.service is already installed! If you plan to update or reinstall, choose 'Yes'."
if ask_user "Do you want to remove the existing service?"; then
sudo systemctl stop abuseipdb-ufw.service
sudo systemctl disable abuseipdb-ufw.service
sudo rm /etc/systemd/system/abuseipdb-ufw.service
sudo systemctl daemon-reload
else else
echo -e "INFO: Existing service will not be removed\n" echo "✅ $dependency is installed ($(command -v "$dependency"))"
if $dependency --version &> /dev/null; then
$dependency --version
else
echo "ℹ️ Version information for $dependency is unavailable"
fi fi
fi fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
# =========================== Prepare installation directory =========================== echo "🚨 Missing dependencies: ${missing[*]}"
install_dir="/usr/local/bin/UFW-AbuseIPDB-Reporter" for dep in "${missing[@]}"; do
script_path="$install_dir/reporter.sh" if yes_no_prompt "📦 Do you want to install $dep?"; then
if [ -d "$install_dir" ]; then case $dep in
echo "INFO: Directory $install_dir already exists. Removing it..." curl ) sudo apt-get install -y curl ;;
if ! sudo rm -rf "$install_dir"; then node ) curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh && sudo bash nodesource_setup.sh && sudo apt-get install -y nodejs && rm -f nodesource_setup.sh ;;
echo "FAIL: Something went wrong. Failed to remove existing directory $install_dir." git ) sudo add-apt-repository ppa:git-core/ppa && sudo apt-get update && sudo apt-get -y install git ;;
esac
else
echo "❌ Cannot proceed without $dep. Exiting..."
exit 1 exit 1
fi fi
fi done
else
echo "✅ All dependencies are installed"
fi
}
echo "INFO: Creating installation directory at $install_dir..." # Check dependencies before proceeding
if ! sudo mkdir -p "$install_dir"; then check_dependencies
echo "FAIL: Something went wrong. Failed to create installation directory."
# Function to validate AbuseIPDB API key
validate_token() {
local api_key=$1
local api_url="https://api.abuseipdb.com/api/v2/check?ipAddress=8.8.8.8"
local response
if command -v curl &>/dev/null; then
response=$(curl -s -o /dev/null -w "%{http_code}" -H "Key: $api_key" "$api_url")
elif command -v wget &>/dev/null; then
response=$(wget --quiet --server-response --header="Key: $api_key" --output-document=/dev/null "$api_url" 2>&1 | awk '/HTTP\/1\.[01] [0-9]{3}/ {print $2}' | tail -n1)
else
echo "🚨 Neither curl nor wget is installed. Please install one of them to proceed."
exit 1 exit 1
fi fi
echo
if [[ $response -eq 200 ]]; then
echo "✅ Yay! Token is valid."
return 0
else
echo "❌ Invalid token! Please try again."
return 1
fi
}
# =========================== Prepare reporter.sh script =========================== # Check for UFW log file
GITHUB_URL="https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/reporter.sh" if [[ ! -f /var/log/ufw.log ]]; then
echo "INFO: Downloading reporter.sh from $GITHUB_URL..." read -r -p "🔍 /var/log/ufw.log not found. Please enter the path to your log file: " ufw_log_path
if ! download_file "$GITHUB_URL" "$script_path"; then if [[ -f $ufw_log_path ]]; then
echo "FAIL: Something went wrong while downloading the file from GitHub servers! Maybe try running this script as sudo?" echo "✅ Log file found at $ufw_log_path"
else
echo "❌ Provided log file path does not exist. Exiting..."
exit 1 exit 1
fi
else
ufw_log_path="/var/log/ufw.log"
echo "✅ /var/log/ufw.log exists"
fi fi
echo "INFO: Saved reporter.sh at location $script_path"
if ! sudo chmod +x "$script_path"; then # Prompt for AbuseIPDB API token
echo "FAIL: Failed to make reporter.sh executable." while true; do
exit 1 read -r -p "🔑 Please enter your AbuseIPDB API token: " api_token
fi if validate_token "$api_token"; then
echo -e "INFO: reporter.sh has been made executable.\n"
# =========================== AbuseIPDB API token ===========================
max_attempts=4
valid_token=false
for ((attempts = 0; attempts < max_attempts; attempts++)); do
read -rsp "$ Please enter your AbuseIPDB API token: " api_key
echo
if [[ "$api_key" =~ ^[a-f0-9]{80}$ ]]; then
valid_token=true
break break
fi fi
continue
done
attempts_left=$((max_attempts - attempts - 1)) # Prompt for server ID
if (( attempts_left > 0 )); then while true; do
echo "WARN: Invalid API token format. Please enter an 80-character hexadecimal string. You have $attempts_left/$max_attempts attempts left." read -r -p "🖥️ Enter the server ID (e.g., homeserver1). Leave blank if you do not wish to provide one: " server_id
if [[ -z $server_id ]]; then
server_id=null
break
elif [[ $server_id =~ ^[A-Za-z0-9]{1,16}$ ]]; then
break
else
echo "❌ It must be 1-16 characters long, contain only letters and numbers, and have no spaces or special characters."
fi fi
done done
if [[ "$valid_token" != true ]]; then # Prompt for system update and upgrade
echo "FAIL: Maximum number of attempts reached. Installation aborted!" if yes_no_prompt "🛠️ Do you want the script to run apt update and apt upgrade for you?"; then
exit 1 echo "🔧 Updating and upgrading the system..."
sudo apt-get update && sudo apt-get upgrade
fi fi
# Encode the API token # Clone repository & set permissions
token_file="$install_dir/.abuseipdb_token" if [ ! -d "/opt" ]; then
echo "INFO: Encoding data (file $token_file)..." mkdir -p /opt
if ! echo -n "$api_key" | openssl enc -base64 | sudo tee "$token_file" >/dev/null; then echo "📂 '/opt' has been created"
echo "FAIL: Something went wrong. Failed to encode API token."
exit 1
fi
# Update the ENCODED_API_KEY_FILE variable in reporter.sh by replacing the existing definition
echo "INFO: Updating ENCODED_API_KEY_FILE variable in reporter.sh..."
if ! sudo sed -i "s|^ENCODED_API_KEY_FILE=.*|ENCODED_API_KEY_FILE=\"$token_file\"|" "$script_path"; then
echo "FAIL: Failed to update ENCODED_API_KEY_FILE in reporter.sh."
exit 1
fi
echo "INFO: Setting permissions (chmod 644) for /var/log/ufw.log..."
sudo chmod 644 /var/log/ufw.log
echo
# =========================== abuseipdb-ufw.service ===========================
if ask_user "Would you like to add reporter.sh as a service and start it?"; then
service_file="/etc/systemd/system/abuseipdb-ufw.service"
echo "INFO: Setting up reporter.sh as a service"
if ! sudo bash -c "cat > $service_file" <<-EOF
[Unit]
Description=UFW AbuseIPDB Reporter
After=network.target
Documentation=$REPO
[Service]
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=$script_path
Restart=always
User=$(logname)
WorkingDirectory=$install_dir
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
then
echo "FAIL: Failed to create service file. Please check your permissions!"
exit 1
fi
sudo systemctl daemon-reload
if sudo systemctl enable abuseipdb-ufw.service && sudo systemctl start abuseipdb-ufw.service; then
echo "INFO: Attempting to start the abuseipdb-ufw.service..."
else
echo "FAIL: Failed to enable or start the abuseipdb-ufw.service. Please check the system logs for details."
exit 1
fi
echo "INFO: Waiting 8 seconds to verify the script's stability..."
sleep 8
if sudo systemctl is-active --quiet abuseipdb-ufw.service; then
echo "INFO: abuseipdb-ufw.service is running!"
sudo systemctl status abuseipdb-ufw.service --no-pager
else
echo "FAIL: abuseipdb-ufw.service failed to start."
sudo systemctl status abuseipdb-ufw.service --no-pager
exit 1
fi
else else
echo -e "INFO: reporter.sh will not be added as a service. Running the script directly... Press ^C to stop.\n" echo "✅ '/opt' directory already exists"
if "$script_path"; then
echo "INFO: reporter.sh executed successfully."
else
echo "FAIL: Failed to execute reporter.sh!"
exit 1
fi
fi fi
echo
# Prompt to add the service to autostart cd /opt || { echo "❌ Failed to change directory to '/opt'. Exiting..."; exit 1; }
if ask_user "Do you want to add abuseipdb-ufw.service to autostart?"; then
if sudo systemctl enable abuseipdb-ufw.service; then if [ ! -d "UFW-AbuseIPDB-Reporter" ]; then
echo "INFO: Great! abuseipdb-ufw.service has been added to autostart. Installation finished!" echo "📥 Cloning the UFW-AbuseIPDB-Reporter repository..."
echo "INFO: Run 'journalctl -u abuseipdb-ufw.service -f' to view more logs." sudo git clone https://github.com/sefinek/UFW-AbuseIPDB-Reporter.git --branch node.js || { echo "❌ Failed to clone the repository. Exiting..."; exit 1; }
else
echo "FAIL: Failed to add abuseipdb-ufw.service to autostart!"
exit 1
fi
else else
echo "INFO: abuseipdb-ufw.service will not be added to autostart. Installation finished!" echo "✨ The UFW-AbuseIPDB-Reporter repository already exists"
fi fi
sudo chown "$USER":"$USER" /opt/UFW-AbuseIPDB-Reporter -R
echo "📥 Pulling latest changes..."
cd UFW-AbuseIPDB-Reporter || { echo "❌ Failed to change directory to 'UFW-AbuseIPDB-Reporter'. Exiting..."; exit 1; }
git pull || { echo "❌ Failed to pull the latest changes. Exiting..."; exit 1; }
# Install npm dependencies
echo "📦 Installing npm dependencies..."
npm install -silent
# Copy configuration file
if [ -e config.js ]; then
echo "✅ config.js already exists"
else
echo "📑 Copying default.config.js to config.js..."
cp default.config.js config.js
fi
# Update config.js with API token, Server ID, and UFW log path
config_file="config.js"
if [[ -f $config_file ]]; then
echo "🔧 Updating $PWD/$config_file..."
sed -i "s|UFW_LOG_FILE: .*|UFW_LOG_FILE: '$ufw_log_path',|" $config_file
sed -i "s|SERVER_ID: .*|SERVER_ID: '$server_id',|" $config_file
sed -i "s|ABUSEIPDB_API_KEY: .*|ABUSEIPDB_API_KEY: '$api_token',|" $config_file
else
echo "❌ $config_file not found. Make sure the repository was cloned and initialized correctly."
exit 1
fi
# Create logs directory
echo "📂 Creating /var/log/ufw-abuseipdb directory..."
sudo mkdir -p /var/log/ufw-abuseipdb
sudo chown "$USER":"$USER" /var/log/ufw-abuseipdb -R
# Change permissions for UFW log file
echo "🔒 Changing permissions for $ufw_log_path..."
sudo chmod 644 "$ufw_log_path"
# Install pm2
echo "📦 Installing PM2..."
sudo npm install pm2 -g -silent
# Configure PM2
echo "⚙️ Adding PM2 to autostart..."
startup_command=$(pm2 startup | grep "sudo env PATH" | sed 's/^[^s]*sudo/sudo/')
if [ -n "$startup_command" ]; then
echo "⚙️ Executing: $startup_command"
eval "$startup_command" &>/dev/null || {
echo "❌ Failed to execute the startup command!"
}
else
echo "❌ Failed to find the command generated by pm2 startup! PM2 was not added to autostart."
fi
echo "⚙️ Running a script with PM2 and saving the current state of all processes managed by it..."
pm2 start --silent
pm2 save --silent
# Final
echo "🌌 Checking PM2 status..."
pm2 status
echo -e "\n🎉 Installation and configuration completed! Use the 'pm2 logs' command to monitor logs in real time."
echo -e "\n====================================== Summary ======================================"
echo "🖥️ Server ID : ${server_id:-null}"
echo "🔑 API Key : $api_token"
echo "📂 Script : $PWD"
echo "⚙️ Config File : $PWD/config.js"
echo -e "\n====================================== Support ======================================"
echo "📩 Email : contact@sefinek.net"
echo "🔵 Discord : https://discord.gg/RVH8UXgmzs"
echo "😺 GitHub Issues : https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues"
+261
View File
@@ -0,0 +1,261 @@
{
"name": "ufw-abuseipdb-reporter",
"version": "0.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ufw-abuseipdb-reporter",
"version": "0.1.2",
"license": "GPL-3.0",
"dependencies": {
"axios": "^1.7.9",
"chokidar": "^4.0.3",
"cron": "^3.5.0",
"ipaddr.js": "^2.2.0",
"simple-git": "^3.27.0"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"globals": "^15.14.0"
}
},
"node_modules/@eslint/js": {
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1"
}
},
"node_modules/@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"license": "MIT"
},
"node_modules/@types/luxon": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
"integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/cron": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/cron/-/cron-3.5.0.tgz",
"integrity": "sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==",
"license": "MIT",
"dependencies": {
"@types/luxon": "~3.4.0",
"luxon": "~3.5.0"
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/globals": {
"version": "15.14.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
"integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ipaddr.js": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/luxon": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/readdirp": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz",
"integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==",
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/simple-git": {
"version": "3.27.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz",
"integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==",
"license": "MIT",
"dependencies": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.3.5"
},
"funding": {
"type": "github",
"url": "https://github.com/steveukx/git-js?sponsor=1"
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
{
"name": "ufw-abuseipdb-reporter",
"version": "0.1.2",
"description": "A tool (with a simple installer) that monitors UFW firewall logs in real time and reports IP addresses to the AbuseIPDB database.",
"keywords": [
"ufw",
"abuseipdb"
],
"homepage": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter",
"bugs": {
"url": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sefinek/UFW-AbuseIPDB-Reporter.git"
},
"license": "GPL-3.0",
"author": "Sefinek <contact@sefinek.net> (https://sefinek.net)",
"type": "commonjs",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"up": "ncu -u && npm install && npm update && npm audit fix"
},
"dependencies": {
"axios": "^1.7.9",
"chokidar": "^4.0.3",
"cron": "^3.5.0",
"ipaddr.js": "^2.2.0",
"simple-git": "^3.27.0"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"globals": "^15.14.0"
}
}
+1
View File
@@ -0,0 +1 @@
git pull && pm2 restart ufw-abuseipdb && pm2 logs ufw-abuseipdb
-222
View File
@@ -1,222 +0,0 @@
#!/bin/bash
###
# https://github.com/sefinek/UFW-AbuseIPDB-Reporter
##
LOG_FILE="/var/log/ufw.log"
ENCODED_API_KEY_FILE="./.abuseipdb_token"
REPORTED_IPS_FILE="/tmp/ufw-abuseipdb-reporter.cache"
REPORT_INTERVAL=43200 # 12h (in seconds)
declare -A reported_ips
log() {
local level="$1"
local message="$2"
echo "[$level] $message"
}
# Check if the API key file exists and decode it
if [[ -f "$ENCODED_API_KEY_FILE" ]]; then
DECODED_API_KEY=$(openssl enc -d -base64 -in "$ENCODED_API_KEY_FILE")
if [[ -z "$DECODED_API_KEY" ]]; then
log "ERROR" "Failed to decode API key from $ENCODED_API_KEY_FILE"
exit 1
fi
else
log "ERROR" "API key file not found at $ENCODED_API_KEY_FILE"
exit 1
fi
ABUSEIPDB_API_KEY="$DECODED_API_KEY"
# Check if jq, curl, or wget packages are available
if ! command -v jq &> /dev/null; then
log "ERROR" "jq is not installed. Please install jq to run this script."
exit 1
fi
if ! command -v curl &> /dev/null && ! command -v wget &> /dev/null; then
log "ERROR" "Neither curl nor wget is available. Please install one of them to continue."
exit 1
fi
load_reported_ips() {
if [[ -f "$REPORTED_IPS_FILE" ]]; then
while IFS= read -r line; do
[[ -z "$line" ]] && continue
IFS=' ' read -r ip report_time <<< "$line"
if [[ -n "$ip" && -n "$report_time" ]]; then
reported_ips["$ip"]=$report_time
else
log "WARN" "Invalid line format: '$line'"
fi
done < "$REPORTED_IPS_FILE"
log "INFO" "Loaded ${#reported_ips[@]} IPs from $REPORTED_IPS_FILE"
else
log "INFO" "$REPORTED_IPS_FILE does not exist. No data to load."
fi
}
save_reported_ips() {
: > "$REPORTED_IPS_FILE"
for ip in "${!reported_ips[@]}"; do
echo "$ip ${reported_ips[$ip]}" >> "$REPORTED_IPS_FILE"
done
}
is_local_ip() {
local ip="$1"
[[ "$ip" =~ ^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|fc|fd|fe80|::1) ]]
}
report_to_abuseipdb() {
local ip="$1" categories="$2" proto="$3" spt="$4" dpt="$5" ttl="$6" len="$7" tos="$8" timestamp="$9"
local comment="Blocked by UFW ($proto on $dpt)
Source port: $spt"
[[ -n "$ttl" ]] && comment+="
TTL: $ttl"
[[ -n "$len" ]] && comment+="
Packet length: $len"
[[ -n "$tos" ]] && comment+="
TOS: $tos"
comment+="
This report (for $ip) was generated by:
https://github.com/sefinek/UFW-AbuseIPDB-Reporter" # Please do not remove the URL to the repository of this script. I would be really grateful. 💙
local res
if command -v curl >/dev/null 2>&1; then
res=$(curl -s -X POST "https://api.abuseipdb.com/api/v2/report" \
--data-urlencode "ip=$ip" \
--data-urlencode "categories=$categories" \
--data-urlencode "comment=$comment" \
-H "Key: $ABUSEIPDB_API_KEY" \
-H "Accept: application/json")
elif command -v wget >/dev/null 2>&1; then
res=$(wget -qO- --post-data="ip=$ip&categories=$categories&comment=$comment" \
--header="Key: $ABUSEIPDB_API_KEY" \
--header="Accept: application/json" \
"https://api.abuseipdb.com/api/v2/report")
else
log "ERROR" "Neither curl nor wget is available to send the report."
return 1
fi
local abuse_confidence_score
abuse_confidence_score=$(echo "$res" | jq -r '.data.abuseConfidenceScore')
if [[ "$abuse_confidence_score" =~ ^[0-9]+$ ]]; then
log "INFO" "Successfully reported IP $ip to AbuseIPDB (score $abuse_confidence_score)"
return 0
else
log "ERROR" "Failed to report IP $ip to AbuseIPDB: $res"
return 1
fi
}
is_ip_reported_recently() {
local ip="$1"
local current_time
current_time=$(date +%s)
if [[ -v reported_ips["$ip"] ]]; then
local report_time=${reported_ips["$ip"]}
(( current_time - report_time < REPORT_INTERVAL )) && return 0
fi
return 1
}
mark_ip_as_reported() {
local ip="$1"
reported_ips["$ip"]=$(date +%s)
}
determine_categories() {
local proto="$1"
local dpt="$2"
# See: https://www.abuseipdb.com/categories
case "$proto" in
"TCP")
case "$dpt" in
22) echo "14,22,18" ;; # Port Scan | SSH | Brute-Force
80 | 443 | 8080) echo "14,21" ;; # Port Scan | Web App Attack
25) echo "14,11" ;; # Port Scan | Email Spam
21) echo "14,5,18" ;; # Port Scan | FTP Brute-Force | Brute-Force
53) echo "14,1,2" ;; # Port Scan | DNS Compromise | DNS Poisoning
23 | 3389) echo "14,15,18" ;; # Port Scan | Hacking | Brute-Force
3306) echo "14,16" ;; # Port Scan | SQL Injection
6666 | 6667 | 6668 | 6669) echo "14,8" ;; # Port Scan | Fraud VoIP
9999) echo "14,6" ;; # Port Scan | Ping of Death
*) echo "14" ;; # Port Scan
esac
;;
"UDP")
case "$dpt" in
53) echo "14,1,2" ;; # Port Scan | DNS Compromise | DNS Poisoning
123) echo "14,17" ;; # Port Scan | Spoofing
*) echo "14" ;; # Port Scan
esac
;;
*) echo "14" ;; # Port Scan
esac
}
process_log_line() {
local line="$1"
if [[ "$line" == *"[UFW BLOCK]"* ]]; then
local timestamp src_ip proto spt dpt ttl len tos categories
timestamp=$(echo "$line" | awk '{print $1, $2, $3}')
[[ -z "$timestamp" ]] && timestamp=$(date '+%Y-%m-%d %H:%M:%S')
src_ip=$(echo "$line" | grep -oP 'SRC=\K[^\s]+')
if is_local_ip "$src_ip"; then
log "INFO" "Ignoring local IP: $src_ip"
return
fi
proto=$(echo "$line" | grep -oP 'PROTO=\K[^\s]+')
spt=$(echo "$line" | grep -oP 'SPT=\K[^\s]+')
dpt=$(echo "$line" | grep -oP 'DPT=\K[^\s]+')
ttl=$(echo "$line" | grep -oP 'TTL=\K[^\s]+')
len=$(echo "$line" | grep -oP 'LEN=\K[^\s]+')
tos=$(echo "$line" | grep -oP 'TOS=\K[^\s]+')
# Report MUST NOT be of an attack where the source address is likely spoofed i.e. SYN floods and UDP floods.
# TCP connections can only be reported if they complete the three-way handshake. UDP connections cannot be reported.
# More: https://www.abuseipdb.com/reporting-policy
if [[ "$proto" == "UDP" ]]; then
log "INFO" "Skipping UDP traffic: SRC=$src_ip DPT=$dpt"
return
fi
if is_ip_reported_recently "$src_ip"; then
log "INFO" "IP $src_ip ($proto) was reported recently"
return
fi
categories=$(determine_categories "$proto" "$dpt")
log "INFO" "Reporting IP $src_ip ($proto $dpt) with categories $categories..."
if report_to_abuseipdb "$src_ip" "$categories" "$proto" "$spt" "$dpt" "$ttl" "$len" "$tos" "$timestamp"; then
mark_ip_as_reported "$src_ip"
save_reported_ips
fi
fi
}
load_reported_ips
log "INFO" "Starting to monitor $LOG_FILE"
tail -Fn0 "$LOG_FILE" | while read -r line; do
process_log_line "$line"
done
+13
View File
@@ -0,0 +1,13 @@
const axios = require('axios');
const { version } = require('../package.json');
axios.defaults.headers.common = {
'User-Agent': `Mozilla/5.0 (compatible; UFW-AbuseIPDB-Reporter/${version}; +https://github.com/sefinek/UFW-AbuseIPDB-Reporter)`,
'Accept': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
};
axios.defaults.timeout = 30000;
module.exports = axios;
+30
View File
@@ -0,0 +1,30 @@
const { existsSync, readFileSync, writeFileSync } = require('node:fs');
const { CACHE_FILE, IP_REPORT_COOLDOWN } = require('../config.js').MAIN;
const log = require('../utils/log.js');
const reportedIPs = new Map();
const loadReportedIPs = () => {
if (existsSync(CACHE_FILE)) {
readFileSync(CACHE_FILE, 'utf8')
.split('\n')
.forEach(line => {
const [ip, time] = line.split(' ');
if (ip && time) reportedIPs.set(ip, Number(time));
});
log(0, `Loaded ${reportedIPs.size} IPs from ${CACHE_FILE}`);
} else {
log(0, `${CACHE_FILE} does not exist. No data to load.`);
}
};
const saveReportedIPs = () => writeFileSync(CACHE_FILE, Array.from(reportedIPs).map(([ip, time]) => `${ip} ${time}`).join('\n'), 'utf8');
const isIPReportedRecently = ip => {
const reportedTime = reportedIPs.get(ip);
return reportedTime && (Date.now() / 1000 - reportedTime < IP_REPORT_COOLDOWN / 1000);
};
const markIPAsReported = ip => reportedIPs.set(ip, Math.floor(Date.now() / 1000));
module.exports = { reportedIPs, loadReportedIPs, saveReportedIPs, isIPReportedRecently, markIPAsReported };
+40
View File
@@ -0,0 +1,40 @@
const { networkInterfaces } = require('node:os');
const axios = require('./axios.js');
const isLocalIP = require('../utils/isLocalIP.js');
const { REFRESHING_IP_ADDRESS } = require('../config.js').MAIN;
const ipAddrList = new Set();
const fetchIPv4Address = async () => {
try {
const { data } = await axios.get('https://api.sefinek.net/api/v2/ip');
if (data?.success && data?.message) ipAddrList.add(data.message);
} catch (err) {
console.warn('Error fetching IPv4 address:', err.message);
}
};
const fetchIPv6Address = () => {
try {
Object.values(networkInterfaces()).flat().forEach(({ address, internal }) => {
if (!internal && address && !isLocalIP(address)) ipAddrList.add(address);
});
} catch (err) {
console.warn('Error fetching IPv6 address:', err.message);
}
};
const fetchServerIPs = async () => {
ipAddrList.clear();
await fetchIPv4Address();
fetchIPv6Address();
};
(async () => {
await fetchServerIPs();
setInterval(fetchServerIPs, REFRESHING_IP_ADDRESS);
// console.debug(ipAddrList);
})();
module.exports = () => Array.from(ipAddrList);
+19
View File
@@ -0,0 +1,19 @@
const { exec } = require('node:child_process');
const ecosystem = require('../ecosystem.config.js');
const executeCommand = cmd =>
new Promise((resolve, reject) => {
exec(cmd, (err, stdout, stderr) => {
if (err || stderr) reject(err || stderr);
else resolve(stdout);
});
});
module.exports = async () => {
try {
console.log(await executeCommand('npm install --omit=dev'));
console.log(await executeCommand(`pm2 restart ${ecosystem.apps[0].name}`));
} catch (err) {
console.error(err);
}
};
+29
View File
@@ -0,0 +1,29 @@
const { AUTO_UPDATE_SCHEDULE } = require('../config.js').MAIN;
const simpleGit = require('simple-git');
const { CronJob } = require('cron');
const restartApp = require('./reloadApp.js');
const log = require('../utils/log.js');
const git = simpleGit();
const pull = async () => {
log(0, '$ git pull');
const { summary } = await git.pull();
log(0, `Changes: ${summary.changes}; Deletions: ${summary.insertions}; Insertions: ${summary.insertions};`);
};
const pullAndRestart = async () => {
try {
await pull();
await restartApp();
} catch (err) {
log(2, err.message);
}
};
// https://crontab.guru
new CronJob(AUTO_UPDATE_SCHEDULE || '0 18 * * *', pullAndRestart, null, true, 'UTC'); // At 18:00
module.exports = { pull };
+10
View File
@@ -0,0 +1,10 @@
const ipaddr = require('ipaddr.js');
module.exports = ip => {
const range = ipaddr.parse(ip).range();
return [
'unspecified', 'multicast', 'linkLocal', 'loopback', 'reserved', 'benchmarking',
'amt', 'broadcast', 'carrierGradeNat', 'private', 'as112', 'uniqueLocal',
'ipv4Mapped', 'rfc6145', '6to4', 'teredo', 'as112v6', 'orchid2', 'droneRemoteIdProtocolEntityTags',
].includes(range);
};
+10
View File
@@ -0,0 +1,10 @@
const levels = {
0: { method: 'log', label: '[INFO]' },
1: { method: 'warn', label: '[WARN]' },
2: { method: 'error', label: '[FAIL]' },
};
module.exports = (level, msg) => {
const { method, label } = levels[level] || { method: 'log', label: '[N/A]' };
console[method](`${label} ${msg}`);
};