Cloudflare-WAF-To-AbuseIPDB/index.js
2024-08-16 23:04:10 +02:00

150 lines
No EOL
6 KiB
JavaScript

require('dotenv').config();
const axios = require('axios');
const PAYLOAD = require('./scripts/payload.js');
const generateComment = require('./scripts/generateComment.js');
const isImageRequest = require('./scripts/isImageRequest.js');
const headers = require('./scripts/headers.js');
const { logToCSV, readReportedIPs } = require('./scripts/csv.js');
const formatDelay = require('./scripts/formatDelay.js');
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 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;
}
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)}`);
} else if (err.request) {
log('error', 'No response received from Cloudflare');
} else {
log('error', `Unknown error with api.cloudflare.com. ${err.message}`);
}
return null;
}
};
const reportIP = async (event, url, country, skippedRayIds, blockedIPs, cycleErrorCounts) => {
try {
await axios.post('https://api.abuseipdb.com/api/v2/report', {
ip: event.clientIP,
categories: '19',
comment: generateComment(event)
}, { headers: headers.ABUSEIPDB });
logToCSV(new Date(), event.rayName, event.clientIP, url, 'Reported', country);
log('info', `Reported: ${event.clientIP}; URL: ${url}`);
return true;
} catch (err) {
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);
log('warn', `Rate limited (429) while reporting: ${event.clientIP}; URL: ${url}; (Will retry after 5 hours)`);
cycleErrorCounts.blocked++;
} else {
log('error', `Error ${err.response.status} while reporting: ${event.clientIP}; URL: ${url}; Message: ${err.response.data.message}`);
cycleErrorCounts.otherErrors++;
}
} else if (err.request) {
log('error', `No response from AbuseIPDB while reporting: ${event.clientIP}; URL: ${url}`);
cycleErrorCounts.noResponse++;
} else {
log('error', `Unknown error: ${err.message} while reporting: ${event.clientIP}; URL: ${url}`);
cycleErrorCounts.otherErrors++;
}
return false;
}
};
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...');
const reportedIPs = readReportedIPs();
const skippedRayIds = new Set(reportedIPs.filter(ip => ip.action.startsWith('Skipped')).map(ip => ip.rayid));
const blockedIPs = new Map(reportedIPs.filter(ip => ip.action.includes('429')).map(ip => [ip.ip, ip.timestamp.getTime()]));
while (true) {
log('info', '===================== New Reporting Cycle =====================');
let cycleImageSkippedCount = 0, cycleProcessedCount = 0, cycleReportedCount = 0, cycleSkippedCount = 0;
const cycleErrorCounts = { blocked: 0, noResponse: 0, otherErrors: 0 };
const blockedIPEvents = await fetchBlockedIPs();
if (blockedIPEvents) {
for (const event of blockedIPEvents) {
cycleProcessedCount++;
if (skippedRayIds.has(event.rayName) || isIPBlockedRecently(event.clientIP, blockedIPs)) {
cycleSkippedCount++;
continue;
}
const url = `${event.clientRequestHTTPHost}${event.clientRequestPath}`;
const country = event.clientCountryName;
if (isImageRequest(event.clientRequestPath)) {
skippedRayIds.add(event.rayName);
logToCSV(new Date(), event.rayName, event.clientIP, url, 'Skipped - Image Request', country);
log('info', `Skipping: ${event.clientIP}; URL: ${url}; (Image request detected)`);
cycleImageSkippedCount++;
continue;
}
if (!exceptedRuleIds.has(event.ruleId) && shouldReportDomain(event.clientRequestHTTPHost, reportedIPs)) {
const wasReported = await reportIP(event, url, country, skippedRayIds, blockedIPs, cycleErrorCounts);
if (wasReported) {
cycleReportedCount++;
await new Promise(resolve => setTimeout(resolve, COOLDOWN_MS));
}
} else {
skippedRayIds.add(event.rayName);
logToCSV(new Date(), event.rayName, event.clientIP, url, 'Skipped - Already Reported', country);
log('info', `Skipping: ${event.clientIP} (domain ${event.clientRequestHTTPHost}); URL: ${url}; (Already reported recently)`);
cycleSkippedCount++;
}
}
}
// Podsumowanie cyklu
log('info', 'Cycle Summary:');
log('info', `- Total IPs processed: ${cycleProcessedCount}`);
log('info', `- Reported IPs: ${cycleReportedCount}`);
log('info', `- Skipped IPs: ${cycleSkippedCount}`);
log('info', `- Skipped due to Image Requests: ${cycleImageSkippedCount}`);
log('info', `- 429 Too Many Requests: ${cycleErrorCounts.blocked}`);
log('info', `- No response errors: ${cycleErrorCounts.noResponse}`);
log('info', `- Other errors: ${cycleErrorCounts.otherErrors}`);
log('info', '==================== End of Reporting Cycle ====================');
const delay = process.env.NODE_ENV === 'production' ? 2 * 60 * 60 * 1000 : 10 * 1000;
log('info', `Waiting ${formatDelay(delay)}...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
})();