This commit is contained in:
Sefinek 2025-02-02 01:51:39 +01:00
parent a0eb452044
commit 73c8a834d3
22 changed files with 214 additions and 212 deletions

View file

@ -1,42 +0,0 @@
# production or development
NODE_ENV=production
############################### TOKENS ###############################
# Cloudflare (https://dash.cloudflare.com/profile/api-tokens)
CLOUDFLARE_ZONE_ID=00000000000000000000000000000000
CLOUDFLARE_API_KEY=0000000000000000000000000000000000000000
# AbuseIPDB (https://www.abuseipdb.com/account/api)
ABUSEIPDB_API_KEY=00000000000000000000000000000000000000000000000000000000000000000000000000000000
############################### CYCLES ###############################
# Main interval (in minutes) of each cycle. For production 2h; development 8s.
CYCLE_INTERVAL=120
# The minimum time (in hours) that must pass after reporting an IP address before it can be reported again.
# The required time is >= 15 minutes, according to AbuseIPDB API limits.
REPORTED_IP_COOLDOWN=7
# The maximum URI length that can be reported to AbuseIPDB.
# If Cloudflare returns a longer URI, the API request will fail.
MAX_URL_LENGTH=920
# Additional delay (in miliseconds) after each successful IP report to avoid overloading the AbuseIPDB API.
SUCCESS_COOLDOWN=80
# Interval for refreshing your IP address (in minutes).
# This ensures that WAF violations originating from your IP address are not reported to AbuseIPDB.
IP_REFRESH_INTERVAL=80
############################### SEFINEK API ###############################
# Report IP addresses to api.sefinek.net to support the development of the repository at https://github.com/sefinek/Malicious-IP-Addresses. SEFINEK_API_SECRET is required if true.
REPORT_TO_SEFINEK_API=false
# Secret key for api.sefinek.net
SEFINEK_API_SECRET=
# How often should the log (reported_ips.csv) be analyzed and sent to the Sefinek API? In hours.
SEFINEK_API_INTERVAL=1
# Sefinek API v2 URL
SEFINEK_API_URL=https://api.sefinek.net/api/v2

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
node_modules node_modules
.env config.js
reported_ips.csv reported_ips.csv

View file

