How a 95,000-IP Botnet Used tel: Links to Cripple a Client’s WordPress Site

I’ve been hosting and managing WordPress websites for over 20 years. Brute force attacks, XML-RPC abuse, spam injections, DDoS attempts… I’ve seen all of it. But last weekend I ran into something I’d never encountered before, and after searching around I couldn’t find a single article documenting this type of attack. So I’m writing one.

Something Was Wrong, But Nothing Looked Wrong

One of the WordPress sites on our cPanel server had been sluggish for about a week. It wasn’t just that site feeling it, either. The whole server was slower than usual, and other client sites that normally loaded instantly were dragging.

We went through the usual checklist. Cleared caches. Optimized the database. Checked for bloated error logs and reviewed plugin performance. Nothing jumped out. We ran a Wordfence scan, which kept failing due to memory exhaustion. We found and fixed a wp-cron misconfiguration that had been generating tens of thousands of unnecessary loopback requests. Load would drop for a bit and then climb right back up.

After hours of chasing our tail, we finally stopped guessing and looked at what was actually consuming resources in real time.

There It Was

Running ps aux --sort=-%cpu showed six PHP-FPM processes for this one site, each consuming 75-79% CPU. A single website was consuming the equivalent of nearly five full CPU cores.

We pulled the Apache access logs and the problem was staring right at us:

187.244.118.5 "GET /directory-page/categories/escape-room/tel:5551234567/tel:5559876543/tel:5555551234/tel:5558675309" 301
168.181.58.2 "GET /directory-page/categories/escape-room/tel:5551234567/tel:5553456789/tel:5557891234/tel:5554567890" 301
27.252.198.69 "GET /directory-page/categories/escape-room/tel:5551234567/tel:5552345678/tel:5558901234/tel:5556789012" 301

Hundreds of requests per second. All hitting the same URL pattern. All from different IPs. And all with fake user agents like Windows 98, Opera 8, MSIE 5.0, and Android 2.0. Nobody is running those browsers in 2026.

How the Attack Works

The site had a business directory page built with Elementor that listed local businesses with clickable phone numbers. Somewhere in the template, the tel: URIs were being rendered as relative links rather than proper <a href="tel:5551234567"> protocol links.

That distinction matters more than you’d think. When a tel: link is coded correctly, the browser handles it by opening the phone dialer. No request ever hits your server. But when it’s rendered as a relative path, a crawler treats it like a page on the site. A request to /directory-page/categories/escape-room/tel:5551234567 doesn’t return a 404 in WordPress. WordPress just loads the parent page, which contains more phone links, which generates more URL paths, which creates an infinite recursive crawl loop.

A botnet found this loop and went to town. Every request had a unique URL because the phone number combinations were randomized, which meant caching was completely useless. Every single request was a cache miss that spun up a fresh PHP process. With 95,000 unique IPs hammering the site, the server was trying to handle hundreds of concurrent PHP requests, each one rendering a full WordPress page with Elementor.

That’s what had been dragging down the server for a week.

Why This Was So Hard to Catch

This attack is different from what most WordPress admins are used to dealing with, and that’s what makes it dangerous.

With 95,000 unique source IPs, there’s nobody to block. Traditional firewall deny lists don’t help when the IPs are disposable and constantly rotating.

Because the phone number combinations are randomized, every URL is different. Page caching, the thing that normally saves WordPress sites from traffic spikes, does nothing here. Every request is a cache miss.

In this case, WordPress didn’t reject these URLs. When a request came in for /valid-page/garbage/more-garbage/, instead of returning a 404 it loaded the valid parent page. This behavior can vary depending on your permalink structure, theme, and plugins, but the result here was that every junk URL the botnet generated got a full 200 response with a complete PHP execution cycle.

Standard security monitoring won’t catch it. If you’re watching for failed logins, xmlrpc abuse, or malware signatures, this looks like normal page traffic. Wordfence didn’t flag a single request.

And on any server hosting multiple sites, one site under this attack slows down every other site on the machine. We spent hours optimizing the wrong things because we didn’t realize the traffic pattern was the actual problem.

The Fix

Immediate Mitigation

