Preface
I started encountering continuous attacks a few weeks ago. Crucially, these attacks are not the low-tech HTTP flood attacks of the past, but low-rate attacks targeting a weak point in WordPress's defense: the AJAX function entry point (/wp-admin/admin-ajax.php) (ranging from a few to over a dozen per second):

This just happens to bypass the global rate limit I set on Cloudflare, and the range of attacking IPs varies greatly. Previously, they were mostly foreign, mainly from Russia, but in recent days, they have switched to domestic IPs:

Among these, the most threatening are the attacks from Russia, as they seem to be able to masquerade as Yandex search engine spiders, thereby bypassing the ”Known Bots” filter in Cloudflare's WAF rules and hitting my internal network directly (I even feel they can penetrate Cloudflare's ”5-second shield”, because for a while I tried setting up Managed Challenges specifically for this path, but the attacks from Russia still hit the internal WAF, while others were intercepted. However, I haven't had time to verify this specifically, so I can't be completely sure). This left me completely baffled by the massive amount of blog access data for a few days. The requests in the red box in the figure below are just the blog request data seen on the internal WAF from 0:00 to 8:00 AM on the 26th:

Therefore, it forced me to specifically modify Cloudflare WAF's skip policies:

Due to the robustness of my three-layer defense system (Outer layer: Cloudflare WAF Free Edition, Middle layer: Chaitin SafeLine WAF Community Edition, Inner layer: WordPress security plugin Wordfence Free Edition), these attacks did not have a substantive impact on my blog access. However, they severely affected the accuracy of my statistical data (as a perfectionist, looking at the massive number of requests on the blog site in Chaitin SafeLine WAF was really annoying). I hadn't planned to write about this in this week's article, but because I was so annoyed by these attacks—like flies constantly buzzing around my ears—I decided to set out to solve this problem!
Additional Knowledge: WordPress's admin-ajax.php
First, let's talk about WordPress's admin-ajax.php (the full path is/wp-admin/admin-ajax.php)。
admin-ajax.php is the entry point for WordPress's AJAX functionality: many dynamic features in WordPress (including comment verification, liking, reply loading, etc.) are implemented using AJAX requests, and these requests are typically completed through the /wp-admin/admin-ajax.php path (for popular science knowledge about AJAX technology, you can refer to my other article:Home Data Center Series: A Review of Core Web Development Concepts Starting from Ajax Cross-Origin Issues (Non-Programmer's Perspective))。
Taking the behavior of users posting comments on WordPress as an example, let's briefly introduce the workflow of AJAX requests.
- User Action Triggers AJAX Request:
After a user enters a comment in the comment form and clicks the submit button, the frontend JavaScript will initiate a background request via XMLHttpRequest or fetch, usually pointing to /wp-admin/admin-ajax.php.
- Browser Initiates HTTP Request:
The browser will follow the JavaScript instructions to package the comment data into an HTTP request and send it to the server (in this case, admin-ajax.php).
- Request Characteristics:
• Request headers will carry necessary information (such as Cookies, Referer, etc.) so that the server can identify the user's status.
• They are usually POST requests used to submit data (such as comment content).
Originally, if you do not manage WordPress directly through the domain name provided for external access (which I believe is the secure way to operate and maintain WordPress: separating the address for external access from the address used for daily WordPress management, thereby facilitating WAF policy settings), and you also do not need the comment function, then the simplest and most brutal way is to directly block all requests starting with/wp-adminon Cloudflare WAF. However, although I do not manage it through this domain, the comment function is still needed, so I cannot solve it in such a brutal way.
Well, if we don't block it, can we use Cloudflare's WAF to handle access requests to admin-ajax.php using challenges? No, because Cloudflare performs additional verification on each request (such as browser fingerprinting, behavioral verification, etc.). If the verification fails, the request will be blocked. This is fatal for AJAX requests because these requests are usually automatically initiated by frontend scripts and are not complete browser interactions, so they will not respond to challenges (I didn't notice at first, but after setting it up, a friend told me they couldn't post comments, which made me realize the issue, and then I found out the translation function was also gone~).
Generally speaking, there are two ways of attacking admin-ajax.php: direct and indirect. The direct way goes without saying—targeting admin-ajax.php directly. This is actually easier to deal with, and usually the internal Chaitin SafeLine WAF can handle it. The indirect way is to use other applications based on AJAX technology to attack indirectly, such as the application with the path ”/wp-content/plugins/translatepress-multilingual/includes/trp-ajax.php”, which is the path of the TranslatePress translation plugin I use. High-frequency attacks on admin-ajax.php can be achieved indirectly through high-frequency calls to the translation plugin (after all, any AJAX call on WordPress must go through the admin-ajax.php entry point):

