UA(User-Agent)是客户端向服务器发起请求时携带的一段身份标识字符串,它告诉服务器当前请求来自什么设备、什么浏览器、什么操作系统。攻击者、恶意爬虫和自动化扫描工具往往不会使用正常的浏览器UA,它们要么直接空着,要么使用各种脚本语言默认的库标识,要么伪造一些过时的、不完整的UA串。CDN边缘函数让我们有能力在请求到达源站之前,在最靠近用户的边缘节点上就对这些异常UA进行识别和拦截,把恶意流量挡在门外,既能减轻源站压力,又能提升整体安全水位。

为什么要在边缘侧处理UA黑名单

传统做法是在源站服务器上通过Nginx、Apache配置或者应用层代码来过滤UA,但这种方式存在明显缺陷。恶意请求已经穿过了整个CDN网络到达源站,消耗了带宽和连接资源,如果请求量巨大,源站可能在过滤逻辑生效前就被打满。边缘函数运行在CDN的每一个边缘节点上,可以在请求进入网络的第一跳就做出判断,直接返回403、444或者自定义拦截页面,源站甚至感知不到这些请求的存在。这种架构上的优势意味着更低的响应延迟、更少的资源浪费,以及更精准的拦截效果。

常见的异常UA特征

在实际运维中,我们会遇到几类典型的异常UA。第一类是空UA,请求头里根本没有User-Agent字段,正常的浏览器绝对不会出现这种情况,只有脚本、扫描器或者配置错误才会。第二类是极其简短或者格式异常的字符串,比如只有一个“curl/7.54.0”或者“python-requests/2.25.1”,这些是命令行工具和编程语言HTTP库的默认标识,正常用户不会用这些东西访问网页。第三类是包含明显恶意关键词的UA,比如“sqlmap”、“nikto”、“nmap”、“masscan”等知名安全工具的名称。第四类是伪造但伪造水平低劣的UA,比如声称自己是Chrome但版本号是十几年前的,或者操作系统和浏览器组合根本不可能存在。第五类是过长的UA串,有些攻击工具会构造超长的UA来触发缓冲区溢出或者WAF绕过,正常浏览器UA长度一般不会超过两三百个字符。

边缘函数实现UA检测的基本思路

在CDN边缘函数中,我们可以在请求处理的生命周期早期插入一段逻辑:获取请求头中的User-Agent字段,如果不存在则直接判定为异常;如果存在,则将其与预设的规则集进行匹配。规则集可以包括精确匹配、前缀匹配、正则匹配等多种模式。匹配到黑名单规则后,函数直接返回一个预设的HTTP状态码和响应体,不再继续回源。整个判断过程在亚毫秒级别完成,对正常用户完全透明。

编写边缘函数代码示例

以下是一段典型的CDN边缘函数代码,演示了如何实现UA黑名单的检测与拦截。这段代码的逻辑非常清晰:先提取UA,再依次检查是否为空、是否命中黑名单关键词、是否匹配恶意正则,任一条件触发则直接返回403拦截页面。

async function handleRequest(request) {
  const ua = request.headers.get('User-Agent') || '';
  
  // 规则1:空UA直接拦截
  if (ua.trim() === '') {
    return new Response('Access Denied: Empty User-Agent', { status: 403 });
  }
  
  // 规则2:黑名单关键词列表,精确包含匹配
  const blacklistKeywords = [
    'sqlmap', 'nikto', 'nmap', 'masscan', 'acunetix',
    'burpsuite', 'w3af', 'zap', 'openvas', 'nessus',
    'curl', 'wget', 'python-requests', 'go-http-client',
    'axios', 'node-fetch', 'okhttp', 'insomnia'
  ];
  
  const lowerUA = ua.toLowerCase();
  for (const keyword of blacklistKeywords) {
    if (lowerUA.includes(keyword)) {
      return new Response('Access Denied: Forbidden User-Agent', { status: 403 });
    }
  }
  
  // 规则3:正则匹配异常模式
  const dangerousPatterns = [
    /^[a-zA-Z0-9._-]+$/,  // 纯字母数字符号,没有空格和括号,典型的脚本UA
    /^.{0,10}$/,          // 长度小于10的极短UA
    /.{500,}/             // 长度超过500的超长UA
  ];
  
  for (const pattern of dangerousPatterns) {
    if (pattern.test(ua)) {
      return new Response('Access Denied: Suspicious User-Agent Pattern', { status: 403 });
    }
  }
  
  // 规则4:检查是否伪装成浏览器但格式严重错误
  // 正常浏览器UA至少包含Mozilla/和括号中的系统信息
  if (ua.includes('Mozilla') && !ua.includes('(')) {
    return new Response('Access Denied: Malformed User-Agent', { status: 403 });
  }
  
  // 通过所有检查,继续正常回源
  return fetch(request);
}

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

这段代码展示了四种层次的检测逻辑。第一层是空UA检测,最简单也最有效。第二层是关键词黑名单,覆盖了主流的安全扫描工具和自动化脚本库。第三层使用正则表达式来识别那些不符合正常浏览器UA格式特征的请求,比如纯字母数字组成的字符串、极短或极长的UA。第四层是一个简单的格式校验,如果声称自己是Mozilla系浏览器但缺少括号中的系统信息,基本可以判定为伪造。这些规则可以根据自己的业务场景灵活增减。

