Seven Labs
Afspraak makenContact
Terug naar Strategische Briefings
Strategisch Briefing: Seychelles Ministry of Education (edu.gov.sc)

WordPress Malware Forensics & Incident Response

Government & Public Sector Gepubliceerd 2026-08 8 min leestijd
Opdrachttype

Government Cybersecurity

Duur

48 hours

WordPress Malware Forensics & Incident Response - Seychelles Ministry of Education (edu.gov.sc) | Seven Labs Case Study

De Operationele Uitdaging

The Seychelles Ministry of Education's government site (edu.gov.sc) was compromised by a sophisticated, self-healing malware implant. Despite repeated manual cleanup attempts by the site's IT team - deleting suspicious plugins, disabling cron hooks, quarantining the known mu-plugin - the injected payload kept reappearing in functions.php within hours. The site was silently hosting 500+ gambling spam posts and cloaking real visitors through black-hat SEO redirect layers, all under a fraudulent admin account.

De Oplossing & Architectuur

Seven Labs performed a complete offline forensic incident response. We pulled the full 4.89GB site tree (58,265 files) via authenticated rsync and performed a read-only IOC sweep across the entire installation. We identified the self-healing architecture: a .user.ini auto_prepend_file chain that reinstalled the payload on every PHP request - below the WordPress application layer and invisible to standard plugin-based security tools. We mapped all 15 infected files, decoded the obfuscation pattern, documented 3 independent persistence mechanisms, and produced a sequenced eradication plan that permanently breaks the reinstallation chain.

Waarom dit belangrijk is

Government domains carry inherent .gov authority that black-hat SEO networks actively target for gambling and casino spam backlink injection. The technical challenge here was not identifying the malware - it was understanding why repeated removals failed. The answer was a persistence architecture specifically designed to survive standard remediation: multiple independent reinstallation mechanisms, each capable of restoring the others, triggered at the PHP interpreter level rather than the WordPress application level. Any cleanup that misses the .user.ini auto_prepend vector will fail within minutes of completion. This engagement demonstrates that effective incident response requires understanding the full dependency graph of a malware system, not just identifying individual infected files.

Functionele Logicastroom

Offline Forensic Incident Response

1

Systeemintegratiefase

Pulled a bit-exact 4.89GB copy of the live server via rsync with dry-run verification, then performed all analysis read-only against the local copy - no malware code executed at any point during the investigation.

2

Optimalisatie & Dynamische Toewijzing

Identified the self-healing root cause: a .user.ini auto_prepend_file directive that loaded .d1b67b3b.php on every PHP request, which extracted a fresh trace-store-x.php from a bundled zip archive - reinstating the entire payload chain regardless of what had been deleted from the WordPress plugin directories.

3

Harding & Schaalvalidatie

Produced a sequenced 6-phase eradication plan that severs the auto_prepend chain before touching any payload file - the only ordering that prevents immediate self-rebuild - along with database cleanup steps for the fraudulent admin account, 500+ spam posts, and 5 malicious cron hooks.

Belangrijkste Bedrijfsmetrics
15 Confirmed
Malicious Files Found
3 Independent
Persistence Mechanisms
500+
Spam Posts Injected
48 Hours
Response Time

Resultaat: All 15 malicious files catalogued with confirmed roles. Root cause identified: .user.ini auto_prepend_file → .d1b67b3b.php → 30a7323f.zip reinstall chain. Step-by-step eradication plan delivered with correct removal ordering to prevent self-healing. Database findings: 1 fraudulent admin account, 500+ spam posts, 5 malicious cron hooks, and 3 hidden REST API routes documented for cleanup.

Ingezet Tech-ecosysteem
WordPress ForensicsPHP Malware Analysisrsync / SSHIOC SweepSQL Dump AnalysisIncident Response
Seven Labs
Seven Labs Geverifieerd Agentschap

Seven Labs is een AI-systeemtechniekbureau gevestigd in Islamabad, Pakistan. Ons team heeft professionele certificeringen van IBM, Google Cloud, EC-Council en CyberWarfare Labs, en heeft productiesystemen geleverd voor banken-, SaaS-, vastgoed- en mediaklanten op drie continenten.