This is more troublesome because it is actually a normal call, just with a very high frequency and continuous duration (which can be considered a targeted DDoS attack). At the same time, by scanning WordPress, attackers can know which plugins are installed. Any plugin that uses AJAX technology can become an accomplice in indirect attacks.
Solution Outline
Outer Layer: Cloudflare Level
From the perspective of a Cloudflare Free user, the most effective script detection mechanism is usually the WAF's Managed Challenge. Unfortunately, as mentioned earlier, Cloudflare's challenge mechanism is designed to verify user behavior, but AJAX requests cannot automatically pass the challenge because they lack the capability for user interaction.
So can we start with the User-Agent? Actually, it's not impossible. For a while, I used WAF rules to block abnormal traffic based on UA. Unfortunately, UA can be changed at will. Crucially, the attackers targeting me are not the same group, and their UAs are all different and constantly changing. I gave up on this clumsy method after blocking them for a while.
So theoretically, what can be used is the rate limiting feature, because rate limiting can set a threshold for requests accessing a certain path, which can at least greatly limit the number of attacks:

Unfortunately, although rate limiting allows adding multiple URI paths using ”or”, only one final request rate threshold can be set. Generally speaking, when used for site-wide DDoS protection, this configured rate value needs to ensure normal daily access, so it cannot be set too low. Therefore, there is nothing that can be done about attacks that do not reach this rate threshold but are still higher than the normal access rate (which is exactly what I encountered this time). Furthermore, from the perspective of maximizing value, it is best to use this single rate value for protecting against site-wide DDoS attacks:

Of course, temporarily using rate limiting rules to mitigate attacks targeting admin-ajax.php is also possible (if you want to do this, it is best to directly limit the path of the application calling admin-ajax.php, which in my case is the TranslatePress path). However, doing so means the rate limiting threshold must be set very low, making it impossible to balance site-wide DDoS attack protection (setting it too low will affect normal access to the website).
In summary, from the perspective of Cloudflare (Free users), it is impossible to solve the issue of admin-ajax.php attack traffic solely through WAF-related configurations (it cannot be blocked, cannot be challenged, and using rate limiting feels like using a sledgehammer to crack a nut). Although theoretically the best solution would be to resolve this issue at the Cloudflare level, the ideal is beautiful but reality is harsh. Therefore, the strategy of "defeating the enemy outside the gates" seems unfeasible.
Additionally, this part involves Cloudflare WAF knowledge. Friends who are not familiar with it can refer to my Cloudflare tutorial series - WAF edition:Home Data Center Series Cloudflare Tutorial (Part 4): CF WAF Feature Introduction and Detailed Configuration Tutorial。
If not limited to Free users, Cloudflare actually has quite a few more advanced features to deal with scripts, such as:
Super Bot Fight Mode
• Basic Features: Suitable for users without complex configuration needs, capable of distinguishing between known good bots (such as Googlebot) and known malicious bots.
• Advantages: No CAPTCHA verification is required to determine if traffic is from a malicious bot; built-in malicious bot database automatically blocks known malicious sources.
• Disadvantages: Limited effectiveness in identifying complex attacks (such as bots masquerading as legitimate traffic); relatively simple configuration, does not support custom rules.
Bot Management
• Advanced Features: Suitable for users who need fine-grained control and analysis, can be used in conjunction with WAF rules.
• Core Technology:Browser Fingerprinting: By analyzing request header information (such as User-Agent, Accept-Language) and browser behavioral characteristics (such as mouse and keyboard events);Traffic Analysis: Combining request patterns, IP address reputation, and geographic location, etc.;Machine Learning Models: Identifying malicious bots through real-time analysis of traffic.
• Advantages: Capable of accurately identifying bot traffic disguised as normal browsers; provides detailed analytical data, making it easy for administrators to optimize rules. Supports customized actions, such as direct block, allow, or challenge (such as JS Challenge or CAPTCHA).
• Disadvantages: Is a high-end feature of Cloudflare, requiring Business or Enterprise plan support; configuration requires a certain technical background.
The above two technologies are more advanced in identifying and protecting against malicious bot traffic. Their mechanisms differ from traditional Managed Challenges, reducing interference with user experience to some extent without always triggering CAPTCHAs. However, whether they actually work well, I don't know—only practice brings true knowledge, and unfortunately, I don't have the money and can't afford them~.
Middle Layer: Chaitin SafeLine WAF
SafeLine WAF itself can protect against conventional attacks targeting admin-ajax.php (which is the direct method mentioned earlier), after all, there are so many protection modules:

So actually, I don't care much about this type of attack; what I care about are attacks caused by indirect methods. The characteristic of this kind of attack is that it usually appears in pairs with the application calling AJAX, as shown in the example below:

This is actually normal behavior, but it becomes an attack when the volume gets too large. Note: The attack type ”Command Injection” in the image above is not accurate; it was actually a WAF false positive triggered because the article content to be translated contained command code, but that's not important—I just wanted to illustrate what ”appearing in pairs” means.
Compared to the tight feature quotas for Cloudflare Free users, the available features on SafeLine WAF (Community Edition) are much more generous.
• Global Rate Limiting:

• CC Protection (Site-level Rate Limiting):


• BOT Protection (Bot Verification)


• Authentication


Among these features, authentication is not useful here; Bot protection has a similar situation to Cloudflare WAF's challenge and cannot be used for admin-ajax.php; and although rate limiting can be configured globally and at the site level, the problem is that according to normal configuration logic, it is impossible to separate normal access to the site from access to admin-ajax.php and the TranslatePress translation feature (actually, the reason is the same as why the rate limit threshold set on Cloudflare is difficult to balance normal access and attack traffic): if the rate limit is set too low, it will affect normal access to the blog, and if set too high, it will have no effect on protecting against this kind of attack.
It seems that solely from the perspective of SafeLine (Community Edition), there is also no way to solve the problem of admin-ajax.php attack traffic, so the conventional strategy of luring the enemy in deep and surrounding to annihilate them won't work either.
Inner Layer: Wordfence
The Wordfence plugin is a very popular security tool for WordPress, and its free version provides a range of basic protection features. Although features are limited compared to the Pro version, the free version is basically sufficient for individual bloggers. Here are several main features of the Wordfence free version and their protective effects against admin-ajax.php attacks:
Firewall Protection (Web Application Firewall, WAF)
• Basic Rule Set: The free version provides a basic set of WAF rules to detect and block common attacks, such as SQL injection, cross-site scripting (XSS), abuse of admin-ajax.php, etc. For example, compared to the real-time rule updates of the Premium version, the free version's rule updates usually have a 30-day delay.
• Request Rate Limiting: Rules can be configured to limit the access frequency of admin-ajax.php, for example: limiting the number of requests from a single IP within a short period; setting a threshold, beyond which access is directly denied:

