从 Cloudflare API 获取被 Cloudflare WAF 拦截的 IP 并提交给 AbuseIPDB

从 Cloudflare API 获取被 Cloudflare WAF 拦截的 IP 并提交给 AbuseIPDB

笔记本约 1.1 千字

Cloudflare 的 CTO 在今年(2019 年)九月 23 日发表了一篇博客「Cleaning up bad bots (and the climate)」推出「Bot Fight Mode」功能,但是试用后我发现这个功能聊胜于无,我利用 Cloudflare Firewall Rules 达成的效果远好于 Bot Fight Mode。在对抗 Bad Bot(恶意爬虫)的道路上,不如我也主动出击。

由于「隐私政策」于 2020 年 5 月 27 日更新,已经不再将被 Cloudflare WAF 拦截的 IP 提交给 AbuseIPDB。

「Bot Fight Mode」启用后 Cloudflare 会检查请求是否发自 Bad Bot(恶意爬虫)、通过消耗 Bad Bot 的 CPU 资源来对抗 Bad Bot,并会通过植树来补偿 Bad Bot 带来的碳排放;关于我的 Firewall Rules 可以查看我的开源项目 cloudflare-block-bad-bot-ruleset,在这个项目中我开源了我使用的其中非常小一部分的 Firewall Rules。

我采取的方案是:我编写的 WAF 规则远比 Cloudflare 的「Bot Fight Mode」严格许多,可以匹配到更多恶意爬虫(和无恶意的爬虫);通过 Cloudflare API 获取 Firewall Events 日志,获取所有被拦截的 IP,去重以后全部提交给 AbuseIPDB

首先是获取 Cloudflare 的 API Token。Cloudflare 最近终于支持 API Token 权限细分了,支持在不同服务、不同功能、读写操作等维度设置 API Token 的权限。使用新的 API Token 以后可以不需要使用 X-Auth-Mail X-Auth-Key 、可以直接使用 Authentication: Bearer [API Token] 这样的 HTTP Simple Auth 请求 Cloudflare API。

前往 Cloudflare Dashboard 的「My Profile」,在「API Token」面板点击「Create Token」创建一个新的 Token,名字随意、权限设置如下图所示:

cf-waf-to-abuseipdb-cf-token.png

需要包含的域名也根据需要设置,不过每个 API Token 的权限自然是越小越好,所以最好使用「Specific Zone」。

然后是前往 AbuseIPDB 创建 Key。如果没有账户,需要先注册一个。

接下来直接上代码。在处理 JSON 上,Node.js 明显比 Shell 方便多了:

const fetch = require('node-fetch');
const FormData = require('form-data');

const { env } = process;

// 从环境变量读 API Key 等配置
const cfApiKey = env.CF_API_KEY; // Cloudflare 的 API Token
const cfZoneId = env.CF_ZONE_ID; // Cloudflare 上指定域名的 Zone ID
const abipdbKey = env.ABUSEIPDB_KEY; // AbuseIPDB 的 Key

const now = new Date().getTime();

const since = new Date(now - 86400 * 1000).toISOString();
const until = new Date(now).toISOString();

let data = [];
let stat = 0;

function sortIP(data) {
    const input_ips = [];

    for (const { ip } of data) {
        input_ips.push(ip);
    }

    const result = [...new Set(input_ips)];

    return result;
}

function reportToAbuseIPDB(iplist) {
    function report(ip) {
        const form = new FormData();

        form.append('ip', ip);
        form.append('comment', 'The IP has triggered Cloudflare WAF. Report generated by Cloudflare-WAF-to-AbuseIPDB (https://github.com/SukkaW/Cloudflare-WAF-to-AbuseIPDB)');
        form.append('categories', '9,13,14,15,16,19,20,21');

        fetch('https://api.abuseipdb.com/api/v2/report', {
            method: 'post',
            body: form,
            headers: { 'Key': abipdbKey, 'Accept': 'application/json', ...form.getHeaders() }
        })
            .then(res => res.json())
            .then(({ data }) => { console.log(`${data.ipAddress} has abuse confidence score of ${data.abuseConfidenceScore}`); })
            .catch((err) => { console.error('The IP has already been reported or AbuseIPDB\'s Rate Limit has been met'); });
    }

    for (const ip of iplist) {
        report(ip);
    }
}

(function queryCfWAF(cfApiKey, cfZoneId, cursor) {
    if (stat === 0) console.log(`Querying Firewall Events from Cloudflare API V4...\n`);

    cursor = !cursor ? '' : `cursor=${cursor}`;
    fetch(`https://api.cloudflare.com/client/v4/zones/${cfZoneId}/security/events?limit=1000&since=${since}&until=${until}&${cursor}`, {
        headers: {
            'Authorization': `Bearer ${cfApiKey}`,
            'Content-Type': 'application/json'
        }
    }).then(res => res.json()).then(({ result, result_info }) => {
        data = [...data, ...result];
        stat = data.length;

        return [result_info, result[result.length - 1].occurred_at];
    }).then(([{ cursors }, occured_at]) => {
        const toEnd = new Date(occured_at).getTime() - new Date(since).getTime();
        if (toEnd < 0) {
            // To the End
            const iplist = sortIP(data);
            console.log(`----------------------------------------\n${iplist.length} IPs has been quried from Cloudflare API. Reporting to AbuseIPDB...`)
            reportToAbuseIPDB(iplist);
        } else {
            console.log(`${stat} events already queried from Cloudflare API.`)
            queryCfWAF(cfApiKey, cfZoneId, cursors.before);
        }
    });
})(cfApiKey, cfZoneId);

以上代码可以自动获取过去 24 小时内的 Cloudflare Firewall Events 并将 IP 提交到 Travis。设置好环境变量后,就可以运行 node index.js 了。

接下来是利用 Travis CI 实现自动化。在 Travis CI 上为项目设置环境变量(注意 API Key 等需要设置为 Secret),设置 Cron 以实现每天定时运行一次:

效果如下:

cf-waf-to-abuseipdb-log.png

如果你也想使用 Travis CI 自动将 Cloudflare 的 Firewall Events 中拦截的 IP 提交给 AbuseIPDB,只需要在 GitHub 上 Fork 我的 Cloudflare-WAF-to-AbuseIPDB 项目、在 Travis CI 中启用、配置相应的环境变量和定时运行 即可。

从 Cloudflare API 获取被 Cloudflare WAF 拦截的 IP 并提交给 AbuseIPDB
本文作者
Sukka
发布于
2019-11-19
许可协议
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!
如果你喜欢我的文章,或者我的文章有帮到你,可以考虑一下打赏作者
评论加载中...