尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

学习资料被“大肥鱼”批量抓走?Nginx 防盗链与四层防护实战

学习资料被“大肥鱼”批量抓走?Nginx 防盗链与四层防护实战 最近好几个同学跑来跟我说自己辛苦整理的 PDF 笔记、视频课程、题库资源突然被人批量下载后打包传播服务器日志里全是同一个客户端的访问记录带宽被打满甚至网站直接 502。这类事件的“主角”往往是自动化脚本圈子里常调侃它为“大肥鱼”——一条专盯着学习资料、批量捞走资源的“大鱼”。如果你也遇到过资源被人盗链、接口被疯狂刷、文件被批量拉取的场景那这篇文章正是为你准备的。我会从攻击者视角拆解再给出完整的防护落地方案包含 Nginx 防盗链、访问频率限制、接口鉴权、日志监控四个层面的实操配置以及常见问题的排查思路。无论你是个人站长还是团队里负责学习平台的后端开发都可以按这篇文章一步步把资源保护起来。1. “大肥鱼”在做什么学习资料被批量抓取的真相1.1 它是什么先给“大肥鱼”下一个通俗定义它不是某个特定软件而是一类恶意爬虫、批量下载脚本、盗链工具的统称。攻击者会把这些脚本伪装成正常浏览器绕过基础限制遍历你的学习资料链接并批量下载。常见行为包括遍历网站中的 PDF、MP4、ZIP 等静态资源 URL。伪造Referer或User-Agent绕过防盗链。并发请求下载接口抢占带宽和服务器连接数。抓取网页中的真实文件地址再分发到其他地方传播。1.2 为什么学习资料容易成为目标学习资料通常具备“高价值、可传播、无状态”三个特点。PDF 教程、视频课程、题库资源往往具有版权价值但服务器又很难像电商接口那样对每一次下载做强校验所以就成了批量抓取的重灾区。还有一个容易被忽略的点学习平台的文件 URL 往往是规则性的比如https://example.com/files/lesson/1001.pdf https://example.com/files/lesson/1002.pdf这种可预测的 URL 结构对脚本来说就像打开了一扇门攻击者只需要把数字递增就能把整个目录的资源拉走。1.3 攻击会带来哪些实际影响影响面具体表现带宽资源大量并发下载打满出口带宽正常用户访问变慢服务器性能连接数被占满Nginx 返回 502 或超时内容价值付费/内部资料被无偿传播收益受损数据安全如果 URL 参数包含用户信息可能存在越权风险所以保护学习资料不只是“防爬”更是保障服务稳定性、内容资产安全的重要工作。2. 环境准备与实验拓扑下面开始实操。为了演示完整链路我们使用一个常见环境操作系统LinuxCentOS 7 或 Ubuntu 20.04 均可Web 服务器Nginx 1.20后端服务Spring Boot 2.7 或 Node.js示例以 Spring Boot 为主数据库MySQL 5.7用于记录下载日志和用户权限文件存储本地目录或对象存储本实验以本地目录为例如果你本机环境版本不同不影响整体思路。核心配置思路比具体版本更重要。先建好实验目录结构/opt/learn-platform ├── html # 前端静态文件 ├── files # 学习资料目录 │ ├── lesson │ │ ├── 1001.pdf │ │ ├── 1002.pdf │ │ └── 1003.pdf ├── nginx │ └── conf.d │ └── learn.conf └── logs # 日志目录当前场景模拟的是我们有一个学习资料站资源存放在files/lesson目录用户可以通过 URL 直接访问文件。“大肥鱼”脚本正在抓取这些资源。3. 攻击原理拆解为什么简单的安全策略会被绕过3.1 基础防盗链的局限很多站点第一道防线是 Nginx 防盗链也就是校验Referer字段。配置类似location ~* \.(pdf|mp4|zip)$ { valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } }这段配置的作用是只有当请求来源是你的域名或者直接输入 URL 访问时才允许下载。然而问题在于Referer是 HTTP 请求头完全可以被脚本伪造。攻击者只需要在脚本里加上headers { Referer: https://example.com/, User-Agent: Mozilla/5.0 ... }这道防线就形同虚设。所以单纯依赖 Referer 防盗链只能防“小白”爬虫防不住“大肥鱼”。3.2 文件 URL 可猜测刚才已经提到1001.pdf、1002.pdf这类顺序编号的 URL 是最大的隐患。攻击者不需要知道你网站内部结构只要拿到一个合法 URL就能推断出批量下载规则。防护思路有两个方向让 URL 不可预测例如加入随机 Token 或签名。让 URL 有时效性过期即失效。3.3 并发请求没有限制如果不限制同一 IP 或同一用户的请求频率脚本就能同时开启几十个线程疯狂请求。Nginx 默认允许的连接数较高这会导致网络带宽被打满。后端服务线程池被打满。正常用户的请求被饿死。4. 第一层防护Nginx 防盗链与文件访问规则4.1 升级版防盗链配置先看一个比基础防盗链更严格一点的配置。我们把静态文件访问统一收敛到一个 location 中处理。# 文件路径/opt/learn-platform/nginx/conf.d/learn.conf server { listen 80; server_name learn.example.com; root /opt/learn-platform/html; index index.html; # 学习资料目录 location /files/ { alias /opt/learn-platform/files/; # 1. 校验 Referer valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } # 2. 只允许指定 HTTP 方法 limit_except GET { deny all; } # 3. 记录下载日志 access_log /opt/learn-platform/logs/download.log download; } }这里的download日志格式需要提前在nginx.conf或conf.d中定义log_format download $remote_addr [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent;配置完成后重载 Nginxnginx -t nginx -s reload用普通方式访问curl -I https://learn.example.com/files/lesson/1001.pdf如果没有带正确的Referer会返回403 Forbidden。4.2 使用签名 URL 替代明文 URLReferer 校验只能算“弱校验”。更强的方案是签名 URL在生成下载地址时加入过期时间和签名服务器验证通过后才允许访问。签名 URL 的生成规则示例原始 URL/files/lesson/1001.pdf 附加参数expire1735689600signmd5(file_1001_2024_example_secret)最终 URLhttps://learn.example.com/files/lesson/1001.pdf?expire1735689600signabc123...Nginx 侧可以使用secure_link_module模块来校验签名。在 Linux 下安装 Nginx 时添加--with-http_secure_link_module即可支持。location /files/ { alias /opt/learn-platform/files/; secure_link $arg_md5,$arg_expires; secure_link_md5 $secure_link_expires$uri learn_secret; if ($secure_link ) { return 403; } if ($secure_link 0) { return 410; } }后端生成签名链接时用相同规则计算 MD5// 文件路径src/main/java/com/example/learn/util/SignedUrlUtil.java import java.security.MessageDigest; public class SignedUrlUtil { private static final String SECRET learn_secret; public static String generateSignedUrl(String uri, long expireTime) throws Exception { String data expireTime uri SECRET; String md5 md5(data); return /files uri ?md5 md5 expires expireTime; } private static String md5(String data) throws Exception { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(data.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } }这里的关键点是expires参数控制链接有效期。md5参数根据 URL、过期时间和密钥生成攻击者无法伪造。加了签名后即使攻击者看到一条 URL也无法推断出其他 URL。4.3 隐藏真实目录结构除了签名 URL还可以把文件从 Web 根目录中移走改为通过后端接口流式返回。这样攻击者即使猜测 URL也无法直接访问到物理文件。例如前端下载链接为https://learn.example.com/api/download?fileId1001后端根据fileId查询文件路径再以流的方式返回// 文件路径src/main/java/com/example/learn/controller/DownloadController.java RestController RequestMapping(/api/download) public class DownloadController { GetMapping public ResponseEntityResource download(RequestParam Long fileId) { // 1. 根据 fileId 查询文件真实路径必须校验权限 String realPath /data/private/files/ fileId .pdf; // 2. 加载文件资源 Resource resource new FileSystemResource(realPath); if (!resource.exists()) { return ResponseEntity.notFound().build(); } // 3. 设置下载响应头 return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, attachment; filenamelesson.pdf) .contentType(MediaType.APPLICATION_PDF) .body(resource); } }这样做的好处是物理文件路径完全对外不可见所有下载请求都被纳入后端权限校验和日志记录。5. 第二层防护访问频率限制与并发控制5.1 使用 Nginx limit_req 限制请求速率签名 URL 解决了 URL 可猜测问题但攻击者如果拿到大量有效签名 URL仍然可以快速请求。所以我们需要限制单个 IP 的请求速率。Nginx 的limit_req模块可以按 IP 做漏桶限流。# 在 http 或 server 上下文定义限流规则 limit_req_zone $binary_remote_addr zonedownload_limit:10m rate5r/s; server { location /files/ { limit_req zonedownload_limit burst10 nodelay; # 其他配置... } }参数说明rate5r/s平均每秒最多处理 5 个请求。burst10允许瞬时最多 10 个请求排队。nodelay排队请求不延迟处理超过 burst 的请求直接返回 503。对于正常用户5r/s 完全够用对于批量下载脚本超过阈值会立刻被拦截。5.2 限制连接数除了请求速率还可以限制单 IP 并发连接数limit_conn_zone $binary_remote_addr zoneconn_limit:10m; server { location /files/ { limit_conn conn_limit 2; } }这样单个 IP 最多只能同时建立 2 个连接脚本的并行下载能力会被大幅削弱。5.3 后端接口限流如果是通过后端接口下载也要在业务层做限流。以 Spring Boot 为例可以使用简单的拦截器实现基于 IP 的限流// 文件路径src/main/java/com/example/learn/interceptor/RateLimitInterceptor.java Component public class RateLimitInterceptor implements HandlerInterceptor { private final CacheString, AtomicInteger counter Caffeine.newBuilder() .expireAfterWrite(Duration.ofSeconds(1)) .build(); Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String ip request.getRemoteAddr(); AtomicInteger count counter.get(ip, k - new AtomicInteger(0)); if (count.incrementAndGet() 10) { response.setStatus(429); return false; } return true; } }这里使用 Caffeine 本地缓存在一秒内最多允许 10 次请求。如果是分布式环境建议换成 Redis Lua 脚本实现更可靠的计数。6. 第三层防护身份鉴权与权限校验6.1 登录后才能下载学习资料不是公共资源正确的做法是要求用户登录后才能下载。登录后生成一个短期有效的 Token下载接口校验 Token 并记录用户行为。Spring Boot 中可以用拦截器校验 Token// 文件路径src/main/java/com/example/learn/interceptor/LoginInterceptor.java Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !TokenManager.valid(token)) { response.setStatus(401); return false; } return true; } }配置拦截规则只拦截下载相关接口// 文件路径src/main/java/com/example/learn/config/WebConfig.java Configuration public class WebConfig implements WebMvcConfigurer { Autowired private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/download/**); } }6.2 权限控制到单份资料除了登录还需要控制“这个用户能不能下载这份资料”。比如某些资料是 VIP 专享某些资料属于特定课程。这时下载接口必须在返回文件之前查询权限GetMapping public ResponseEntityResource download(RequestParam Long fileId, RequestParam Long userId) { // 查询用户是否有该文件权限 boolean hasPermission permissionService.check(userId, fileId); if (!hasPermission) { return ResponseEntity.status(403).build(); } // 继续执行下载... }这个查询一定不能省。不要“信任”前端传来的角色字段所有判断必须站在后端数据源基础之上。6.3 水印与用户标识如果资料是视频或 PDF可以在下载时为不同用户插入不同的水印。这样即使资料被泄露也能追踪到泄露源头。PDF 水印可以使用 iText 生成视频水印则需在转码时嵌入。水印不是防护手段但它是很强的威慑手段和溯源手段建议在资料价值较高时启用。7. 第四层防护监控、告警与日志分析7.1 分析访问日志前面配置了独立下载日志接下来要定期分析日志。下面这个命令可以快速统计下载量最高的 IPawk {print $1} /opt/learn-platform/logs/download.log | sort | uniq -c | sort -rn | head -20输出示例1200 192.168.1.10 800 192.168.1.11 10 203.0.113.5如果某个 IP 的请求量远远超过正常用户行为就应该进入观察名单。7.2 配置访问频率告警手动看日志比较被动我们可以写一个简单脚本定期扫描日志中频率异常高的 IP。# 文件路径/opt/learn-platform/scripts/alert_hot_ip.py import re from collections import Counter from datetime import datetime, timedelta LOG_FILE /opt/learn-platform/logs/download.log THRESHOLD 100 def analyze(): now datetime.now() counter Counter() with open(LOG_FILE, r, encodingutf-8) as f: for line in f: # 日志格式: $remote_addr [$time_local] $request ... match re.match(r(\d\.\d\.\d\.\d) \[([^\]])\], line) if not match: continue ip match.group(1) time_str match.group(2) log_time datetime.strptime(time_str, %d/%b/%Y:%H:%M:%S %z) # 只统计最近10分钟的日志 if log_time.replace(tzinfoNone) now - timedelta(minutes10): counter[ip] 1 for ip, count in counter.most_common(20): if count THRESHOLD: print(f[ALERT] {ip} requests: {count} in last 10 minutes) if __name__ __main__: analyze()这个脚本可以由 cron 每分钟执行一次*/1 * * * * /usr/bin/python3 /opt/learn-platform/scripts/alert_hot_ip.py /opt/learn-platform/logs/alert.log 217.3 封禁异常 IP确认某个 IP 存在恶意抓取行为后可以在 Nginx 层直接封禁。在conf.d/block.conf中添加deny 192.168.1.10; deny 192.168.1.11;重载 Nginx 后立即生效nginx -s reload更工程化的做法是把封禁逻辑做成接口或脚本动态写入 IP 黑名单文件再用nginx -s reload刷新。8. 常见问题与排查思路在实际搭建过程中最容易踩到下面这些坑。问题现象常见原因解决思路配置完成后正常用户也无法下载secure_link_md5计算规则不一致检查后端生成签名和 Nginx 校验的密钥、字段顺序、拼接方式是否完全一致加了limit_req后提示 503频率限制过于严格或burst太小调大burst或把rate从1r/s调整为5r/s视频文件无法拖动进度条Nginx 未配置 Range 支持确认没有在 location 中禁用 Range同时保证后端透传Accept-Ranges头仍能通过 IP 直连绕过站点DNS 解析和防火墙未限制在 server 中拒绝非本站域名的访问或使用防火墙只放行 80/443对象存储临时 URL 泄露签名 URL 有效期过长将 URL 有效期缩短到几分钟并限制 IP 绑定用户举报下载速度越来越慢可能是 IP 被封禁或带宽被恶意请求占满检查 Nginx 日志确认封禁策略是否误伤再补充一个容易被忽略的细节不要只对文件 URL 做防护还要对页面接口做防护。攻击者通常先抓取页面上所有链接找到真实文件地址后再批量下载。如果页面能访问文件也必须有校验。9. 最佳实践与工程建议9.1 文件存储与访问分离不要把学习资料直接放在 Web 服务目录下。建议架构文件存放到独立存储区例如/data/private/files。对外访问统一走后端下载接口。私密文件不生成永久公网 URL。这样即使 Nginx 配置出现漏洞攻击者也无法直接拼路径拿到文件。9.2 链接有效期要短签名 URL 的有效期建议控制在 5~30 分钟。学习资料不像安装包用户通常会在短时间内完成下载短有效期能把泄露风险降到最低。如果业务要求支持断点续传有效期的计算应从“首次请求时间”开始而不是“生成时间”否则用户下载一半链接失效体验会很差。9.3 权限校验放到后端很多前端项目为了省事直接把文件 URL 写在静态页面或接口返回里。这等于把钥匙挂在门上。文件下载必须走后端接口服务端确认用户身份、权限、频率后再返回文件流。9.4 日志不能只留“有没有”下载日志必须包含IP、时间、用户 ID、文件名、Referer、User-Agent、状态码、下载字节数。这些字段不仅是排查问题的依据也是今后做安全分析的基础数据。9.5 拒绝一切非常规请求在 Nginx 层直接处理# 禁止非 GET 请求下载文件 if ($request_method !~ ^GET$) { return 405; } # 拒绝包含可疑参数的请求 if ($query_string ~* \.\./|/etc/passwd|select|insert) { return 403; }注意Nginx 的if指令有一些历史坑要避免在location中写过于复杂的if逻辑。上面这种最简单的场景问题不大但复杂规则建议放到后端或使用 OpenResty 处理。9.6 应急响应预案再完备的防护也可能被攻破。建议提前准备一份应急预案发现异常 IP 后先限流再封禁。定期轮转签名密钥。出现大规模泄露时可以下架文件重新生成签名 URL。不要只封一个 IP要分析整个攻击特征比如相同的 User-Agent、相同的 Referer 模板。10. 结语与下一步学习方向围绕“大肥鱼”这类批量抓取行为我从四个层面梳理了完整的防护方案Nginx 防盗链与签名 URL、访问频率限制、后端鉴权、日志监控与告警。这套方案不依赖特定云厂商落到你自己的服务器上也能直接用。下一步你可以继续研究这些方向OpenResty 动态封禁通过 Lua 脚本在 Nginx 层实时处理恶意 IP效率比修改配置文件后 reload 更高。对象存储的私有读模式如果你使用云对象存储可以把文件设为私有读通过云服务商提供的临时凭证访问省去自己维护签名系统的成本。行为分析不只盯着单 IP而是结合 User-Agent、请求间隔、文件类型等多维度特征识别异常流量。验证码与设备指纹对下载量较大的场景增加人机校验能有效阻止脚本批量操作。最后提醒一句防护方案不是配完就不管了。日志要定期看告警要及时处理签名密钥要定期更换权限模型要跟着业务调整。安全是一个持续迭代的过程希望这篇文章能帮你把学习资料保护好不再被“大肥鱼”偷走。
返回列表