白名单机制的补充价值

黑名单机制虽然直接有效,但存在天然的滞后性,攻击者随时可以更换UA绕过黑名单。更稳健的策略是结合白名单机制:只允许符合正常浏览器UA格式的请求通过,其余一律拦截。正常浏览器UA有一个相对固定的格式规范,大致是“Mozilla/版本号 (系统信息) 引擎信息 浏览器品牌/版本号”。我们可以编写正则表达式来校验UA是否符合这个基本骨架,不符合的直接拒绝。这种做法的误杀风险需要仔细评估,比如一些合法的RSS阅读器、API客户端、健康检查探针可能会被误拦,需要为它们单独设置例外规则。

动态规则更新与远程配置

把黑名单写死在边缘函数代码里不是长久之计。每当需要新增或删除一个关键词,都要修改代码、测试、部署,运维成本很高。更合理的做法是让边缘函数在启动时或者定期从一个远程配置中心拉取最新的规则列表,存储在内存中供每次请求使用。这个配置中心可以是一个对象存储上的JSON文件,也可以是一个键值数据库。边缘函数在每次处理请求时读取内存中的规则集,性能几乎不受影响,而规则更新只需要修改远程配置文件即可实时生效。这种架构让安全策略的调整变得极其灵活,发现新的恶意UA特征后几分钟内就能全网生效。

处理误杀与日志记录

任何自动化的拦截策略都可能误杀正常用户。为了快速发现和纠正误杀,边缘函数在拦截请求时应该记录详细的日志,至少包括被拦截的UA原文、请求的URL路径、客户端IP、拦截触发的具体规则编号以及时间戳。这些日志可以输出到CDN厂商提供的日志服务中,也可以回调到自己的日志收集系统。通过分析拦截日志,我们能发现哪些正常服务或用户被误伤了,然后针对性地调整规则。比如某个企业内部系统使用Python脚本调用接口,UA中带有“python-requests”,我们可以在黑名单中移除这个关键词,或者结合IP白名单放行。

与WAF和频率限制的协同

UA屏蔽只是安全防护体系中的一环,它应该与WAF规则、IP频率限制、Bot管理等机制协同工作。边缘函数可以同时承担多种职责:先检查UA,再检查请求频率是否超过阈值,最后把干净的请求交给WAF做深度检测。多层防护叠加起来,能有效抵御各种类型的自动化攻击。比如一个扫描器可能伪造了正常的浏览器UA绕过了UA检测,但它在短时间内发起了大量请求,频率限制模块就能识别并拦截它。反过来,一个低频但UA明显异常的请求,频率限制可能抓不到,但UA黑名单能直接挡掉。

针对移动端和非浏览器客户端的考量

移动应用的HTTP请求通常使用系统自带的网络库,它们的UA格式和浏览器不一样。比如iOS应用的UA可能包含“CFNetwork”和“Darwin”,Android应用可能包含“Dalvik”或“Art”。如果业务有移动端原生应用访问,UA白名单规则必须兼容这些合法的非浏览器UA格式。同样,一些合作伙伴的API回调、Webhook请求可能使用特定的HTTP客户端库,UA也会偏离浏览器格式。在制定规则之前,应该先分析现有正常流量的UA分布,把合法的非浏览器UA纳入白名单,避免一刀切导致业务中断。

边缘函数性能优化细节

UA检测逻辑在每次请求时都会执行,因此代码效率至关重要。几个优化要点:第一,把命中率最高的规则放在最前面,比如空UA检测应该最先执行,因为大量恶意请求连UA都不带。第二,使用简单的字符串包含判断而不是复杂的正则表达式,字符串的indexOf或includes操作比正则快得多,关键词黑名单应该优先用这种方式。第三,正则表达式对象应该在函数初始化阶段编译好并复用,不要在每次请求处理时重新创建RegExp实例。第四,避免在检测逻辑中进行网络调用或异步IO操作,这会显著增加延迟。第五,合理使用短路逻辑,一旦命中某条规则就立即返回,不再执行后续检查。

持续运营与规则迭代

安全对抗是一个持续的过程,攻击者的UA特征在不断变化。建议建立一套规则迭代机制:定期审查拦截日志,分析被拦截请求的UA分布,提取新的恶意特征加入黑名单;同时关注误拦报告,及时移除或调整过于激进的规则。可以每周进行一次规则回顾,每月进行一次全面的UA流量分析。如果团队有安全运营人员,可以把这项工作纳入日常安全运营流程。没有专门安全团队的小团队,也可以依赖社区共享的威胁情报来更新黑名单,很多安全社区会定期发布最新的恶意UA特征库。

CDN边缘函数屏蔽异常UA是一项投入产出比极高的安全措施。它实现简单、性能开销极小、拦截效果立竿见影,能有效过滤掉大量低水平的自动化攻击和扫描流量。配合白名单校验、动态规则更新、日志监控和与其他安全模块的联动,可以构建起一道坚实的边缘防线。关键在于规则要贴合自身业务实际,既不能过于宽松导致漏网,也不能过于严格造成误伤,在持续运营中找到最佳平衡点。