@ -22,7 +22,6 @@ Triggered Cloudflare WAF (securitylevel) from T1.
Action taken: MANAGED_CHALLENGE Action taken: MANAGED_CHALLENGE
ASN: 53667 (PONYNET) ASN: 53667 (PONYNET)
Protocol: HTTP/1.0 (method GET) Protocol: HTTP/1.0 (method GET)
Zone: blocklist.sefinek.net
Endpoint: / Endpoint: /
Timestamp: 2024-11-09T19:20:18Z Timestamp: 2024-11-09T19:20:18Z
Ray ID: 8e0028cb79ab3a96 Ray ID: 8e0028cb79ab3a96
@ -47,33 +46,40 @@ https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB
```bash ```bash
npm install npm install
``` ```
3. Environment variables. Create a new `.env.default` file with the same content, then rename it to `.env`. Fill it with your tokens, etc. Remember to set `NODE_ENV` to `production`! 3. Create a new configuration file.
4. Run the script. ```bash
cp config.default.js config.js
```
4. Paste the tokens into the `config.js` file. Make sure that `NODE_ENV` is set to `production`.
```bash
nano config.js
```
5. Run the script.
```bash ```bash
node . node .
``` ```
5. If you want to run the process 24/7, install the [PM2](https://www.npmjs.com/package/pm2) module. 6. If you want to run the process 24/7, install the [PM2](https://www.npmjs.com/package/pm2) module.
```bash ```bash
npm install pm2 -g npm install pm2 -g
``` ```
6. Modify the log paths in the `ecosystem.config.js` file to be correct and existing. You don't need to create `.log` files, just ensure the directory structure is accurate. 7. Modify the log paths in the `ecosystem.config.js` file to be correct and existing. You don't need to create `.log` files, just ensure the directory structure is accurate.
7. Run the process continuously using `PM2` to ensure constant operation and automatic restart in case of a failure. 8. Run the process continuously using `PM2` to ensure constant operation and automatic restart in case of a failure.
```bash ```bash
pm2 start pm2 start
``` ```
8. Save a snapshot of the currently running `Node.js` processes. 9. Save a snapshot of the currently running `Node.js` processes.
```bash ```bash
pm2 save pm2 save
``` ```
9. Add `PM2` to startup. 10. Add `PM2` to startup.
```bash ```bash
pm2 startup pm2 startup
``` ```
10. Execute the command generated by PM2, e.g.: 11. Execute the command generated by PM2, e.g.:
```bash ```bash
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u sefinek --hp /home/sefinek sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u sefinek --hp /home/sefinek
``` ```
11. Thats it! Monitor logs using the `pm2 logs` command. 12. Thats it! Monitor logs using the `pm2 logs` command.
## 🔤 How to Get Tokens? ## 🔤 How to Get Tokens?
@ -100,4 +106,4 @@ I'm not particularly fond of Python and usually try to avoid using this programm
## 📑 MIT License ## 📑 MIT License
Copyright 2024 © by [Sefinek](https://sefinek.net). All Rights Reserved. Copyright 2024-2025 © by [Sefinek](https://sefinek.net). All Rights Reserved.

64
config.default.js Normal file
View file

@ -0,0 +1,64 @@
exports.CONFIG = {
MAIN: {
NODE_ENV: 'production', // Environment mode: 'production' or 'development'
CLOUDFLARE_ZONE_ID: '00000000000000000000000000000000', // https://dash.cloudflare.com/profile/api-tokens
CLOUDFLARE_API_KEY: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', // API key for Cloudflare access
ABUSEIPDB_API_KEY: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', // API key for reporting malicious IPs to AbuseIPDB
},
CYCLES: {
// Main interval (in minutes) of each cycle
CYCLE_INTERVAL: 120 * 60 * 1000,
// The minimum time (in hours) that must pass after reporting an IP address before it can be reported again.
// The required time is >= 15 minutes, according to AbuseIPDB API limits.
REPORTED_IP_COOLDOWN: 6 * 60 * 60 * 1000,
// The maximum URI length that can be reported to AbuseIPDB.
// If Cloudflare returns a longer URI, the API request will fail.
MAX_URL_LENGTH: 780,
// Additional delay (in milliseconds) after each successful IP report to avoid overloading the AbuseIPDB API.
SUCCESS_COOLDOWN: 30,
// Interval for refreshing your IP address (in minutes).
// This ensures that WAF violations originating from your IP address are not reported to AbuseIPDB.
IP_REFRESH_INTERVAL: 80 * 60 * 1000,
},
SEFINEK_API: {
// Report IP addresses to api.sefinek.net to support the development of the repository at https://github.com/sefinek/Malicious-IP-Addresses. SECRET_TOKEN is required if true.
REPORT_TO_SEFIN_API: true,
// Secret key for api.sefinek.net
SECRET_TOKEN: 'HKKAUZHTDAH7W87SyL6XsWkV8UeUFVA9VvvXhn6H9Wn6kfDW6ZsXCtbahmkaYcLbxZGyrAKPmSkXb3AJ6pCU3VuDyTjUSehMyDZ',
// How often should the log (reported_ips.csv) be analyzed and sent to the Sefinek API? In hours.
INTERVAL: 60 * 60 * 1000, // Frequency for analyzing and submitting logs to the Sefinek API
},
};
exports.GENERATE_COMMENT = ({ action, clientAsn, clientASNDescription, clientRequestHTTPProtocol, clientRequestHTTPMethodName, clientRequestHTTPHost, clientRequestPath, clientRequestQuery, datetime, rayName, ruleId, userAgent, source, clientCountryName }) => {
const fields = [
{ label: 'Action taken', value: action?.toUpperCase() },
{ label: 'ASN', value: `${clientAsn} (${clientASNDescription})` },
{ label: 'Protocol', value: `${clientRequestHTTPProtocol} (${clientRequestHTTPMethodName} method)` },
// { label: 'Zone', value: clientRequestHTTPHost },
{ label: 'Endpoint', value: clientRequestPath },
{ label: 'Query', value: clientRequestQuery },
{ label: 'Timestamp', value: datetime },
{ label: 'Ray ID', value: rayName },
// { label: 'Rule ID', value: ruleId },
{ label: 'UA', value: userAgent || 'Empty string' },
];
const reportLines = fields
.filter(({ value }) => value)
.map(({ label, value }) => `${label}: ${value}`);
return `Triggered Cloudflare WAF (${source}) from ${clientCountryName}.
${reportLines.join('\n')}
Report generated by Cloudflare-WAF-To-AbuseIPDB:
https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB`; // Please do not remove the repository URL. I'd really appreciate it (:
};

View file

@ -1,17 +1,14 @@
require('dotenv').config(); const axios = require('./services/axios.js');
const { CONFIG, GENERATE_COMMENT } = require('./config.js');
const { axios } = require('./services/axios.js');
const { CYCLE_INTERVAL, REPORTED_IP_COOLDOWN, MAX_URL_LENGTH, SUCCESS_COOLDOWN, SEFINEK_API_INTERVAL, REPORT_TO_SEFINEK_API } = require('./scripts/config.js');
const PAYLOAD = require('./services/payload.js'); const PAYLOAD = require('./services/payload.js');
const generateComment = require('./scripts/generateComment.js');
const SefinekAPI = require('./services/SefinekAPI.js'); const SefinekAPI = require('./services/SefinekAPI.js');
const isImageRequest = require('./scripts/isImageRequest.js'); const isImageRequest = require('./utils/isImageRequest.js');
const headers = require('./scripts/headers.js'); const headers = require('./utils/headers.js');
const { logToCSV, readReportedIPs, wasImageRequestLogged } = require('./services/csv.js'); const { logToCSV, readReportedIPs, wasImageRequestLogged } = require('./services/csv.js');
const formatDelay = require('./scripts/formatDelay.js'); const formatDelay = require('./utils/formatDelay.js');
const clientIp = require('./services/clientIp.js'); const fetchServerIP = require('./services/fetchServerIP.js');
const whitelist = require('./scripts/whitelist.js'); const whitelist = require('./utils/whitelist.js');
const log = require('./scripts/log.js'); const log = require('./utils/log.js');
const fetchBlockedIPs = async () => { const fetchBlockedIPs = async () => {
try { try {
@ -19,7 +16,7 @@ const fetchBlockedIPs = async () => {
const events = data?.data?.viewer?.zones[0]?.firewallEventsAdaptive; const events = data?.data?.viewer?.zones[0]?.firewallEventsAdaptive;
if (events) { if (events) {
const filtered = events.filter(x => const filtered = events.filter(x =>
x.ip !== clientIp.getAddress() && x.ip !== fetchServerIP() &&
( (
x.source === 'securitylevel' || x.source === 'securitylevel' ||
x.source === 'badscore' || x.source === 'badscore' ||
@ -52,7 +49,7 @@ const isIPReportedRecently = (rayId, ip, reportedIPs) => {
return latest; return latest;
}, null); }, null);
if (lastReport && (Date.now() - lastReport.timestamp) < REPORTED_IP_COOLDOWN) { if (lastReport && (Date.now() - lastReport.timestamp) < CONFIG.CYCLES.REPORTED_IP_COOLDOWN) {
return { recentlyReported: true, timeDifference: Date.now() - lastReport.timestamp, reason: lastReport.status === 'TOO_MANY_REQUESTS' ? 'RATE-LIMITED' : 'REPORTED' }; return { recentlyReported: true, timeDifference: Date.now() - lastReport.timestamp, reason: lastReport.status === 'TOO_MANY_REQUESTS' ? 'RATE-LIMITED' : 'REPORTED' };
} }
@ -66,13 +63,13 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
return false; return false;
} }
if (event.clientIP === clientIp.address) { if (event.clientIP === fetchServerIP()) {
logToCSV(event.rayName, event.clientIP, country, hostname, endpoint, event.userAgent, event.action, 'YOUR_IP_ADDRESS'); logToCSV(event.rayName, event.clientIP, country, hostname, endpoint, event.userAgent, event.action, 'YOUR_IP_ADDRESS');
log(0, `Your IP address (${event.clientIP}) was unexpectedly received from Cloudflare. URI: ${uri}`); log(0, `Your IP address (${event.clientIP}) was unexpectedly received from Cloudflare. URI: ${uri}`);
return false; return false;
} }
if (uri.length > MAX_URL_LENGTH) { if (uri.length > CONFIG.CYCLES.MAX_URL_LENGTH) {
logToCSV(event.rayName, event.clientIP, country, hostname, endpoint, event.userAgent, event.action, 'URI_TOO_LONG'); logToCSV(event.rayName, event.clientIP, country, hostname, endpoint, event.userAgent, event.action, 'URI_TOO_LONG');
// log(0, `URI too long ${event.clientIP}; Received: ${uri}`); // log(0, `URI too long ${event.clientIP}; Received: ${uri}`);
return false; return false;
@ -82,7 +79,7 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
await axios.post('https://api.abuseipdb.com/api/v2/report', { await axios.post('https://api.abuseipdb.com/api/v2/report', {
ip: event.clientIP, ip: event.clientIP,
categories: '19', categories: '19',
comment: generateComment(event), comment: GENERATE_COMMENT(event),
}, { headers: headers.ABUSEIPDB }); }, { headers: headers.ABUSEIPDB });
logToCSV(event.rayName, event.clientIP, country, hostname, endpoint, event.userAgent, event.action, 'REPORTED'); logToCSV(event.rayName, event.clientIP, country, hostname, endpoint, event.userAgent, event.action, 'REPORTED');
@ -105,15 +102,14 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
(async () => { (async () => {
log(0, 'Loading data, please wait...'); log(0, 'Loading data, please wait...');
await clientIp.fetchIPAddress();
// Sefinek API // Sefinek API
if (REPORT_TO_SEFINEK_API && SEFINEK_API_INTERVAL && process.env.SEFINEK_API_SECRET) { if (CONFIG.SEFINEK_API.REPORT_TO_SEFIN_API && CONFIG.SEFINEK_API.INTERVAL && CONFIG.SEFINEK_API.SECRET_TOKEN) {
setInterval(SefinekAPI, SEFINEK_API_INTERVAL); setInterval(SefinekAPI, CONFIG.SEFINEK_API.INTERVAL);
} }
// Ready // Ready
if (process.env.NODE_ENV === 'production') { if (CONFIG.MAIN.NODE_ENV === 'production') {
try { try {
process.send('ready'); process.send('ready');
} catch (err) { } catch (err) {
@ -132,7 +128,7 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
continue; continue;
} }
const userIp = clientIp.getAddress(); const userIp = fetchServerIP();
if (!userIp) log(1, `Your IP address is missing! Received: ${userIp}`); if (!userIp) log(1, `Your IP address is missing! Received: ${userIp}`);
let cycleImageSkippedCount = 0, cycleProcessedCount = 0, cycleReportedCount = 0, cycleSkippedCount = 0; let cycleImageSkippedCount = 0, cycleProcessedCount = 0, cycleReportedCount = 0, cycleSkippedCount = 0;
@ -153,7 +149,7 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
const reportedIPs = readReportedIPs(); const reportedIPs = readReportedIPs();
const { recentlyReported } = isIPReportedRecently(event.rayName, ip, reportedIPs); const { recentlyReported } = isIPReportedRecently(event.rayName, ip, reportedIPs);
if (recentlyReported) { if (recentlyReported) {
// if (process.env.NODE_ENV === 'development') { // if (MAIN.NODE_ENV === 'development') {
// const hoursAgo = Math.floor(timeDifference / (1000 * 60 * 60)); // const hoursAgo = Math.floor(timeDifference / (1000 * 60 * 60));
// const minutesAgo = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)); // const minutesAgo = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60));
// const secondsAgo = Math.floor((timeDifference % (1000 * 60)) / 1000); // const secondsAgo = Math.floor((timeDifference % (1000 * 60)) / 1000);
@ -178,7 +174,7 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
const wasReported = await reportIP(event, `${event.clientRequestHTTPHost}${event.clientRequestPath}`, event.clientCountryName, event.clientRequestHTTPHost, event.clientRequestPath, cycleErrorCounts); const wasReported = await reportIP(event, `${event.clientRequestHTTPHost}${event.clientRequestPath}`, event.clientCountryName, event.clientRequestHTTPHost, event.clientRequestPath, cycleErrorCounts);
if (wasReported) { if (wasReported) {
cycleReportedCount++; cycleReportedCount++;
await new Promise(resolve => setTimeout(resolve, SUCCESS_COOLDOWN)); await new Promise(resolve => setTimeout(resolve, CONFIG.CYCLES.SUCCESS_COOLDOWN));
} }
} }
@ -190,8 +186,8 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
log(0, `- Other errors: ${cycleErrorCounts.otherErrors}`); log(0, `- Other errors: ${cycleErrorCounts.otherErrors}`);
log(0, '===================== End of Reporting Cycle ====================='); log(0, '===================== End of Reporting Cycle =====================');
log(0, `Waiting ${formatDelay(CYCLE_INTERVAL)}...`); log(0, `Waiting ${formatDelay(CONFIG.CYCLES.CYCLE_INTERVAL)}...`);
cycleId++; cycleId++;
await new Promise(resolve => setTimeout(resolve, CYCLE_INTERVAL)); await new Promise(resolve => setTimeout(resolve, CONFIG.CYCLES.CYCLE_INTERVAL));
} }
})(); })();

35
package-lock.json generated
View file

@ -1,26 +1,26 @@
{ {
"name": "cf-waf-to-abuseipdb", "name": "cf-waf-to-abuseipdb",
"version": "1.2.3", "version": "1.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cf-waf-to-abuseipdb", "name": "cf-waf-to-abuseipdb",
"version": "1.2.3", "version": "1.3.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"dotenv": "^16.4.7" "ipaddr.js": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.18.0", "@eslint/js": "^9.19.0",
"globals": "^15.14.0" "globals": "^15.14.0"
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.18.0", "version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -65,18 +65,6 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.9", "version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@ -124,6 +112,15 @@
"url": "https://github.com/sponsors/sindresorhus" "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/mime-db": { "node_modules/mime-db": {
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "cf-waf-to-abuseipdb", "name": "cf-waf-to-abuseipdb",
"version": "1.2.3", "version": "1.3.0",
"description": "Node.js script for automatically reporting incidents to AbuseIPDB using data obtained from Cloudflare WAF.", "description": "Node.js script for automatically reporting incidents to AbuseIPDB using data obtained from Cloudflare WAF.",
"keywords": [ "keywords": [
"abuseipdb", "abuseipdb",
@ -21,6 +21,7 @@
}, },
"license": "MIT", "license": "MIT",
"author": "Sefinek <contact@sefinek.net> (https://sefinek.net)", "author": "Sefinek <contact@sefinek.net> (https://sefinek.net)",
"type": "commonjs",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
@ -28,10 +29,10 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"dotenv": "^16.4.7" "ipaddr.js": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.18.0", "@eslint/js": "^9.19.0",
"globals": "^15.14.0" "globals": "^15.14.0"
} }
} }

View file

@ -1,25 +0,0 @@
const CYCLE_INTERVAL = process.env.NODE_ENV === 'production' ?
parseInt(process.env.CYCLE_INTERVAL || '120') * 60 * 1000 : 8 * 1000;
const REPORTED_IP_COOLDOWN = parseInt(process.env.REPORTED_IP_COOLDOWN || '6') * 60 * 60 * 1000;
const MAX_URL_LENGTH = parseInt(process.env.MAX_URL_LENGTH || '920');
const SUCCESS_COOLDOWN = parseInt(process.env.SUCCESS_COOLDOWN || '200');
const IP_REFRESH_INTERVAL = parseInt(process.env.IP_REFRESH_INTERVAL || '80') * 60 * 1000;
const REPORT_TO_SEFINEK_API = process.env.REPORT_TO_SEFINEK_API === 'true';
const SEFINEK_API_INTERVAL = process.env.NODE_ENV === 'production' ?
parseInt(process.env.SEFINEK_API_INTERVAL || '1') * 60 * 60 * 1000 : 5 * 1000;
module.exports = {
CYCLE_INTERVAL,
REPORTED_IP_COOLDOWN,
MAX_URL_LENGTH,
SUCCESS_COOLDOWN,
IP_REFRESH_INTERVAL,
REPORT_TO_SEFINEK_API,
SEFINEK_API_INTERVAL,
};

View file

@ -1,30 +0,0 @@
module.exports = ({ action, clientAsn, clientASNDescription, clientRequestHTTPProtocol, clientRequestHTTPMethodName, clientRequestHTTPHost, clientRequestPath, clientRequestQuery, datetime, rayName, ruleId, userAgent, source, clientCountryName }) => {
const fields = [
{ label: 'Action taken', value: action?.toUpperCase() },
{ label: 'ASN', value: `${clientAsn} (${clientASNDescription})` },
{ label: 'Protocol', value: `${clientRequestHTTPProtocol} (${clientRequestHTTPMethodName} method)` },
// { label: 'Zone', value: clientRequestHTTPHost },
{ label: 'Endpoint', value: clientRequestPath },
{ label: 'Query', value: clientRequestQuery },
{ label: 'Timestamp', value: datetime },
{ label: 'Ray ID', value: rayName },
// { label: 'Rule ID', value: ruleId },
{ label: 'UA', value: userAgent || 'Empty string' },
];
const reportLines = fields
.filter(({ value }) => value)
.map(({ label, value }) => `${label}: ${value}`);
return `Triggered Cloudflare WAF (${source}) from ${clientCountryName}.
${reportLines.join('\n')}
Report generated by Cloudflare-WAF-To-AbuseIPDB:
https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB`;
};
/*
* Hello! 👋 I'm really glad you're here.
* Please do not remove the repository URL in the comment above.
* Id really appreciate it (:
*/

View file

@ -1,17 +0,0 @@
const { name, version, homepage } = require('../package.json');
const UserAgent = `Mozilla/5.0 (compatible; ${name}/${version}; +${homepage})`;
const CLOUDFLARE = {
'User-Agent': UserAgent,
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
};
const ABUSEIPDB = {
'User-Agent': UserAgent,
'Content-Type': 'application/json',
'Key': process.env.ABUSEIPDB_API_KEY,
};
module.exports = { UserAgent, CLOUDFLARE, ABUSEIPDB };

View file

@ -1,15 +1,14 @@
const { axios } = require('./axios.js'); const axios = require('./axios.js');
const { readReportedIPs, updateSefinekAPIInCSV } = require('./csv.js'); const { readReportedIPs, updateSefinekAPIInCSV } = require('./csv.js');
const log = require('../scripts/log.js'); const log = require('../utils/log.js');
const clientIp = require('./clientIp.js'); const fetchServerIP = require('./fetchServerIP.js');
const whitelist = require('../scripts/whitelist.js'); const whitelist = require('../utils/whitelist.js');
const { MAIN } = require('../config.js').CONFIG;
const API_URL = `${process.env.SEFINEK_API_URL}/cloudflare-waf-abuseipdb/post`;
module.exports = async () => { module.exports = async () => {
const reportedIPs = (readReportedIPs() || []).filter(x => const reportedIPs = (readReportedIPs() || []).filter(x =>
x.status === 'REPORTED' && x.status === 'REPORTED' &&
x.ip !== clientIp.getAddress() && x.ip !== fetchServerIP() &&
!x.sefinekAPI && !x.sefinekAPI &&
( (
x.source === 'securitylevel' || x.source === 'securitylevel' ||
@ -35,7 +34,7 @@ module.exports = async () => {
if (!uniqueLogs.length) return log(0, 'No unique IPs to send to Sefinek API'); if (!uniqueLogs.length) return log(0, 'No unique IPs to send to Sefinek API');
try { try {
const res = await axios.post(API_URL, { const res = await axios.post('https://api.sefinek.net/api/v2/cloudflare-waf-abuseipdb/post', {
reportedIPs: uniqueLogs.map(ip => ({ reportedIPs: uniqueLogs.map(ip => ({
rayId: ip.rayId, rayId: ip.rayId,
ip: ip.ip, ip: ip.ip,
@ -45,7 +44,7 @@ module.exports = async () => {
country: ip.country, country: ip.country,
timestamp: ip.timestamp, timestamp: ip.timestamp,
})), })),
}, { headers: { 'Authorization': process.env.SEFINEK_API_SECRET } }); }, { headers: { 'Authorization': MAIN.SECRET_TOKEN } });
log(0, `Successfully sent ${uniqueLogs.length} logs to Sefinek API. Status: ${res.status}`); log(0, `Successfully sent ${uniqueLogs.length} logs to Sefinek API. Status: ${res.status}`);

View file

@ -1,8 +1,13 @@
const axios = require('axios'); const axios = require('axios');
const { UserAgent } = require('../scripts/headers.js'); const { UserAgent } = require('../utils/headers.js');
const { version } = require('../package.json');
axios.defaults.headers.common['User-Agent'] = UserAgent; axios.defaults.headers.common = {
axios.defaults.timeout = 25000; 'User-Agent': UserAgent,
'Accept': 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
};
module.exports = { axios, moduleVersion: version }; axios.defaults.timeout = 30000;
module.exports = axios;

View file

@ -1,23 +0,0 @@
const { axios } = require('./axios.js');
const { IP_REFRESH_INTERVAL } = require('../scripts/config.js');
const log = require('../scripts/log.js');
let address = null; // Holds the IP address
const fetchIPAddress = async () => {
try {
const { data } = await axios.get('https://api.sefinek.net/api/v2/ip');
if (data?.success) {
address = data.message;
} else {
log(2, 'Failed to retrieve your IP');
}
} catch (err) {
log(2, 'Error fetching your IP:', err.stack);
}
};
setInterval(fetchIPAddress, IP_REFRESH_INTERVAL);
module.exports = { fetchIPAddress, getAddress: () => address };

View file

@ -1,6 +1,6 @@
const fs = require('node:fs'); const fs = require('node:fs');
const path = require('node:path'); const path = require('node:path');
const log = require('../scripts/log.js'); const log = require('../utils/log.js');
const CSV_FILE_PATH = path.join(__dirname, '..', 'reported_ips.csv'); const CSV_FILE_PATH = path.join(__dirname, '..', 'reported_ips.csv');
const MAX_CSV_SIZE_BYTES = 3 * 1024 * 1024; // 3 MB const MAX_CSV_SIZE_BYTES = 3 * 1024 * 1024; // 3 MB

41
services/fetchServerIP.js Normal file
View file

@ -0,0 +1,41 @@
const { networkInterfaces } = require('node:os');
const axios = require('./axios.js');
const isLocalIP = require('../utils/isLocalIP.js');
const log = require('../utils/log.js');
const { CYCLES } = require('../config.js').CONFIG;
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) {
log(2, `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) {
log(2, `Error fetching IPv6 address: ${err.message}`);
}
};
const fetchServerIPs = async () => {
ipAddrList.clear();
await fetchIPv4Address();
fetchIPv6Address();
};
(async () => {
await fetchServerIPs();
setInterval(fetchServerIPs, CYCLES.IP_REFRESH_INTERVAL);
// console.debug(ipAddrList);
})();
module.exports = () => Array.from(ipAddrList);

View file

@ -1,3 +1,5 @@
const { MAIN } = require('../config.js').CONFIG;
const query = `query ListFirewallEvents($zoneTag: string, $filter: FirewallEventsAdaptiveFilter_InputObject) { const query = `query ListFirewallEvents($zoneTag: string, $filter: FirewallEventsAdaptiveFilter_InputObject) {
viewer { viewer {
zones(filter: { zoneTag: $zoneTag }) { zones(filter: { zoneTag: $zoneTag }) {
@ -28,7 +30,7 @@ const query = `query ListFirewallEvents($zoneTag: string, $filter: FirewallEvent
module.exports = () => { module.exports = () => {
const variables = { const variables = {
zoneTag: process.env.CLOUDFLARE_ZONE_ID, zoneTag: MAIN.CLOUDFLARE_ZONE_ID,
filter: { filter: {
datetime_geq: new Date(Date.now() - (60 * 60 * 12 * 1000)).toISOString(), datetime_geq: new Date(Date.now() - (60 * 60 * 12 * 1000)).toISOString(),
// datetime_leq: new Date(Date.now() - (60 * 60 * 8 * 1000)).toISOString(), // datetime_leq: new Date(Date.now() - (60 * 60 * 8 * 1000)).toISOString(),

18
utils/headers.js Normal file
View file

@ -0,0 +1,18 @@
const { MAIN } = require('../config.js').CONFIG;
const { version, homepage } = require('../package.json');
const UserAgent = `Mozilla/5.0 (compatible; Cloudflare-WAF-To-AbuseIPDB/${version}; +${homepage})`;
const CLOUDFLARE = {
'User-Agent': UserAgent,
'Content-Type': 'application/json',
'Authorization': `Bearer ${MAIN.CLOUDFLARE_API_KEY}`,
};
const ABUSEIPDB = {
'User-Agent': UserAgent,
'Content-Type': 'application/json',
'Key': MAIN.ABUSEIPDB_API_KEY,
};
module.exports = { UserAgent, CLOUDFLARE, ABUSEIPDB };

10
utils/isLocalIP.js Normal file
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);
};