We needed to kill this fast. After trying several approaches that didn’t work (other redirect rules in the .htaccess were firing before our block could catch the requests), this is what finally did the job. Add it to the very top of your .htaccess, before any cache plugin or WordPress rewrite rules:

apache
# Block botnet tel: URI resource exhaustion attacks
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{THE_REQUEST} /tel: [NC]
RewriteRule ^ - [F]
</IfModule>

The important detail is using %{THE_REQUEST} instead of %{REQUEST_URI}. The former matches against the original raw request line before any other rewrites process it. We tried %{REQUEST_URI} first and it flat out didn’t work because trailing slash redirects and www normalization rules were issuing 301s before our block ever had a chance to evaluate.

Server load dropped to under 2 within minutes.

This Won’t Break Your Phone Links

If you’re wondering whether this rule will break legitimate tel: links on the front end, it won’t. When a visitor clicks <a href="tel:5551234567">Call Us</a>, the browser opens the phone dialer directly. No HTTP request goes to your server. The only time /tel: shows up in a URL path hitting your server is when a bot is crawling it like a page, and that’s exactly what we want to block.

Fixing the Root Cause

The real fix is finding where the template renders tel: URIs as relative links and correcting the HTML. Phone links should always be output as proper protocol links:

html
<a href="tel:5551234567">Call Us</a>

When they’re coded correctly, the browser handles them natively and they never generate server requests. This closes the infinite crawl loop permanently.

Nginx Alternative

If you’re running nginx as a reverse proxy or standalone, you can block this at the nginx level instead, which is even more efficient since it drops the connection before anything else processes:

nginx
location ~* /tel: {
    return 444;
}

The 444 response is an nginx-specific status code that simply closes the connection with zero response body. Extremely lightweight.

Long-Term Protection

Putting Cloudflare in front of the domain is probably the best single thing you can do going forward. Even their free tier with Bot Fight Mode would catch this traffic before it reaches your server. The fake user agents alone would trigger their bot detection.

Beyond that, get in the habit of periodically reviewing raw access logs for unusual patterns. Security plugins are great at detecting malware and brute force attacks, but they have blind spots. This type of resource exhaustion doesn’t trip any of the usual alarms.

Was This an AI-Driven Attack?

I wondered about this too, given how much AI is being used for offensive security these days. Based on what I observed, I don’t think so. The fake user agents are classic botnet signatures that have been in use for years. Windows 98, impossible OS and browser combinations, randomized version numbers. An AI-driven attack would more likely use realistic, modern user agent strings to blend in with legitimate traffic.

My best guess is that this is a traditional scraper botnet that stumbled into the infinite URL space created by the malformed tel: links and just kept spawning crawlers because it kept finding “new” pages. Whether the resource exhaustion was intentional or just a side effect of the botnet getting stuck in a crawl loop is hard to say, but the result was the same.

What I Took Away From This

The biggest lesson was embarrassingly simple: when a server is slow, look at what’s actually hitting it before you start optimizing things. We spent hours tweaking caches, databases, and cron jobs while the answer was sitting right there in the access logs.

WordPress’s permissive URL handling is also worth thinking about. The fact that it serves content for URLs with arbitrary path segments appended to valid pages creates real opportunities for this kind of resource exhaustion. It’s not a bug exactly, but it’s a behavior that can be exploited.

Security plugins have blind spots too. Wordfence is excellent at what it does, but it’s designed to detect malware, brute force attempts, and known vulnerability exploits. It’s not analyzing traffic patterns for resource exhaustion.

And finally, check your templates for relative protocol links. Any tel:, mailto:, or similar protocol URI that gets rendered as a relative path instead of a proper protocol link creates a potential infinite crawl loop that a bot can exploit.

If you’ve seen something similar or have questions about implementing these fixes, I’d like to hear about it. And if you’re a hosting provider or security researcher, I’m curious whether this attack pattern has shown up elsewhere. I couldn’t find any documentation when I went looking.

Canon Cinema RAW Light Workflow

Cinema RAW Light Workflow by Visual Harmony

Here’s a Cinema RAW Light workflow I discovered over the weekend. This approach uses Davinci Resolve but you could use a similar process in other NLEs. In terms of practicality and for our needs, this is superior to using the Canon CRD software.

