1.3.0
This commit is contained in:
parent
a0eb452044
commit
73c8a834d3
22 changed files with 214 additions and 212 deletions
42
.env.default
42
.env.default
|
|
@ -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
2
.gitignore
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
node_modules
|
node_modules
|
||||||
.env
|
config.js
|
||||||
reported_ips.csv
|
reported_ips.csv
|
||||||
28
README.md
28
README.md
|
|
@ -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. That’s it! Monitor logs using the `pm2 logs` command.
|
12. That’s 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
64
config.default.js
Normal 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 (:
|
||||||
|
};
|
||||||
46
index.js
46
index.js
|
|
@ -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
35
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
|
|
@ -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.
|
|
||||||
* I’d really appreciate it (:
|
|
||||||
*/
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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}`);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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
41
services/fetchServerIP.js
Normal 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);
|
||||||
|
|
@ -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
18
utils/headers.js
Normal 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
10
utils/isLocalIP.js
Normal 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);
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue