A year ago, I opened one of the websites I managed and found a completely blank page.
I logged into the server and immediately saw that something was seriously wrong. Almost every file and directory had been renamed using the same strange suffix. Nothing was where it was supposed to be.
Someone—or more accurately, something—had entered the server, broken everything it could reach, and left behind the digital equivalent of a half-destroyed building.
I had several other websites hosted on the same server, so I checked those too.
They were all in the same condition.
Fortunately, the server had automated backups. I restored an earlier snapshot, the websites came back online, and the immediate crisis was over.
Then I started searching for the cause.
I reviewed logs, recent file changes, WordPress activity, server events, and anything else that might explain what had happened. But I found nothing conclusive.
Looking back, my biggest mistake was restoring the backup before preserving the compromised state.
At the time, though, my priority was understandable: the client’s website had to be online. I was under pressure, and restoring the service felt more urgent than collecting forensic evidence.
Eventually, I stopped investigating.
The websites were working again, and the case remained unresolved.
Until the exact same scenario happened again.

This time, I did not restore the backup
When the attack returned, I decided not to repeat the same mistake.
Instead of immediately rolling the server back, I put on my detective hat and started preserving evidence.
I also used GPT as a forensic-thinking partner—not to blindly execute recommendations, but to help organize the investigation, form hypotheses, challenge assumptions, and decide which evidence to examine next.
The first step was to create a separate server from one of the available backups.
This gave me an isolated environment where I could inspect logs and suspicious files without touching the live website or potentially triggering any persistence mechanism that might still be active.
The original server remained intact, while the restored copy became my investigation environment.
The first clue: finding the exact attack window
All major WordPress directories had been renamed with the same suffix:
wp-content__8a534d5
wp-admin__8a534d5
wp-includes__8a534d5
By examining filesystem timestamps, I was able to narrow down the destructive operation to a three-minute window:
June 26, between 11:02 and 11:05.
That changed the investigation completely.
Instead of searching through days of unrelated logs, I now had a precise period to examine.
Around that exact time, I found requests targeting suspicious PHP files with names such as:
wp-wlx.php
lock360.php
admin.php
style.php
One of those requests even used the following User-Agent:
ShellBot 2.0
The requests included command-like parameters for unlocking files, creating an administrator, checking existing code, downloading remote payloads, and performing other actions.
Most importantly, one of those commands was executed at the exact second the renaming operation began.
This was no longer a theory about a failed WordPress update, corrupted storage, or an accidental script.
Someone was remotely issuing commands through a webshell inside the website.