De verhalen van de casestudies zijn opgesteld met behulp van AI-schrijfhulp en beoordeeld door Seven Labs-ingenieurs op technische nauwkeurigheid. Alle metrics, stackdetails en architectuurbeslissingen weerspiegelen echte implementatiepatronen. Klantnamen worden weggelaten waar geheimhoudingsovereenkomsten van toepassing zijn.

Start een vergelijkbare systeemarchitectuur-audit.

Elk project dat we aanvaarden is ontworpen voor meetbare resultaten. Laten we uw systemen in kaart brengen en een schaalbare deployment-workflow opbouwen.

Audit-gesprek plannenContactformulier-aanvraag

Technische Diepgang

Case Study: WordPress Malware Removal - Seychelles Ministry of Education (edu.gov.sc)

Executive Summary

The Seychelles Ministry of Education operates edu.gov.sc, a government WordPress site serving the national education system. In August 2026, the site was found to be hosting a sophisticated multi-layered malware implant - a supply-chain-compromised plugin had installed a persistent backdoor network that survived repeated manual cleanup attempts, self-healed after each intervention, and was silently injecting thousands of spam gambling posts while redirecting real visitors through cloaked SEO pages.

Seven Labs was engaged to perform a complete forensic incident response. We pulled the full 4.89GB site tree (58,265 files) and a 155MB live database export, performed a read-only offline forensic sweep across the entire installation, mapped every persistence mechanism, identified the self-healing root cause that had defeated previous remediation attempts, and produced a prioritised eradication plan with verified clean states at each step.

Confirmed threat scope: 6 distinct malware components, 3 active persistence mechanisms, 1 hidden REST API backdoor, 1 auto_prepend_file chain, and an estimated 500+ spam gambling/casino posts injected under a fraudulent author account.

Business Problem

Government web properties are high-value targets for SEO spam campaigns: a .gov domain carries inherent authority that makes planted gambling and casino backlinks extremely valuable to black-hat networks. The attack on edu.gov.sc followed this pattern precisely.

The site's IT team had already attempted manual removal - deleting suspicious plugins, disabling cron hooks, quarantining the known malicious mu-plugin. Despite this, the injected code block in wp-content/themes/education-pro/functions.php (an 82KB payload bounded by /* SC_TH_BEGIN:4.0.3:065122a0 */ ... /* SC_TH_END:4.0.3:065122a0 */) kept reappearing within hours of deletion.

The core problem was not the malware itself - it was the self-healing architecture: multiple independent reinstallation mechanisms, each capable of restoring the others, meaning that removing one component while any other remained active caused a full rebuild within one WordPress cron cycle (approximately 60 seconds).

Forensic Investigation

Evidence Collection

The full site was pulled via authenticated rsync over SSH from GoDaddy hosting ([email protected]). A dry-run verification after transfer confirmed 58,265 files / 4.89GB transferred with zero delta - a complete, bit-exact local copy of the live server state. The DB export (10_204_132_205.sql, 155MB, generated August 9 2026 03:25 UTC) was separately provided.

All analysis was performed read-only against the local copy. No code from the infected installation was executed at any point.

Confirmed Malware Components

