Contents
Preface
我从几周前开始就遇到连绵不断的攻击,关键这些攻击还不是以前那种毫无技术含量的http flood攻击,而是基于wordpress的一个防守薄弱点:ajax功能入口(/wp-admin/admin-ajax.php
)发起的缓速率攻击(1秒钟几个到十多个):
刚好避过我cloudflare上设置的全局速率限制,而且攻击IP的范围变化较多,之前是国外,以俄罗斯为主,最近几天换成国内的IP了:
这其中,威胁最大的是来自俄罗斯的攻击,因为其似乎可以伪装成yandex搜索引擎的蜘蛛,从而通过cloudflare WAF规则中”已知自动程序的”的筛选,直接打进我内网(甚至感觉可以穿透cloudflare的”5秒盾”,因为有段时间我尝试专门针对这个路径设置了托管质询,但是俄罗斯来的攻击依然打到了内网WAF上,其他的则拦截了下了,不过这点我没空专门进行验证,所以也不能完全确认),搞得我有几天看到博客巨量的访问数据一头雾水,下图红框中的请求仅仅是26号0点到早上8点内网WAF上看到的博客请求数据:
因此,还逼我专门改了cloudflare WAF的跳过策略:
由于我的三层防御体系的健壮性(外层:cloudflare WAF之Free版,中层:长亭雷池WAF之社区版,内层:wordpress安全插件wordfence之免费版),这些攻击并没有对我的博客访问造成实质上的影响,但是却严重影响了我统计数据的准确性(强迫症患者看着长亭雷池WAF上博客站点的巨量请求真心别扭),本来我这周的文章没想过要写这个,但是因为被这种攻击弄得不胜其烦,就像是苍蝇老是在耳边飞来飞去,所以决定要着手解决这个问题了!
附加知识:wordpress的admin-ajax.php
首先来谈谈wordpress的admin-ajax.php(完整路径是/wp-admin/admin-ajax.php
).
admin-ajax.php是WordPress的AJAX功能入口:WordPress 中的很多动态功能(包括评论验证、点赞、回复加载等)都是使用 AJAX 请求实现,而这些请求通常都是通过 /wp-admin/admin-ajax.php 路径完成(关于AJAX技术的科普知识可以参看我的另一篇文章:Home Data Center Series: A review of the core concepts of current Web development starting with the Ajax cross-domain issue (from a non-programmer's perspective)).
以wordpress上用户发表评论的行为为例,简要介绍一下AJAX 请求的工作流程。
- 用户操作触发 AJAX 请求:
用户在评论表单中输入评论并点击提交按钮后,前端 JavaScript 会通过 XMLHttpRequest 或 fetch 发起一个后台请求,通常指向 /wp-admin/admin-ajax.php。
- 浏览器发起 HTTP 请求:
浏览器会按照 JavaScript 的指令,将评论数据封装成 HTTP 请求并发送到服务器(在此情况下是 admin-ajax.php)。
- 请求特点:
• 请求头会携带必要的信息(如 Cookies、Referer 等),以便服务器识别用户状态。
• 它们通常是 POST 请求,用于提交数据(如评论内容)。
本来,如果不直接通过对外提供访问的域名来管理wordpress(这是我认为安全运维wordpress的方式:对外提供访问的地址和平时管理wordpress用的地址进行分离,从而方便WAF策略的设置),同时也不需要评论功能的话,那么最简单粗暴的办法是直接在cloudflare WAF上将所有以/wp-admin
开头的访问请求直接阻止,不过我虽然不通过这个域名进行管理,评论功能却是需要的,所以不能这么粗暴来解决。
那么,不阻止的话,能不能用cloudflare的WAF使用质询的方式来处理对admin-ajax.php的访问请求呢?也不行,因为Cloudflare会对每个请求进行额外验证(如浏览器指纹分析、行为验证等),如果验证未通过,请求会被拦截,这对ajax请求来说是致命的,因为这些请求通常是由前端脚本自动发起,并不是完整的浏览器交互行为,所以不会响应质询(我开始没注意,结果设置后有朋友来告诉我不能发表评论了,我才意识到这个问题,然后发现翻译功能也没了~)。
一般而言,针对admin-ajax.php的攻击有直接和间接两种方式:直接的方式不必说,就是以admin-ajax.php为目标直接开干,这种反而比较容易对付,通常内网的长亭雷池WAF就能解决;而间接的方式,就是利用其他基于ajax技术的应用间接进行攻击,比如路径为”/wp-content/plugins/translatepress-multilingual/includes/trp-ajax.php”的应用,这是我使用的翻译插件TranslatePress的路径,可以通过对翻译插件的高频率调用来间接实现对admin-ajax.php的高频率攻击(毕竟wordpress上任何ajax的调用都要经过admin-ajax.php这个入口进行):
这种就比较难麻烦了,因为这种其实是正常的调用,只不过调用频率很高而且一直持续而已(也算是针对性的DDoS攻击)。同时,通过扫描wordpress就可以知道安装了哪些插件,只要是使用了ajax技术的插件,都可以成为间接攻击的帮凶。
解决思路梳理
外层:cloudflare层面
从cloudflare free版用户的角度来说,通常最行之有效的脚本检测机制就是WAF的托管质询,可惜的是,如前面所说,Cloudflare的质询机制设计目的是验证用户行为,但 AJAX 请求无法自动通过质询,因为它缺乏用户交互的能力。
那么能否从User-Agent方面下手呢?其实不是不行,我有段时间就是使用WAF规则基于UA来封杀异常流量,可惜UA是可以随意更改的,关键攻击我的不是一伙人,UA各不相同,总是变来变去,我封了一段时间就放弃这种笨方法了。
所以理论上来说,能用的就是速率限制功能,因为速率限制可以对访问某一个路径的请求设置阀值,至少可以极大的限制攻击数量:
可惜的是,速率限制虽然可以通过”or”的方式添加多个URI路径,但是最终的请求速率阀值却只能设置一个,一般而言,在用于全站DDos防护的时候,这个设置的速率值需要能保证平时的正常的访问,所以不可能设置太低,因此,对于没达到这个速率阀值但又高于正常访问速率的攻击是无可奈何的(我这次遇到的就是),且从价值最大化的角度来讲,这唯一的一个速率值还是用在防护全站范围的DDos攻击上最好:
当然,临时使用速率限制规则来缓解针对admin-ajax.php的攻击的也是可以的(如果要这么做,最好是直接限制调用admin-ajax.php的应用的路径,比如在我这里就是TranslatePress的路径),只是这样一来速率限制设置的阀值肯定要设置小,这样就无法兼顾全站的DDos攻击防护了(设置小了会影响网站的正常访问)。
综上所述,从cloudflare(Free用户)的角度来说,是没办法仅仅通过WAF相关的配置解决关于admin-ajax.php攻击流量的问题的(又不能阻止,又不能质询,用速率限制又感觉大炮打蚊子),虽然理论上最好的方案是能在cloudflare层面能解决这个问题,奈何理想很丰满,现实却很骨感,所以,御敌于国门之外的战略看来行不通了。
另,这部分内容涉及到cloudflare的WAF知识,不熟悉的朋友可以参考我的cloudflare系列教程之WAF篇:Home Data Center Series CloudFlare Tutorial (IV) CF WAF Function Introduction and Detailed Configuration Tutorial.
如果不仅仅限于Free用户,其实cloudflare还是有蛮多更高级对付脚本的功能,比如:
1. Super Bot Fight Mode(高级机器人防护模式)
• 基础功能:适合没有复杂配置需求的用户,能够区分已知的好机器人(如 Googlebot)和已知的恶意机器人。
• advantage:不需要 CAPTCHA 验证即可判断流量是否为恶意机器人;内置恶意机器人数据库,自动阻止已知恶意来源。
• shortcoming:针对复杂攻击(如伪装成合法流量的机器人)的识别效果有限;配置相对简单,不支持自定义规则。
2. Bot Management(机器人管理)
• 高级功能:适合需要精细化控制和分析的用户,可以与 WAF 规则配合使用。
• 核心技术:1、浏览器指纹:通过分析请求头信息(如 User-Agent、Accept-Language)以及浏览器的行为特征(如鼠标和键盘事件);2、流量分析:结合请求模式、IP 地址信誉和地理位置等;3、机器学习模型:通过对流量的实时分析识别恶意机器人。
• advantage:能够精准识别伪装成正常浏览器的机器人流量;提供详细的分析数据,便于管理员优化规则。支持定制化操作,例如直接阻止、允许、挑战(如 JS Challenge 或 CAPTCHA)。
• shortcoming:是 Cloudflare 的高端功能,需要 Business 或 Enterprise 计划支持;配置需要一定技术背景。
以上两种技术在识别和防护恶意机器人流量方面更先进,它们的机制与传统的托管质询(Managed Challenge)有所不同,可以在一定程度上减少对用户体验的干扰,而无需总是触发人机验证(如 CAPTCHA),不过嘛,到底行不行,我也不知道,实践才能出真知,可惜我没钱,用不起~。
中层:长亭雷池WAF
长亭雷池WAF本身可以防护针对发起admin-ajax.php的常规攻击(就是前面提到的直接的方式),毕竟有这么多防护模块:
所以其实这类攻击我并不太在意,我在意的是间接方式导致的攻击,这种攻击的特点是通常会和调用ajax的应用成对出现,示例如下:
这种其实是正常的行为,只是数量大了之后就形成了攻击。注:上图中的攻击类型为”命令注入”并不准确,其实是因为需要翻译的文章内容中包含了命令代码而引发的WAF错误识别,不过这个不重要,我只是为了说明什么是”成对出现”。
相比cloudflare Free用户各种功能额度紧巴巴,长亭雷池WAF(社区版)上可用的功能就要宽松多了。
• 全局频率限制:
• CC防护(站点级的频率限制):
• BOT防护(人机校验)
• 身份验证
上面这几个功能中,身份验证这里用不上;Bot防护和cloudflare WAF的质询有类似的情况,并不能用于admin-ajax.php;而频率限制虽然可以全局配置以及站点级配置,但是问题在于按照正常的配置逻辑没法把对站点的正常访问和对admin-ajax.php以及TranslatePress翻译功能的访问进行分离设置(其实道理和cloudflare上速率限制设置的阀值难以兼顾正常访问和攻击流量一样):如果频率限制设置得太低就会影响到博客的正常访问了,设置太高又对防护这种攻击没有效果。
看来,仅仅从长亭雷池(社区版)的角度,也没有办法解决关于admin-ajax.php攻击流量的问题,所以,常规的诱敌深入,围而歼之的战略也行不通了。
内层:Wordfence
Wordfence插件是WordPress的一个非常流行的安全工具,其免费版本提供了一系列基本的保护功能。尽管与专业版相比功能有所限制,但免费版对于个人博主而言也基本足够,以下是Wordfence免费版的几个主要功能及对admin-ajax.php攻击的防护作用:
1. 防火墙保护(Web Application Firewall, WAF)
• Basic rule set:免费版提供一套基础的WAF规则,用于检测和阻止常见的攻击,如SQL注入、跨站点脚本(XSS)、对admin-ajax.php的滥用等,比如只是与高级版实时规则更新相比,免费版的规则更新通常有30天延迟。
• Request rate limiting:可以配置规则限制admin-ajax.php的访问频率,例如:限制单个 IP 在短时间内的请求次数;设定阈值,超出后直接拒绝访问:
对于 admin-ajax.php 攻击,“如果抓取工具的网页浏览量超过” 是最直接的防护选项,因为它能够有效限制频繁请求。调整为适当的频率限制(比如受到攻击时候尝试调整为每分钟20-50次),配合 Wordfence 的 IP 封锁功能,能显著减少攻击面。
2. 恶意软件扫描
• 文件完整性检查:对比网站核心文件、插件和主题的内容,检查是否被篡改。
• 后门程序检测:扫描网站是否包含后门、恶意脚本或代码片段。
• 已知漏洞检测:检查已安装的插件和主题是否存在已知漏洞。
• SEO垃圾内容扫描:检测网站是否被插入SEO垃圾链接或隐藏内容。
如果 admin-ajax.php 被篡改或包含恶意代码,扫描功能会发出警告。
3. 登录安全
• 限制登录尝试:防止暴力破解攻击,限制IP登录失败次数。
• 阻止被暴力破解攻击的IP:自动封锁尝试使用常见用户名(如”admin”)的攻击来源。
• 登录验证码(reCAPTCHA):免费版支持通过Google reCAPTCHA保护登录表单。
如果攻击涉及通过 admin-ajax.php 发起登录尝试,Wordfence 免费版的登录安全功能可以限制错误尝试次数,并临时封锁相关 IP。
4. IP封锁
• 手动封锁:允许管理员手动封锁可疑IP地址或整个IP范围。
• 基本IP封锁:基于已知的攻击者IP地址进行封锁,但更新频率较低。
一旦通过流量监控识别出攻击源,可以在”封锁”功能中添加这些 IP。
通过以上这些功能的合理使用,Wordfence虽然可以在一定程度上对admin-ajax.php的攻击流量进行缓解,但是,我是很不赞成直接在首都打攻防战的:即便Wordfence能全部拦截下来,也还是消耗了WP的性能,更别说未必能全部拦截下来。
理论上的最佳解决方案
经过一番考量,我觉得目前可能的最佳解决方案还是结合cloudflare和长亭雷池的WAF,利用长亭雷池WAF的高频防护功能让攻击被终结在长亭雷池的WAF上。
只是这样的话,需要解决一个关键问题:如何实现对wordpress的访问进行分流,将正常的博客访问请求、对admin-ajax.php的攻击请求、对路径”/wp-content/plugins/translatepress-multilingual/includes/trp-ajax.php”的高频调用请求三者分离开来,如果能够实现这一点,就可以将3者的访问请求分别发到长亭雷池WAF上的3个不同的站点,并分别对这3个站点设置不同的CC防护阀值,这样就可以既不影响博客的正常访问,又可以对admin-ajax.php的攻击以及对TranslatePress的高频调用进行封禁的同时不影响评论功能以及翻译功能。
cloudflare tunnel + 长亭雷池WAF打造最佳解决方案
之前的一篇关于cloudflare zero trust的文章中,我详细介绍了cloudflare tunnel在多种场景下的运用方式(参见文章:Cloudflare tutorial series for home data centers (Part 9) Introduction to common Zero Trust functions and multi-scenario usage tutorials),其中有个细节一直让我比较在意,就是在tunnel中创建public hostname的时候,是可以指定URI路径的,如下:
我之前一直没遇到有这个需求的场景,也就没有深究,不过,此时我就想,同一个子域名的话,不加路径的public hostname和加了路径的public hostname能否共存呢?如果可以不就可以解决同一个域名不同URI的访问相互分离这个问题了?所以我赶紧试了试,还真可以(序列号越小的优先级越高):
所以只要把具体的URI放在前面,”*
“放在最下面,就可以实现3个路径的访问分别指向内网长亭雷池WAF上的不同的站点:
这样一来,只要长亭雷池WAF上使用不同的端口创建3个站点(我这里是50080,50081,50082这3个端口)并设置不同的高频访问限制阀值,就可以在限制admin-ajax.php、TranslatePress的翻译功能的调用频率时也不影响正常的博客访问流量:
从上图中可以看出,TranslatePress翻译功能对应的ajax调用请求数量是最多的,因为间接攻击就是通过它发起的,在其对应站点的CC防护我设置的一段时间内超过请求数量阀值或者攻击数量阀值就直接封禁1天(1天之内该IP来访问都会被直接返回404):
实际效果:
因为TranslatePress的ajax调用封禁的及时,所以对admin-ajax.php的调用也就基本正常了,而博客的正常访问统计已经完全正常。
截止到文章写完,攻击依旧在继续,只是我已经没啥感觉了:
注1:在解决这次的问题上,长亭雷池WAF并不是必须的,因为用到的是对不同站点的速率限制功能,而Nginx本身也支持站点的访问速率限制,所以对于当前场景,使用一般的Nginx或者宝塔面板上安装的Nginx或者其他支持速率限制的反向代理都是可以的。
注2:关于长亭雷池WAF上具体的高频防护阀值(主要是这个),高频攻击阀值,高频错误限制(如果有大量扫描产生的404可以使用这个),请大家根据自己的实际情况设置一个合适的值。
Afterword
终于把这烦人的问题解决了,也怪我开始的时候没太把这事放在心上,直接在cloudflare的WAF上质询了事,没有深入研究这个问题,导致了可能长达1-2周的评论和翻译功能异常。
不过,通过这个事,让我发现了使用cloudflare tunnel来对博客不同访问路径进行分流的新用法,这个还是蛮让我开心的,最重要的,是之后遇到其他需要”分流”的场景时,就可以非常简单的通过本文的方式来完成,又多了一种实用的处理问题的方式。
虽然不道德 但是wordfence仅需几行代码就可以使用高级版 博主可以去搜搜 只是每次升级要重新插入代码φ( ̄∇ ̄o)
(博主自己看到就行啦 不用公开 嘿嘿嘿嘿 )
这样简单吗,那需要的时候可以弄下:)