A malicious file is not always the original entry point
One of the first malicious files I found was called about.php.
Its code was heavily obfuscated and disguised with a fake security-related header. At first glance, it would have been easy to label it as the source of the compromise.
But its creation time told a different story.
The file had been created more than two hours after the main destructive event.
That meant it could not have been the initial entry point.
It was a persistence mechanism—one of the tools placed later to allow the attacker to maintain access.
This became one of the most important lessons of the investigation:
Finding malware does not automatically mean you have found the original vulnerability.
Attackers rarely rely on a single file. Once they gain code execution, they often create multiple loaders, webshells, hidden administrator accounts, scheduled events, and fallback access points.
Removing the first suspicious file you see may achieve almost nothing.
I kept moving backward through the timeline.
Another malicious file appeared inside WordPress’s default Twenty Twenty-Three theme:
themes/twentytwentythree/patterns/style.php
This was a fully capable backdoor. It could download additional payloads, create files, modify index.php, deploy webshells, manipulate timestamps, and remove itself after execution.
Its timestamps showed that it had been created one day before the visible destruction.
So I moved further back again.
The compromise was older than the visible attack
On June 24 at exactly 11:01:27, multiple PHP files had been created in deeply nested and seemingly random WordPress directories.
Examples looked like this:
wp-includes/.../uploads/docs/media/.../admin.php
wp-admin/.../tmp/resources/.../index.php
wp-content/plugins/.../languages/.../admin.php
All of them appeared within the same second.
They were owned by the website’s operating-system user, not by root.
One of the files was only 504 bytes. Its purpose was simple:
- Connect to an external domain
- Disable SSL certificate verification
- Download remote code
- Execute it directly using
eval
The file itself was only a loader. The real payload could be changed remotely at any time without modifying the local file.
A few minutes later, an identical copy of the same loader appeared in another random location. The files had the same cryptographic hash.
This strongly suggested that a central malicious script was automatically distributing backdoors and loaders across the website.
If one were removed, others would remain.
The attacker was more persistent than I was—and apparently had a better backup strategy.
Did the attacker compromise the entire server?
My biggest concern was that someone might have obtained SSH credentials or full administrative access to the server.
The authentication logs contained plenty of failed attempts. Like almost every public server, it was constantly being scanned by automated bots trying usernames such as:
root
scott
omar
But every relevant attempt ended with messages such as:
Failed password
Invalid user
preauth
I found no successful SSH logins during the attack window.
I also reviewed Cron activity, Sudo logs, and the system journal. The only notable system-level event around that time was a legitimate CloudPanel snapshot job.
No suspicious temporary files had been created in:
/tmp
/var/tmp
/dev/shm
Every malicious file appeared inside the affected website and was owned by that website’s PHP user.
Other websites on the server were also unaffected during this second incident.
The most reasonable conclusion was therefore:
The attacker most likely gained code execution through WordPress, a plugin, a theme, or a previously planted backdoor—not through SSH or full root access to the server.
Why I could not identify the exact vulnerability
The earliest confirmed malware deployment happened on June 24.
Unfortunately, the complete web access log for that date was no longer available. The server only retained a limited number of daily logs.
That meant I could reconstruct much of the attack, but could not prove the exact first request or vulnerability that initiated it.
The original entry point may have been:
- an outdated plugin;
- a vulnerable theme;
- modified third-party code;
- compromised administrator credentials;
- or a backdoor that had existed for months before being activated.
I could not responsibly claim which one it was.
What I could establish was the sequence:
- Malicious code was already able to execute inside the website.
- On June 24, it deployed multiple loaders and backdoors.
- Those access mechanisms were used again over the following days.
- On June 26, a remote command triggered the destructive renaming operation.
- Additional webshells were created afterward to preserve access.
I never found the criminal standing next to the broken window.
But I reconstructed how they moved through the building.
Restoring the same backup was no longer an acceptable solution
Once I had enough evidence, I stopped trying to clean the existing WordPress installation in place.
The number of backdoors and persistence mechanisms made it impossible to confidently declare every file safe.
I quarantined the compromised website and created a completely clean document root.
Then I:
- downloaded a fresh WordPress core from the official source;
- verified the core files using official checksums;
- disabled all plugins stored in the database;
- removed old WordPress Cron events;
- cleared transients;
- revoked every active login session;
- regenerated WordPress security salts;
- changed the administrator password;
- and removed an unnecessary administrator account I did not clearly remember creating.
I preserved the database, but treated it as untrusted data that needed inspection.
For wp-content, I did not copy the directory as a whole.
Only approved media formats—images, videos, documents, and fonts—were moved into the clean installation. PHP files, plugins, themes, caches, backups, and executable files were excluded.
The plan was to reinstall all plugins and themes from a trusted source.
That is where the next problem appeared.
The theme package I had purchased could not be independently audited
The website used a commercial theme package purchased from RTL-Theme, an Iranian marketplace that distributes localized WordPress themes and plugins.
I downloaded a fresh copy from my account and inspected it before reinstalling.
The scan did not reveal any domains, hashes, filenames, or malware patterns associated with the compromise.
So I found no evidence that RTL-Theme or the package publisher had intentionally placed the specific backdoors involved in this incident.
However, the package was not an untouched copy of the original developer’s release.
It had been repackaged with a custom installer, licensing components, localization changes, and ionCube-encoded PHP files.
Most importantly, critical theme code—including functions.php—was encoded.
In a WordPress theme, functions.php is loaded throughout the website’s execution lifecycle. If its source code cannot be inspected, I cannot independently determine:
- what it executes;
- what remote services it contacts;
- what files it creates or modifies;
- what information it collects;
- or how it differs from the original developer’s version.
Encoding does not prove malicious intent.
It may exist only to enforce licensing or prevent unauthorized redistribution.
But after a security incident, the practical result is the same: I cannot verify the code well enough to reinstall it with confidence in a client’s production environment.
That was the point at which I paused the restoration and decided the website needed to be rebuilt along a different, more trustworthy path.
My criticism is about transparency, not an unsupported accusation
To be clear, I did not prove that RTL-Theme caused the intrusion.
I also did not find the previously identified malware signatures inside the newly downloaded package.
The problem is not that I discovered a confirmed vendor backdoor.
The problem is that a critical part of the software supply chain was inaccessible to me for auditing.
When a marketplace sells a WordPress product, it is not merely selling a ZIP archive. It is supplying code that becomes part of a customer’s infrastructure, business operations, and data-processing environment.
When essential files are modified and encoded, the buyer must place complete trust in the distributor and cannot independently verify the product.
As a customer, that meant:
- I could not fully audit the code.
- I could not reliably compare it with the original developer’s release.
- I could not identify all the modifications made by the distributor.
- I could not conclusively include or exclude it during an incident investigation.
- I could not confidently reinstall it after a compromise.
For low-risk experiments, a localized or repackaged product may appear attractive because of its lower price and easier licensing.
For serious client work, the cost of uncertainty can far outweigh the price difference.
My conclusion was simple:
For production systems that matter, purchase the original product directly from its developer or an authorized marketplace.
What I learned
The first lesson was that having backups is not the same as being secure.
A backup can restore availability, but if it contains the original vulnerability or a hidden backdoor, it only resets the countdown until the next compromise.
The second lesson was to preserve evidence before restoring anything.
Create a snapshot. Isolate the compromised environment. Record timestamps. Save logs. Then begin recovery.
The third lesson was that attackers rarely rely on one obvious malicious file.
They distribute loaders, webshells, hidden access mechanisms, and fallback payloads across legitimate-looking directories. Deleting one suspicious file is not a security strategy.
The fourth lesson was that wp-content it should never be blindly copied from a compromised installation.
Themes, plugins, and the WordPress core should come from trusted sources. Only necessary data should be migrated, and even that data should be filtered and inspected.
The final lesson was about trust:
Code I cannot inspect is code I cannot confidently trust—regardless of whether I paid for it.
Breaking the cycle
This investigation did not end with the discovery of a single perfect CVE, a single guilty plugin, or a single identifiable attacker.
But it achieved something more important.
I reconstructed the attack timeline, identified multiple persistence mechanisms, ruled out several incorrect assumptions, and understood why the original restore had only delayed the problem.
This time, I did not simply return the website to its previous state.
I broke the cycle.
Sometimes solving a problem does not mean repairing what is already broken.
Sometimes it means recognizing that the foundation can no longer be trusted—and rebuilding it properly from the beginning.