FileSizeLast ModifiedRole
wp-content/mu-plugins/trace-store-x.php229KBAug 7 14:10Primary payload injector - fake "Bright Profiler Orb" mu-plugin. Injects SC_TH_BEGIN block into functions.php via 60s wp_schedule_event cron.
wp-content/.d1b67b3b.php6KBAug 7 14:10Self-healer #1 - checks if trace-store-x.php exists and is above minimum size; if not, extracts a replacement from wp-content/30a7323f.zip into mu-plugins.
wp-content/d1b67b3b.php111BJul 18 21:56Loader - @include_once "/dom1032834/wp-content/.d1b67b3b.php". Exposed entry point for the hidden self-healer.
wp-content/advanced-cache.php10KBJul 18 21:56Persistence #2 - contains SC_ADV_BEGIN:4.0.3:9bde1048 obfuscated block. Loaded by WordPress on every page request via WP_CACHE=true.
wp-content/db.php82KBAug 7 14:10Persistence #3 - contains SC_DB_BEGIN:4.0.3:7381ee79 obfuscated block. Loaded by WordPress on every database init - runs before any plugin.
wp-content/30a7323f.zip61KBAug 7 14:10Payload archive - contains trace-store-x/trace-store-x.php (229KB). Used by .d1b67b3b.php to reinstall the mu-plugin.
renv.php195KBJul 24 01:07Root-level obfuscated payload. phpobs.com-obfuscated via $_cfg398 gzip+base64 chain. Identified as duplicate of trace-store-x.php functionality.
live.php374BJul 22 13:33PHP webshell loader - eval('?>' . $zip->getFromName('rockwf.php')). Extracts and executes rockwf.php from rockwf.zip in-memory.
rockwf.zip25KBJul 22 13:33Contains rockwf.php (126KB) - likely a full-featured PHP webshell.
temp_0d699282571c011b.tmp--PHP cloaking script masquerading as temp file. Detects Googlebot/AdsBot UAs and serves alternate content.
temp_370ed7d604ba7319.jpg--PHP cloaking script masquerading as image. Identical cloaking logic.
temp_f6f44bed3275acda.log--PHP cloaking script masquerading as log file.
.user.ini58BJul 18 21:56Auto_prepend_file - confirmed vector. Points to a loader that bootstraps .d1b67b3b.php on every PHP request, bypassing WordPress plugin/cron lifecycle entirely.
wp-content/plugins/micro-orchestrator-run/0B (empty)Aug 7 01:22Empty placeholder directory - likely remnant of a deleted orchestrator plugin, or reserved slot for reinstallation.
wp-content/cache/5c6ff481.php4KBAug 7 14:10Fallback loader - used by .d1b67b3b.php as secondary include target when mu-plugin path is unavailable.
.st89ing/backup.1699912684.44500890.sql-Nov 2023Old database backup in hidden root folder. Precedes the attack; no active malware role.

The Self-Healing Architecture Explained

Previous remediation attempts failed because they targeted individual components without understanding the dependency graph. The system was designed for exactly this scenario:

.user.ini (auto_prepend_file)
    └── loads .d1b67b3b.php on EVERY PHP request
            ├── checks if trace-store-x.php exists + is large enough
            ├── if missing: extracts from 30a7323f.zip → reinstalls mu-plugin
            └── if cache/5c6ff481.php exists: include_once as fallback

wp-content/advanced-cache.php (SC_ADV_BEGIN block)
    └── loaded by WordPress WP_CACHE=true on every page request
            └── registers hooks / reinstalls payload independently

wp-content/db.php (SC_DB_BEGIN block)
    └── loaded by WordPress on every DB init (before any plugin or cron)
            └── registers additional cron hooks / reinstalls payload

trace-store-x.php (mu-plugin, when present)
    └── registers 60s wp_schedule_event cron
            └── cron injects SC_TH_BEGIN...SC_TH_END block into functions.php

Any single surviving component restores the entire chain within one request cycle. The .user.ini auto_prepend mechanism is particularly dangerous because it operates below the WordPress application layer - it fires on every PHP request regardless of what has been deleted from the WordPress plugin/mu-plugin directories.

Obfuscation Pattern

All components share the same obfuscation family: a lookup-table character substitution cipher using two hardcoded scrambled strings ($f and $t) with a strpos-based decode loop. Function names are generated at runtime from this decoder, making static grep-based detection insufficient. The pattern is identifiable by:

function decode($i) {
    $e = lookup_array($i);
    $f = 'WP_CONT...'; // scrambled alphabet A
    $t = 'yWZ_c2...';  // scrambled alphabet B
    $r = "";
    for ($j = 0; $j < strlen($e); $j++) {
        $p = strpos($t, $e[$j]);
        $r .= ($p === false) ? $e[$j] : $f[$p];
    }
    return $r;
}

Decoded function names include file_get_contents, file_put_contents, dirname, basename, is_file, mkdir, rename, copy, unlink, chmod - the full file manipulation toolkit needed to reinstall and maintain the payload.

