The internet took a massive hit this week, and I had a front-row seat to the chaos.
On 21 October 2026, two critical bugs in the PHP runtime went public, and exploit attempts hit the millions within a day. My side project, MatrixMonitor, had been logging the internet's worst attackers every night through all of it.
So I did what any curious engineer would do. I exported three days of data (20–22 October, UTC), set it against the news timeline, and wrote a 27-page threat report.
This post covers three things: how MatrixMonitor works, what actually happened during the PHP2Shell week, and what 295 attacker IPs told me. The most useful finding turned out to be about my own system, not about the attackers.
If you want to look while you read, the globe is live at matrix-monitor.aniruddhagawali.dev.
What MatrixMonitor is
MatrixMonitor is a full-stack threat-intelligence dashboard that draws the internet's most-reported attackers, and the countries they hit, on a 3D globe. Every day it keeps a snapshot, so you can pick any past date and replay it.
Once a day, a .NET worker pulls the AbuseIPDB blacklist, geolocates each IP offline with MaxMind, then enriches the top 100 with their last 24 hours of reports. Those reports tell me what each IP did (brute-force, SSH abuse, web-app attacks) and which countries reported it.
The stack:
-
Backend: ASP.NET Core Web API with a
BackgroundServicefor ingestion, retry on HTTP 500 and back-off on 429 rate limits -
Data: PostgreSQL through Entity Framework Core, with a
historical_attackstable as the system of record -
API:
/api/attacks,/historical-data(cached per day) and a time-zone-aware/min-max-date, running as two replicas behind Docker Compose - Frontend: Next.js 16, React 19, react-globe.gl, TanStack Query and Zustand. Click an attacker and arcs fly to every country it hit; a calendar picks the day.
The original spec also planned ML.NET spike detection, Server-Sent Events for live streaming and Cloudflare Radar context. Those aren't built yet, and as you'll see, this week's data made a strong case for them.
What happened: the PHP2Shell week
PHP2Shell is the name given to two memory-safety bugs in PHP's request handling: CVE-2026-109618 and CVE-2026-109619. Both are rated CVSS v4.0 9.2 and give an unauthenticated attacker code execution as the web-server user.
What made them scary is where they live. Most web vulnerabilities sit in one application, like a WordPress plugin. These sit in the language runtime underneath every PHP site, and the vulnerable code dates back to PHP 5.4 in 2012. A fully patched WordPress, Drupal or Joomla site was still exposed if PHP itself wasn't updated.
| When (UTC) | What happened |
|---|---|
| 17 Oct | Amazon's MadPot honeypots see the first exploitation attempt, four days before disclosure |
| 20 Oct | A leak claims disclosure is imminent; PHP moves its release forward a day |
| 20 Oct, ~20:00 | A video of a working proof of concept is posted by mistake |
| 20 Oct, 21:57 | First exploit bodies hit honeypots, from Google Cloud IPs |
| 21 Oct, 03:02 | Cloudflare turns on seven WAF rules for free and paid plans |
| 21 Oct, ~13:30 | PHP releases 8.2.35, 8.3.36, 8.4.27 and 8.5.12 |
| 21 Oct, 14:19 | watchTowr reports active exploitation attempts against both CVEs |
| 22 Oct | Cloudflare reports millions of attempts, many trying to install "Potassium", a Mirai-derived DDoS botnet |
| 23 Oct | PHP discloses a third, configuration-dependent flaw; Censys counts 34.4 million PHP services on vulnerable versions |
Within 45 minutes of disclosure, Cloudflare saw tens of thousands of attempts. By early 22 October it was millions. As of the latest reports, there's no confirmed successful remote code execution in the wild, but several government, school and company services went offline as a precaution.
How the exploit chain works
- Recycle workers. The attacker keeps hitting the same PHP worker, so stale state from one request survives into the next.
-
Leak memory. Oversized
PHPSESSIDcookies leak up to ~800 KB of heap, revealing memory addresses and defeating ASLR. - Corrupt memory. A crafted multipart upload either exhausts the 128 MB memory limit mid-parse (109618) or reuses freed upload-progress state (109619).
- Run commands. Public tooling chains this into command execution. The observed next step was downloading the Potassium botnet.
The fix is simple: upgrade PHP, then restart php-fpm, Apache or FrankenPHP. Running workers keep the vulnerable code in memory until they restart.
What the data showed
Across three daily snapshots, MatrixMonitor logged 300 high-confidence attacker listings: 295 unique IPs from 49 countries, hitting 69 countries. Every single one had an AbuseIPDB confidence score of 100.
Who attacked
US-hosted IPs led on every one of the three days (25, 20 and 20 IPs). China's count halved from 17 to 9 over the same window. By region, it was a three-way tie: East Asia 25%, Europe 24%, North America 24%.
One caveat matters here. "Hosting country" is where the server sits, not where the person behind it sits. Many attackers rent cloud or VPS machines, so this map shows infrastructure, not nationality.
Who was hit
Germany, the US and France were the top three victims in every snapshot. The busiest single route was US → Germany, with 61 IPs. India was hit by 32 attacker IPs, ranking 23rd of 69.
The most telling number: the average attacker hit about 9 countries. This isn't a hacker choosing a target. It's automated botnets spraying the whole internet and seeing what answers.
A second caveat: AbuseIPDB records the country of whoever reported the IP. Germany, the US and France host lots of hosting companies, honeypots and security vendors, so they rank high partly because they report a lot. Indian servers are probably under-represented for the opposite reason.
What they were doing
The median IP carried 5 attack categories. The same machines brute-force logins, abuse SSH, scan ports and probe web apps.
| Behaviour | Share of IPs |
|---|---|
| Brute-force | 93% |
| SSH abuse | 72% |
| General hacking | 71% |
| Port scan | 68% |
| Web app attack | 52% |
| DDoS participation | 16% |
| SQL injection | 7% |
Day by day
- 20 Oct (leak day): the highest web-app share, with 59 IPs flagged for web-app attacks. Six SQL-injection IPs, four of them on Google Cloud ranges.
- 21 Oct (patch day): web-app attacks dropped to 42 while email spam doubled from 20 to 41. Spam hosts crowded the web attackers out of the top 100.
- 22 Oct (millions of attempts): web-app attacks back up to 54, SQL injection peaked at 8, and attacking countries hit a high of 34.
Only 5 of the 295 IPs appeared on more than one day. Blocking yesterday's IPs barely helps today; blocking their networks does.
The detective work: did these attackers touch PHP2Shell?
Short answer: there's no direct match, but there are two leads worth following. I compared every IP in my export against the ~30 PHP2Shell-related IPs published by Amazon, Wordfence, Sophos and a honeypot study.
| Check | Result |
|---|---|
| Exact IP matches | 0 of ~30 published IPs |
| Shared /24 networks with published attack IPs | 2 |
| Google Cloud-range IPs in my data | 14 |
Lead 1: same neighbourhood. Two IPs in my data sit in the same /24 block as published attackers. One shares a network with a Sophos-linked attack origin. The other shares one with the lead upload-progress actor on a honeypot fleet, which sent 269 exploit requests. A shared /24 usually means the same hosting customer or provider. It's a lead, not proof.
Lead 2: the Google Cloud Belgium pattern. Honeypot researchers saw PHP2Shell memory-pressure probes coming from Google Cloud machines in Belgium. On 22 October, four Google Cloud Belgium IPs entered my top 100, all reported for SQL injection and web-app attacks. That's the strongest signal in the data, because attackers often rent short-lived cloud VMs for exactly this kind of spraying.
Why there's no visible spike
I expected the PHP2Shell wave to light up the globe. It didn't, and the reason is my own pipeline:
- MatrixMonitor keeps only 100 IPs per day out of a blacklist of thousands.
- Every IP scored 100 confidence, so the top 100 was effectively arbitrary.
- AbuseIPDB has no PHP-specific category, so the closest signal is "Web App Attack" and "SQL Injection".
The data shows the mix of attackers that week. It can't show the volume of the PHP2Shell wave. To confirm the leads, the next steps would be checking web-server logs for these IPs, pulling AbuseIPDB report comments (which often name the exact URL attacked), and running an ASN lookup on the shared networks.
What the analysis taught me about my own system
The biggest outcome of this report wasn't about attackers. It was finding five design flaws in MatrixMonitor that I'd never have noticed without real data pushing on it.
-
My "top 100" wasn't really a top 100.
GetAllTopLiveAttackssorted only by confidence score, and every IP scored 100. The selection was effectively random. Ranking by report count or distinct reporters would surface the genuinely most active attackers. - I threw away volume. I didn't store report counts or per-report timestamps, so a spike like PHP2Shell couldn't show up even in principle.
- I didn't store attacker country. MaxMind already returns the ISO country code, but I kept only latitude and longitude. The report had to work countries out from coordinates, and 49 IPs sat on MaxMind's "country centre" placeholders.
- "Victim" means "reporter". The victim country is where the person or sensor that reported the IP sits. That's the best proxy AbuseIPDB offers, but the UI should say so.
- Time zones confused even me. The data is stored in UTC, but the site shows dates in the viewer's time zone. From India, the 21 October page shows the snapshot taken at 23:15 UTC on 20 October. That's correct, and still confusing.
What I'm building next
| Feature | Why this week needed it |
|---|---|
| Rank by total reports and distinct reporters | Makes the daily top 100 the genuinely most active attackers |
| Store report counts and timestamps | Turns a campaign like PHP2Shell into a visible spike |
| Store country ISO code and ASN | Exact breakdowns like "Google Cloud, Belgium" without guesswork |
| Threat-intel overlay | Flags IPs and /24s that match published IOC lists right on the globe |
| Category trend panel | A daily chart of web-app and SQL-injection share, the clearest PHP signal |
| UTC toggle | Ends the 20-vs-21 October confusion |
| ML.NET spike detection and live SSE streaming | The original spec's plan, now with a concrete reason to build it |
Takeaways
For anyone running servers:
-
Patch PHP and restart the workers. Upgrade to 8.2.35, 8.3.36, 8.4.27 or 8.5.12. If you can't patch yet, set
session.upload_progress.enabled = 0. - Block networks, not IPs. With 290 of 295 IPs gone the next day, /24 blocks and cloud-range rate limits do far more than IP blocklists.
- Harden SSH and logins. Brute-force showed up on 93% of IPs and SSH abuse on 72%. Use key-only SSH, fail2ban or CrowdSec, and MFA on admin panels.
- Report your attackers. Indian servers are likely under-represented in AbuseIPDB because few operators here report. More reports make everyone's picture sharper.
For anyone building side projects: point your project at a real event. MatrixMonitor worked fine on quiet days. It took a global incident to show me what it was actually measuring, and what it was missing.
You can replay the PHP2Shell week on the globe yourself. If you're in India, these dates show the 20–22 October UTC snapshots:
The code is on GitHub at AniruddhaGawali/Matrix-Monitor. Feedback and pull requests are welcome.
Sources
- The Hacker News: PHP issues emergency patches for two critical PHP2Shell RCE flaws
- AWS Security Blog: Amazon Threat Intelligence identifies PHP2Shell exploitation prior to disclosure
- Google Cloud: Responding to CVE-2026-109618 and CVE-2026-109619
- CERT-EU Security Advisory 2026-018
- The Hacker News: PHP2Shell exploit attempts hit millions
- OffSeq: PHP2Shell in the wild, the first 48 hours on a honeypot fleet
- Security Affairs: PHP2Shell, millions of attacks, no confirmed successful exploits yet
- Data: MatrixMonitor export (AbuseIPDB + MaxMind GeoLite2), 20–22 October 2026 UTC



Top comments (0)