r2图床防止被刷
最近刚白嫖了cf的r2存储桶作为图床,但是r2存储计费无上限,也就是说存在被刷爆的风险。所以采取以下措施,防止被刷爆。
缓存
利用Cloudflare的页面规则和Cache Rules 功能,为R2域名添加缓存。
接入worker
统计整个网站 1 秒内的总请求数(全部 IP 所有路径)
如果总请求数超过 10,则返回错误页面或自定义响应
js代码
本脚本实现了两个核心功能:
- 请求限流:对所有进入的请求进行秒级限流,限制每秒最大请求数,防止过载。
- 静态资源读取及缓存:仅允许 GET 请求访问,优先从边缘缓存读取资源,缓存未命中时从 Cloudflare R2 存储桶获取资源,并写入边缘缓存,提升访问性能。
主要流程
- 请求限流
- 以当前时间秒级时间戳作为限流键(
count:<timestamp>)存储在 KV 命名空间GLOBAL_REQUEST_COUNT中。- 获取当前秒已处理请求数,超过设定的最大阈值(默认 50)时,返回 HTTP 429 限流响应。
- 否则,异步将当前秒请求计数加一,并设置 5 秒过期,避免 KV 积压。
- 请求方法校验
- 仅允许 GET 请求访问资源,其他请求返回 HTTP 405(Method Not Allowed)。
- 缓存与资源读取
- 构造忽略查询参数的缓存键
cacheKey,尝试从边缘缓存caches.default读取资源。- 若缓存命中,则异步刷新缓存 TTL,直接返回缓存内容。
- 缓存未命中时,从 R2 存储桶
MY_BUCKET读取对应路径资源。- 如果资源不存在,返回 404。
- 若资源存在,构建响应,附带
ETag和Cache-Control头,实现浏览器端长时间缓存(1 年,immutable)。- 异步将响应写入边缘缓存,供后续请求使用。
环境变量与绑定
GLOBAL_REQUEST_COUNTKV 命名空间,用于存储秒级请求计数。请在 Worker 绑定此 KV。MY_BUCKETCloudflare R2 存储桶绑定,用于读取静态资源。MAX_REQUESTS_PER_SECOND(可选) 限流阈值,默认 50。如果需要更改每秒最大请求数,可以在 Worker 环境变量中设置该值。
export default {
async fetch(request, env, ctx) {
// 1. 限流逻辑(秒级请求数)
const now = Math.floor(Date.now() / 1000);
const limitKey = `count:${now}`;
let count = await env.GLOBAL_REQUEST_COUNT.get(limitKey);
count = parseInt(count || "0", 10);
const MAX = parseInt(env.MAX_REQUESTS_PER_SECOND || "50", 10);
if (count >= MAX) {
return new Response("Site is temporarily rate limited. Please try again later.", {
status: 429,
headers: { "Content-Type": "text/plain" },
});
}
// 异步递增计数,设置 5 秒过期避免积压
ctx.waitUntil(
env.GLOBAL_REQUEST_COUNT.put(limitKey, (count + 1).toString(), {
expirationTtl: 5,
})
);
// 2. 只允许 GET 请求
if (request.method !== "GET") {
return new Response("Method Not Allowed", {
status: 405,
headers: { Allow: "GET" },
});
}
// 3. 处理缓存和 R2 读取
const url = new URL(request.url);
const key = url.pathname.slice(1); // 去除开头 "/"
// 缓存键,忽略 query 参数
const cacheKey = new Request(`https://${url.hostname}${url.pathname}`, request);
// 尝试边缘缓存命中
let response = await caches.default.match(cacheKey);
if (response) {
// 异步续命缓存
ctx.waitUntil(caches.default.put(cacheKey, response.clone()));
return response;
}
// 缓存未命中,从 R2 读取
const object = await env.MY_BUCKET.get(key);
if (object === null) {
return new Response("404 Not Found", { status: 404 });
}
// 设置响应头,包含 etag 和缓存控制
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
headers.set("Cache-Control", "public, max-age=31536000, immutable");
response = new Response(object.body, { headers });
// 写入边缘缓存
ctx.waitUntil(caches.default.put(cacheKey, response.clone()));
return response;
}
};
添加环境变量
控制台设置方法(如果不用 wrangler)
- 打开 Cloudflare → Workers → 你的脚本
- 选择“Settings” → “Environment Variables”
- 添加一个变量:
- Name:
MAX_REQUESTS_PER_SECOND - Value:
50
- Name:

连接R2存储桶和KV空间

配置页面规则