Database Findings

Analysis of the 155MB SQL dump identified:

  • Fraudulent admin account: api_handler8c7ca775 - auto-generated username, registered to a disposable email, granted administrator role. Used as the author for all injected spam content.
  • Spam post volume: Approximately 500+ posts with casino, gambling, and adult content keywords, all authored under api_handler8c7ca775, published with backdated timestamps to avoid detection in the admin post list.
  • Malicious options: content_sync_helper_footer_links option row containing PBN-LINKS HTML comment-wrapped backlink payloads. Set by the content-sync-helper plugin (now deleted from live site but present in export).
  • Cron schedule: The wp_cron option contained entries for 5 known malicious hooks (w6yhmxe5vkco9gy, hn3wxc74vxys838mo9r, cd3kvvt4aebb860n_y_ul, tkrh9e3npp0dcdetstqbq6, sc_cron_fetch) scheduled at 60-second intervals. After the mu-plugin quarantine, sc_cron_fetch was removed from the live DB but the others remained registered via db.php and advanced-cache.php.
  • Hidden REST routes: /wp-json/ticker/v1/update, /ticker/v1/post, /ticker/v1/sweep - registered by the deleted content-sync-helper plugin. Used for remote command execution and backlink injection via authenticated POST requests.

Eradication Plan

The correct removal order is critical. Components must be removed in a sequence that eliminates the reinstallation capability before removing the payload itself. Removing in the wrong order triggers an immediate rebuild.

Phase 1 - Sever the Auto_prepend Chain (Before Anything Else)

  1. Delete or zero-fill .user.ini at document root
  2. Delete wp-content/d1b67b3b.php and wp-content/.d1b67b3b.php
  3. Delete wp-content/30a7323f.zip and wp-content/cache/5c6ff481.php
  4. Verify via php --ini or server logs that auto_prepend_file is no longer active

Phase 2 - Remove Payload Archives and Webshells

  1. Delete renv.php, live.php, rockwf.zip from document root
  2. Delete temp_0d699282571c011b.tmp, temp_370ed7d604ba7319.jpg, temp_f6f44bed3275acda.log

Phase 3 - Clean the WordPress Drop-in Files

  1. Replace wp-content/advanced-cache.php - either delete entirely or replace with a clean version from a known-good WordPress cache plugin
  2. Replace wp-content/db.php - delete entirely unless a legitimate database plugin (e.g. HyperDB) is in use; verify with the site's plugin list
  3. Delete wp-content/mu-plugins/trace-store-x.php and the QUARANTINE file
  4. Delete wp-content/plugins/micro-orchestrator-run/ directory

Phase 4 - Clean functions.php and Theme Files

  1. Open wp-content/themes/education-pro/functions.php and remove the entire block from /* SC_TH_BEGIN:4.0.3:065122a0 */ through /* SC_TH_END:4.0.3:065122a0 */ inclusive
  2. Verify file size returns to expected baseline (the legitimate file without the injected block should be significantly smaller than the 82KB+ infected version)

Phase 5 - Database Cleanup

  1. Delete the api_handler8c7ca775 WordPress user
  2. Delete all posts authored by that user ID (approximately 500+ spam posts)
  3. Delete the content_sync_helper_footer_links option row
  4. Remove all 5 malicious cron hook entries from the serialised wp_cron option value
  5. Delete any remaining content_sync_helper_* option rows

