diff --git a/README.md b/README.md index f915876..7393847 100644 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ -# Cloudflare WAF to AbuseIPDB \ No newline at end of file +# ☁️ 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. That’s 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. \ No newline at end of file diff --git a/images/brave_UY5737SsDdlS.png b/images/brave_UY5737SsDdlS.png new file mode 100644 index 0000000..71ce88b Binary files /dev/null and b/images/brave_UY5737SsDdlS.png differ diff --git a/images/brave_lEvin0BcDcoK.png b/images/brave_lEvin0BcDcoK.png new file mode 100644 index 0000000..eac6c62 Binary files /dev/null and b/images/brave_lEvin0BcDcoK.png differ diff --git a/images/brave_oWibgugvXlTH.png b/images/brave_oWibgugvXlTH.png new file mode 100644 index 0000000..583c6c8 Binary files /dev/null and b/images/brave_oWibgugvXlTH.png differ diff --git a/index.js b/index.js index c7cca2c..c1ad1dd 100644 --- a/index.js +++ b/index.js @@ -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}`);