I shot about an hour of RAW footage for a client last week. Even though our main editing workstation is very powerful, it chokes on RAW clips when editing natively in an NLE once you start keyframing and adding effects. So, I needed to widdle them down to something more manageable that can be edited without hiccups. Also, I think it defeats the purpose of shooting in RAW if you are going to transcode before adjusting RAW settings.

Here’s the approach I came up with:

  1. Open RAW clip in Davinci Resolve
  2. Trim clip to only the good stuff. This will need to be done at some point during the project, so you might as well do it now. Trim off everything that won’t be usable in your final edit. Feel free to leave handles if you aren’t sure of the best place to cut. This will save a lot of time because it makes no sense to render/transcode extra footage that will never be used.
  3. Adjust RAW settings
  4. Adjust exposure (if needed)
  5. Remove noise (if present)
  6. Stabilize (if needed)
  7. Crop (if desired)
  8. Render clip to DNxHR HQX 10-bit (or other format if preferred for your situation). The settings I used are: Format: Quicktime, Codec: DNxHR, Type: DNxHR HQX 10-bit. The new file will be rendered with the adjusted RAW settings baked-in, and it will be far less likely to cause frame-stutter in an NLE when editing and adding effects.
  9. Repeat steps 1-8 for each RAW clip you have.

Now, instead of sending my editing partner a bunch of unadjusted RAW clips (doing so would not spark joy), I can now send her manageable .MOV files that have already been pre-trimmed, are exposed properly, and are ready to drop on the timeline and color grade.

Optionally, you can delete the RAW files afterwards since you already have the good stuff rendered at the size and format you need it to be in. I’ll still keep the RAW clips until the project has been delivered, but I will certainly be deleting them and only storing the 4:2:2 clips after final approval.

I hope this is helpful for fellow Canon shooters. Some of you may already be doing it this way, but it’s new to me. This workflow is easy and takes all the pressure out of shooting RAW for me. And since we spend the bulk of our project time in the editing room, this will save a ton of time on each of our projects going forward without having to sacrifice RAW capabilities.

Defining Your Digital Marketing Strategy

One of the keys to a successful digital marketing campaign is to have a well-defined strategy. At Visual Harmony, we work with our clients to develop and implement a custom digital marketing strategy that will help them reach the top of the search results for their desired keyword phrases.

Search Engine Optimization Overview

First, we review their website and prepare a comprehensive report of the current state of their search profile. Next, we identify key competitors and assess what is and isn’t working for them. Continue reading “Defining Your Digital Marketing Strategy”

Visual Harmony is Now Offering Video Production Services and is No Longer Developing Apps!

We are very excited to announce that we are now offering video production services! We have acquired a cinema camera and have partnered with another local videographer to bring Ocala the quality video production services it needs. Our first productions are underway and we will be ready to book new commercial video productions beginning in July. More information and examples of our work will be added to the website as they are ready.

Also, after much consideration, we are no longer offering app development or app maintenance services. We wanted apps to be a staple part of our business. We really did. However, more often than not, they were more trouble than they were worth. I’ve lost track of how many times we discussed an app with prospective clients and had them walk away from sticker shock due to the extensive amount of time that goes in to planning, developing, launching and maintaining an app. We developed some great apps over the years but the required ongoing maintenance is too costly for most clients in this area. So, rather than continue to support an area of business that hasn’t been fruitful, I am eliminating this service offering so that we can put 100% of our energy into continuing to build the best websites in the area and to make room for our new video production services!

Recently Completed Web Projects

We’ve been really busy at Visual Harmony over the past several months. We built and launched new websites for the ELC of Marion County, Ocala Oral Surgery, Owen Construction, Managed Care Consultants, LTC Systems and J. Lowe’s Guide Service and each of these projects is now on display in our projects area.

Stay tuned, we have some amazing projects in the works that will be added to the projects section after they launch.

Celebrating 10 Years of Web Development

We are very happy to be celebrating 10 years of providing quality web development services!

It seems like it was only yesterday when Visual Harmony was founded to serve the web development needs of the Central Florida community.  It’s hard to believe that was all the way back in 2007!

Many thanks to all of the wonderful people and businesses we have been fortunate to work with over the years.  Here’s to the next 10!