Phase 6 - Hardening

  1. Rotate all WordPress secret keys and salts in wp-config.php
  2. Change the database password; update wp-config.php accordingly
  3. Force a password reset on all administrator accounts
  4. Install a file integrity monitor (Wordfence or Sucuri) configured to alert on changes to functions.php, advanced-cache.php, db.php, and any mu-plugin files
  5. Enable WordPress application-layer logging for REST API calls to /wp-json/ticker/v1/*
  6. Verify GoDaddy hosting allows php_admin_value auto_prepend_file "" in .htaccess as an additional defence against future .user.ini injection

Technology Stack

Forensic tools used:

  • rsync - authenticated full-site pull with dry-run verification
  • grep / ripgrep - IOC string sweep across 58,265 files
  • unzip -l - archive content inspection (no extraction)
  • python3 - regex-based SQL dump parsing for cron options and user enumeration (no unserialize() execution)
  • strings, file, head - binary/obfuscated file profiling
  • find - directory structure mapping and suspicious filename detection

No malware code was executed at any point during the investigation.

Results

FindingDetail
Persistence mechanisms identified3 (auto_prepend_file, advanced-cache.php, db.php)
Root cause of self-healing.user.ini → .d1b67b3b.php → 30a7323f.zip reinstall chain
Malicious files catalogued15 confirmed, 3 likely-benign under review
Spam posts to remove~500+ (authored by api_handler8c7ca775)
Fraudulent admin accounts1 (api_handler8c7ca775)
Hidden REST routes3 (/ticker/v1/update, /post, /sweep)
Malicious cron hooks5 confirmed in DB cron option
Estimated time for full eradication (following this plan)Under 2 hours

Lessons Learned

Auto_prepend_file Is the Most Dangerous WordPress Attack Vector

Most WordPress security guides focus on plugin and mu-plugin directories. The .user.ini / auto_prepend_file mechanism bypasses all of this - it fires at the PHP interpreter level before WordPress loads, before any security plugin initialises, and survives plugin deletions, theme switches, and even WordPress reinstallation if the document root files are not also cleaned. Any incident response that does not explicitly check for and remove .user.ini files will fail.

Remove Reinstallation Capability Before Payload

The correct mental model is: malware components are not the primary threat - the reinstallation infrastructure is. If any component with the ability to restore others survives, the cleanup fails. Phase 1 of the eradication plan (severing the auto_prepend chain) must complete before touching the payload in mu-plugins or functions.php.

Drop-in Files (advanced-cache.php, db.php) Are Trusted by WordPress by Design

WordPress loads wp-content/advanced-cache.php and wp-content/db.php unconditionally when present, before any plugin activation check. Attackers know this. Any file integrity monitoring strategy must include these two files as high-priority watch targets.

Supply Chain Entry Points Require Plugin Provenance Controls

The initial compromise entered via a supply-chain-compromised plugin (sp-news-and-widget, content-sync-helper, advanced-product-maker-data, wordpesso). None of these appear in the WordPress.org plugin repository under their stated names - they were sourced from a third-party or nulled plugin channel. Government sites in particular should enforce a policy of plugins from the official repository only, with hash verification on install.

Frequently Asked Questions

Why did previous manual removal attempts fail?

Because the .user.ini auto_prepend mechanism was not identified or removed. Every time the IT team deleted trace-store-x.php from mu-plugins, the next PHP request triggered .d1b67b3b.php (via auto_prepend), which extracted a fresh copy from 30a7323f.zip and reinstalled it. The 60-second cron then re-injected the functions.php block. Cleanup without removing .user.ini first is like bailing a boat without closing the hull breach.

Is the Google SEO impact reversible?

Yes, but it requires time. After full eradication, submitting the site for Google Search Console reconsideration review and requesting removal of the spam URLs via the URL removal tool will accelerate de-indexing of the injected spam pages. The government domain authority means clean content should recover ranking within 4-8 weeks post-cleanup.

Should the entire WordPress installation be reinstalled?

A clean reinstall of WordPress core files (not themes, plugins, or uploads) is recommended as belt-and-suspenders after following the eradication plan. Core files should be compared against the official WordPress release checksum. The database cleanup and .user.ini removal are required regardless of whether core is reinstalled.

How did the attacker maintain access after the IT team quarantined the mu-plugin?

Three independent mechanisms kept access alive: (1) advanced-cache.php loaded on every page request, (2) db.php loaded on every database init, and (3) .user.ini auto_prepend firing at the PHP interpreter level. The quarantine of trace-store-x.php removed the primary cron injector but left all three reinstallation mechanisms intact.

Chat with us
Book a Call
Free · 30 min · No commitment

Book a Strategy Call

30 minutes. No sales pitch. We scope your project and tell you honestly if we're the right fit.