因为我的资源都放在r2.****.***下,所以我配置的域名是r2.****.***/*,缓存基本都拉到最大值,经过测试开启后确实也是从本地读取资源了。
配置Cache Rules



防盗链
配置防盗链,防止其他网站盗取博客图片。

当不包含
博客链接引用方去访问我的 https://imgurl.780789.xyz/ 时,会进行阻止。
开启速率限制
针对单ip限速,每个ip限速到50次/10秒,如果超过这个频率就会被阻止10秒。


waf规则配置
白名单
可以放行常用的代理ip,方便调试。
(ip.src eq x.x.x.x) or (ip.src eq x.x.x.x) or (ip.src eq x.x.x.x) or (ip.src eq x.x.x.x) or (ip.src in $vps_ip)
黑名单
- 防盗链(限制指定域名请求必须来自特定 Referer)
- 屏蔽已知爬虫和漏洞扫描工具(通过 User-Agent、请求头特征识别)
- 拦截异常请求(无User-Agent、无Accept-Encoding、无Accept-Language、空Referer)
- 黑名单IP和ASN拦截
- 屏蔽恶意或异常的请求特征
(http.request.full_uri contains "https:/图床域名.xyz" and not http.referer contains "博客域名l") or (http.host wildcard "图床域名.") or (cf.client.bot) or (http.request.full_uri wildcard r"图床域名." and not http.referer contains "博客域名l") or (http.user_agent eq "") or (http.user_agent contains "Acunetix") or (not len(http.request.headers["accept-encoding"]) > 0) or (not len(http.request.headers["accept-language"]) > 0) or (http.user_agent contains "Acunetix") or (http.user_agent contains "apache") or (http.user_agent contains "attachment") or (http.user_agent contains "BackDoorBot") or (http.user_agent contains "cobion") or (http.user_agent contains "fhscan") or (http.user_agent contains "fimap") or (http.user_agent contains "Gscan") or (http.user_agent contains "hey/") or (http.user_agent contains "Joomla") or (http.user_agent contains "libweb") or (http.user_agent contains "libwww") or (http.user_agent contains "masscan") or (http.user_agent contains "nmap") or (http.user_agent contains "Page" and http.user_agent contains "Analyzer") or (http.user_agent contains "PHPCrawl") or (http.user_agent contains "PyCurl") or (http.user_agent contains "python") or (http.user_agent contains "RankActive") or (http.user_agent contains "RankingBot") or (http.user_agent contains "RankurBot") or (http.user_agent contains "researchscan") or (http.user_agent contains "ScanAlert") or (http.user_agent contains "scanbot") or (http.user_agent contains "virusdie") or (http.user_agent contains "VoidEYE") or (http.user_agent contains "WebShag") or (http.user_agent contains "Wprecon") or (http.user_agent contains "WPScan") or (http.user_agent contains "wrk") or (http.user_agent contains "ZmEu") or (http.user_agent contains "ZumBot") or (http.user_agent contains "Zyborg") or (http.user_agent contains "Zeus") or (http.user_agent contains "zgrab") or (http.user_agent contains "zmap") or (http.user_agent contains "lient" and http.user_agent contains "ttp") or (http.user_agent contains "fuck") or (http.user_agent eq "undefined") or (http.user_agent eq "") or (any(http.request.headers["checkmode"][*] contains "fast")) or (len(http.request.headers["checkmode"]) > 0) or (ip.src.asnum in {8075 64267 63949 60068 54252 51167 47583 45102 44477 397630 396982 36352 36351 31898 30058 24940 213230 212329 203020 202561 200373 16509 16276 14061 135377 132203 212329}) or (http.user_agent contains "netcraft") or (ip.src in {194.52.68.0/24 194.72.238.0/24 83.138.182.72/29 83.138.189.96/29 81.91.240.0/24 89.36.24.0/24 83.222.232.216/30 184.172.0.0/16}) or (http.referer eq "") or (http.request.version in {"HTTP/1.0" "HTTP/1.1"})
js质询
- 屏蔽来自指定ASN和国家的IP访问(黑名单策略)
- 识别异常或伪造请求头,如头部内容异常、伪造代理信息等
- 区分正常浏览器和爬虫、机器人,通过 User-Agent 和请求头字段组合
- 限制非法请求方法,只允许常用安全的 HTTP 方法
- 监控CDN相关头部及循环转发情况,防止恶意流量绕过防护
(ip.geoip.asnum in {174 195 209 577 792 793 794 1215 1216 1217 2497 2914 3223 3255 3269 3326 3329 3457 3462 3598 4184 4190 4637 4694 4755 4785 4788 4816 4826 4835 5056 5610 5617 6471 6584 6830 6876 6877 6939 7029 7224 7303 7489 7552 7684 8068 8069 8070 8071 8074 8075 8100 8220 8560 8881 8987 9009 9299 9312 9370 9534 9678 9952 9984 10026 10453 11351 11426 11691 12076 12271 12334 12367 12874 12876 12989 14061 14117 14140 14576 14618 15169 16276 16509 16591 16629 17043 17428 17707 17788 17789 17790 17791 18013 18228 18403 18450 18599 18734 18978 19527 19740 20207 20473 20552 20554 20860 21704 21769 21859 21887 22773 22884 23468 23724 23885 23959 23969 24088 24192 24424 24429 24940 25429 25697 25820 25935 25961 26160 26496 26818 27715 28429 28431 28438 28725 29066 29286 29287 29802 30083 30823 31122 31235 31400 31898 32097 32098 32505 32613 34081 34248 34549 34947 35070 35212 35320 35540 35593 35804 35816 35908 35916 36351 36352 36384 36385 36444 36492 36806 37963 37969 38001 38197 38283 38365 38538 38587 38588 38627 39284 40065 40676 40788 41009 41096 41264 41378 42652 42905 43289 43624 43989 45011 45012 45062 45076 45085 45090 45102 45102 45102 45103 45104 45139 45458 45566 45576 45629 45753 45899 45932 46484 46844 47232 47285 47927 48024 48024 48337 48905 49327 49588 49981 50297 50340 50837 51852 52000 52228 52341 53089 54463 54538 54574 54600 54854 54994 55158 55330 55720 55799 55924 55933 55960 55967 55990 55992 56005 56011 56109 56222 57613 58073 58199 58461 58466 58519 58543 58563 58593 58772 58773 58774 58775 58776 58844 58854 58862 58879 59019 59028 59048 59050 59051 59052 59053 59054 59055 59067 59077 59374 60068 60592 60631 60798 61112 61154 61317 61348 61577 61853 62044 62240 62468 62785 62904 63018 63023 63075 63288 63314 63545 63612 63620 63631 63655 63677 63678 63679 63727 63728 63729 63835 63838 63888 63916 63949 64050 131090 131106 131138 131139 131140 131141 131293 131428 131444 131477 131486 131495 132196 132203 132509 132510 132513 132591 132839 133024 133199 133380 133478 133492 133746 133752 133774 133775 133776 133905 133929 134238 134327 134760 134761 134763 134764 134769 134770 134771 134835 134963 135061 135290 135300 135330 135377 135629 137693 137697 137699 137753 137784 137785 137787 137788 137876 137969 138366 138407 138607 138915 138949 138950 138952 138982 138994 139007 139018 139124 139144 139201 139203 139220 139316 139327 139726 139887 140096 140596 140701 140716 140717 140720 140723 140979 141157 141180 142570 146817 149167 177453 177549 197099 197540 198047 198651 199490 199506 199524 199883 200756 201094 201978 202053 202675 203087 204601 204720 206092 206204 206791 206798 207319 207400 207590 208425 208556 211914 212708 213251 213375 262187 263022 263196 263639 263693 264344 264509 265443 265537 266706 267784 269939 270110 328608 394699 395003 395936 395954 395973 398101}) or (ip.src.country in {"AR" "BD" "BR" "CO" "CZ" "IN" "ID" "IR" "LR" "LY" "NL" "PH" "RU" "TR" "UA" "VN" "T1" "PR"}) or (any(http.request.headers["accept-encoding"][*] eq "identity")) or (any(http.request.headers["x-requested-with"][*] eq "XMLHttpRequest")) or (len(http.request.headers["x-cache"]) > 0) or (len(http.request.headers["cdn-loopcount"]) > 0) or (len(http.request.headers["trailer"]) > 0 and not http.user_agent contains "Firefox") or (len(http.request.headers["x-frame-options"]) > 0) or (len(http.request.headers["accept-charset"]) > 0) or (http.user_agent contains "Firefox" and len(http.request.headers["sec-ch-ua"]) > 0) or (any(http.request.headers["accept-encoding"][*] eq "x-real-ip")) or (any(http.request.headers["accept-encoding"][*] eq "x-forwarded-for")) or (any(http.request.headers["accept-encoding"][*] eq "x-forwarded-host")) or (any(http.request.headers["accept-encoding"][*] eq "cloudfront-viewer-address")) or (any(http.request.headers["accept-encoding"][*] eq "ali-cdn-real-ip")) or (http.request.version eq "HTTP/1.1") or (not http.request.method in {"GET" "POST" "OPTIONS" "HEAD"})
验证与自查
- 连续快速请求图片超过
MAX_REQUESTS_PER_SECOND(默认 50),返回 429 限流响应 - 非 GET 方法请求(如 POST)返回 405
- 浏览器访问图片时响应头带
Cache-Control: public, max-age=31536000, immutable,二次访问命中边缘缓存 - 在 Cloudflare 分析面板确认 WAF/速率限制规则有拦截记录,爬虫 UA(如
zgrab、masscan)被拦截