Update README.md

This commit is contained in:
Sefinek 2024-08-17 11:13:57 +02:00
parent a6e17b06ed
commit f1eccfbed5
5 changed files with 113 additions and 27 deletions

View file

@ -1 +1,91 @@
# Cloudflare WAF to AbuseIPDB
# ☁️ Cloudflare WAF to AbuseIPDB
This project is an automated script designed to fetch and report IP addresses that have triggered specific Cloudflare firewall events.
It enables reporting incidents detected by Cloudflare WAF to AbuseIPDB.
## 🛠️ Prerequisites
- Node.js
- npm (Node Package Manager)
## 📃 Information
If you want to make changes to the script from this repository, please kindly fork it first.
## 🌌 Example Report
![Sample Cloudflare WAF Report to AbuseIPDB](images/brave_lEvin0BcDcoK.png)
```
IP 192.42.116.183 [T1] triggered Cloudflare WAF (firewallCustom).
Action taken: CHALLENGE
ASN: 1101 (IP-EEND-AS IP-EEND BV)
HTTP protocol: HTTP/1.0 (method GET)
Domain: blocklist.sefinek.net
Endpoint: /
Timestamp: 2024-08-17T00:15:53Z
Ray ID: 8b4578e65f16b8e4
Rule ID: cc5e7a6277d447eca9c1818934ba65c8
User agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36
Report generated by Node-Cloudflare-WAF-AbuseIPDB (https://github.com/sefinek24/Node-Cloudflare-WAF-AbuseIPDB)
```
## 📥 Installation
1. Clone the repository.
```bash
git clone https://github.com/sefinek24/Node-Cloudflare-WAF-AbuseIPDB.git
```
2. Install dependencies.
```bash
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`!
4. Run the script.
```bash
node .
```
5. If you want to run the process 24/7, install the `PM2` module.
```bash
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. Run the process continuously using `PM2` to ensure constant operation and automatic restart in case of a failure.
```bash
pm2 start
```
8. Save a snapshot of the currently running `Node.js` processes.
```bash
pm2 save
```
9. Add `PM2` to startup.
```bash
pm2 startup
```
10. Execute the command generated by PM2, e.g.:
```bash
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.
## 🔤 How to Get Tokens?
### `CLOUDFLARE_ZONE_ID`
![](images/brave_UY5737SsDdlS.png)
### `CLOUDFLARE_API_KEY`
1. Go to [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens).
2. Click the "Create Token" button.
3. Select "Create Custom Token".
4. ![](images/brave_oWibgugvXlTH.png)
### `ABUSEIPDB_API_KEY`
Go to [www.abuseipdb.com/account/api](https://www.abuseipdb.com/account/api).
## 💕 Credits
This project is inspired by the [MHG-LAB/Cloudflare-WAF-to-AbuseIPDB](https://github.com/MHG-LAB/Cloudflare-WAF-to-AbuseIPDB) repository.
I'm not particularly fond of Python and usually try to avoid using this programming language, which is why I decided to create this repository.
## 📑 MIT License
Copyright 2024 © by [Sefinek](https://sefinek.net). All Rights Reserved.

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -1,5 +1,4 @@
require('dotenv').config();
const axios = require('axios');
const PAYLOAD = require('./scripts/payload.js');
const generateComment = require('./scripts/generateComment.js');
@ -12,31 +11,42 @@ const log = require('./scripts/log.js');
const TIME_WINDOW_MS = 20 * 60 * 1000;
const COOLDOWN_MS = 2000;
const BLOCK_TIME_MS = 5 * 60 * 60 * 1000; // 5h
const exceptedRuleIds = new Set(['fa01280809254f82978e827892db4e46']);
const fetchBlockedIPs = async () => {
try {
const response = await axios.post('https://api.cloudflare.com/client/v4/graphql', PAYLOAD, { headers: headers.CLOUDFLARE });
if (!response.data?.data) {
log('error', `Failed to retrieve data from Cloudflare (status ${response.status}). Missing permissions? Check your token. The required permission is Zone.Analytics.Read.`);
return null;
if (response.data?.data) {
const events = response.data.data.viewer.zones[0].firewallEventsAdaptive;
log('info', `Fetched ${events.length} events from Cloudflare`);
return events;
} else {
throw new Error(`Failed to retrieve data from Cloudflare (status ${response.status}). Missing permissions? Check your token. The required permission is Zone.Analytics.Read.`);
}
log('info', `Fetched ${response.data.data.viewer.zones[0].firewallEventsAdaptive.length} events from Cloudflare`);
return response.data.data.viewer.zones[0].firewallEventsAdaptive;
} catch (err) {
if (err.response) {
log('error', `${err.response.status} HTTP ERROR (api.cloudflare.com)\n${JSON.stringify(err.response.data, null, 2)}`);
log('error', `${err.response.status} HTTP ERROR (Cloudflare)\n${JSON.stringify(err.response.data, null, 2)}`);
} else if (err.request) {
log('error', 'No response received from Cloudflare');
} else {
log('error', `Unknown error with api.cloudflare.com. ${err.message}`);
log('error', `Unknown error with Cloudflare. ${err.message}`);
}
return null;
}
};
const reportIP = async (event, url, country, skippedRayIds, blockedIPs, cycleErrorCounts) => {
const shouldReportDomain = (domain, reportedIPs) => {
const lastReport = reportedIPs.find(entry => entry.domain === domain);
return !lastReport || (Date.now() - lastReport.timestamp.getTime()) > TIME_WINDOW_MS;
};
const isIPBlockedRecently = (ip, blockedIPs) => {
const lastBlockTime = blockedIPs.get(ip);
return lastBlockTime && (Date.now() - lastBlockTime) < BLOCK_TIME_MS;
};
const reportIP = async (event, url, country, blockedIPs, cycleErrorCounts) => {
try {
await axios.post('https://api.abuseipdb.com/api/v2/report', {
ip: event.clientIP,
@ -51,7 +61,7 @@ const reportIP = async (event, url, country, skippedRayIds, blockedIPs, cycleErr
if (err.response) {
if (err.response.status === 429) {
blockedIPs.set(event.clientIP, Date.now());
logToCSV(new Date(), event.rayName, event.clientIP, url, 'Blocked - 429 Too Many Requests', country);
logToCSV(new Date(), event.rayName, event.clientIP, url, 'Blocked - 429 Too Many Requests', event.clientCountryName);
log('warn', `Rate limited (429) while reporting: ${event.clientIP}; URL: ${url}; (Will retry after 5 hours)`);
cycleErrorCounts.blocked++;
} else {
@ -70,19 +80,6 @@ const reportIP = async (event, url, country, skippedRayIds, blockedIPs, cycleErr
}
};
const exceptedRuleIds = new Set(['fa01280809254f82978e827892db4e46']);
const shouldReportDomain = (domain, reportedIPs) => {
const lastReport = reportedIPs.find(entry => entry.domain === domain);
if (!lastReport) return true;
return (Date.now() - lastReport.timestamp.getTime()) > TIME_WINDOW_MS;
};
const isIPBlockedRecently = (ip, blockedIPs) => {
const lastBlockTime = blockedIPs.get(ip);
return lastBlockTime && (Date.now() - lastBlockTime) < BLOCK_TIME_MS;
};
(async () => {
log('info', 'Starting IP reporting process...');
@ -118,7 +115,7 @@ const isIPBlockedRecently = (ip, blockedIPs) => {
}
if (!exceptedRuleIds.has(event.ruleId) && shouldReportDomain(event.clientRequestHTTPHost, reportedIPs)) {
const wasReported = await reportIP(event, url, country, skippedRayIds, blockedIPs, cycleErrorCounts);
const wasReported = await reportIP(event, url, country, blockedIPs, cycleErrorCounts);
if (wasReported) {
cycleReportedCount++;
await new Promise(resolve => setTimeout(resolve, COOLDOWN_MS));
@ -132,7 +129,6 @@ const isIPBlockedRecently = (ip, blockedIPs) => {
}
}
// Podsumowanie cyklu
log('info', 'Cycle Summary:');
log('info', `- Total IPs processed: ${cycleProcessedCount}`);
log('info', `- Reported IPs: ${cycleReportedCount}`);