For admin-ajax.php attacks,“If a crawler's page views exceed” is the most direct protection option because it can effectively limit frequent requests. Adjusting to an appropriate rate limit (for example, trying to adjust to 20-50 times per minute when under attack), combined with Wordfence's IP blocking feature, can significantly reduce the attack surface.
Malware Scanning
• File Integrity Check: Compare the contents of website core files, plugins, and themes to check if they have been tampered with.
• Backdoor Detection: Scan the website to see if it contains backdoors, malicious scripts, or code snippets.
• Known Vulnerability Detection: Check if installed plugins and themes have known vulnerabilities.
• SEO Spam Scanning: Detect if the website has been inserted with SEO spam links or hidden content.
If admin-ajax.php is tampered with or contains malicious code, the scanning feature will issue a warning.
Login Security
• Limit Login Attempts: Prevent brute-force attacks, limit the number of failed IP login attempts.
• Block IPs targeted by brute-force attacks: Automatically block attack sources attempting to use common usernames (such as ”admin”).
• Login CAPTCHA (reCAPTCHA): The free version supports protecting login forms via Google reCAPTCHA.
If the attack involves initiating login attempts through admin-ajax.php, the login security feature of Wordfence's free version can limit the number of failed attempts and temporarily block the associated IP.
IP Blocking
• Manual Blocking: Allows administrators to manually block suspicious IP addresses or entire IP ranges.
• Basic IP Blocking: Blocks based on known attacker IP addresses, but with lower update frequency.
Once the attack source is identified through traffic monitoring, these IPs can be added to the ”Blocking” feature.
Through the reasonable use of the above features, although Wordfence can mitigate the attack traffic on admin-ajax.php to some extent, I strongly disagree with fighting defensive battles directly in the "capital" (on the origin server): even if Wordfence can intercept all of them, it still consumes WP's performance, not to mention it might not be able to intercept all of them.
Theoretical Best Solution
After some consideration, I think the best possible solution at present is to combine Cloudflare and Chaitin SafeLine WAF, utilizing the high-frequency protection feature of Chaitin SafeLine WAF to terminate the attacks on the Chaitin SafeLine WAF.
However, doing so requires solving a key problem: how to achieve splitting the access to WordPress, separating normal blog access requests, attack requests on admin-ajax.php, and high-frequency call requests to the path ”/wp-content/plugins/translatepress-multilingual/includes/trp-ajax.php”. If this can be achieved, the access requests of the three can be sent to three different sites on the Chaitin SafeLine WAF respectively, and different CC protection thresholds can be set for these three sites. This way, it can avoid affecting the normal access to the blog, while blocking attacks on admin-ajax.php and high-frequency calls to TranslatePress without affecting the comment and translation functions.
Cloudflare Tunnel + Chaitin SafeLine WAF to Create the Best Solution
In a previous article about Cloudflare Zero Trust, I introduced the usage of Cloudflare Tunnel in various scenarios in detail (see article:Home Data Center Series Cloudflare Tutorial (9) Introduction to Common Zero Trust Features and Multi-Scenario Usage Tutorial), in which there was a detail that kept catching my attention: when creating a public hostname in the tunnel, you can specify a URI path, as follows:

I hadn't encountered a scenario with this requirement before, so I didn't look into it deeply. However, at this moment, I thought: for the same subdomain, can a public hostname without a path and a public hostname with a path coexist? If they can, wouldn't that solve the problem of separating access to different URIs under the same domain? So I quickly tried it, and it actually works (the smaller the serial number, the higher the priority):

So as long as the specific URI is placed first, ”*“ placed at the very bottom, you can achieve pointing the access of the three paths to different sites on the intranet Chaitin SafeLine WAF respectively:



In this way, as long as three sites are created on the Chaitin SafeLine WAF using different ports (in my case, ports 50080, 50081, and 50082) and different high-frequency access limit thresholds are set, the call frequency of admin-ajax.php and TranslatePress's translation function can be limited without affecting normal blog access traffic:

As can be seen from the figure above, the number of AJAX call requests corresponding to the TranslatePress translation function is the largest, because the indirect attack was initiated through it. For the CC protection of its corresponding site, I set it to directly block for 1 day if the number of requests or attacks exceeds the threshold within a certain period of time (any access from that IP within 1 day will directly return a 404):

Actual effect:

Because the AJAX calls of TranslatePress were blocked in time, the calls to admin-ajax.php have basically returned to normal, and the normal access statistics of the blog are completely normal.
As of the completion of this article, the attack is still continuing, but I don't feel much about it anymore:

Note 1: Chaitin SafeLine WAF is not mandatory to solve this problem, because what is used is the rate limiting feature for different sites, and Nginx itself also supports site access rate limiting. Therefore, for the current scenario, using general Nginx, Nginx installed on the BT Panel, or other reverse proxies that support rate limiting is perfectly fine.
Note 2: Regarding the specific high-frequency protection thresholds on Chaitin SafeLine WAF (mainly this one), high-frequency attack thresholds, and high-frequency error limits (this can be used if there are many 404s generated by scanning), please set an appropriate value according to your actual situation.
Afterword
Finally, this annoying problem has been solved. It's also my fault for not taking it too seriously at the beginning, simply challenging it on Cloudflare's WAF and not studying the issue deeply, which led to abnormal comment and translation functions for probably 1-2 weeks.
However, through this incident, I discovered a new way to use Cloudflare Tunnel to split different access paths of the blog, which makes me quite happy. Most importantly, when encountering other scenarios that require ”traffic splitting” in the future, it can be easily accomplished using the method in this article, adding another practical way to handle problems.
Although it's unethical, Wordfence only needs a few lines of code to use the premium version. The blogger can search for it, it's just that you have to re-insert the code every time you upgrade φ( ̄∇ ̄o)
(It's fine as long as the blogger sees it, no need to make it public hehehehe )
(It's fine as long as the blogger sees it, no need to make it public hehehehe )