Removing an infected regular plugin does not guarantee that WordPress has been cleaned. An attacker may leave a mechanism for regaining access—persistence—in the wp-content/mu-plugins directory or in one of the drop-in files that load automatically.
These files are easy to miss during a standard review: they do not always appear in the regular plugin list, and some are required by legitimate caching systems, Redis, or other infrastructure components. An audit should therefore combine inventory, code analysis, ownership and modification-time checks, isolation, and post-restoration monitoring.
What Are MU Plugins and Drop-In Files?
MU plugins, or must-use plugins, are primarily stored in wp-content/mu-plugins. WordPress loads them automatically without normal activation through the administration panel. This is convenient for corporate policies, security checks, custom integrations, and hosting modules, but it also makes the directory attractive for concealing malicious code.
A drop-in is a special PHP file in wp-content that WordPress or a plugin uses instead of a separate standard component. After an incident, advanced-cache.php, object-cache.php, and db.php are the files most commonly checked. For example, object-cache.php may be part of Redis Object Cache, while advanced-cache.php may belong to a page-caching system. The mere presence of such a file does not prove that the site is infected.
By default, WordPress loads MU plugin PHP files from the root of the mu-plugins directory. Files in subdirectories may not be loaded automatically, but they should still be checked: a legitimate MU plugin may have its main file in the root and additional code below it.
- MU plugins should not be assessed solely by whether they appear in the administration panel.
- An unknown file with a random name, a recent modification time, and obfuscated code requires separate investigation.
- The filename, modification time, and location are signals for investigation, not independent proof of maliciousness.
Prepare a Safe Post-Incident Audit
Do not begin with mass deletion. First restrict access to the site or place it in maintenance mode, if this can be done without losing evidence. Create a copy of the files and database in a location that is inaccessible from the web. For an investigation, preserve the original file metadata where possible and record when commands were run.
If there are signs of an active attack, change passwords after the initial evidence collection: SSH, hosting panels, SFTP, databases, WordPress accounts, and external services. Also check SSH keys, cron jobs, and CI/CD access. Otherwise, the attacker may rewrite a file that has already been cleaned.
Work as the user who has access to the files, and do not use root unless necessary. Store copies of suspicious files outside the web root, and do not execute them in a browser or through PHP.
- Create a separate copy of the compromised state before editing anything.
- Record the domain, IP address, server time zone, and approximate time when the incident was detected.
- Do not open a suspicious PHP file through a URL: even a simple view may trigger its execution.
- If the site processes payments or personal data, involve an incident response specialist before destroying evidence.
Inventory MU Plugins and Drop-In Files
Start with an inventory. The WP-CLI command displays the MU plugins known to WordPress. Separately locate drop-in files in wp-content, because they are not regular plugins and must be analyzed differently.
Run the commands from the WordPress installation directory. If WordPress is installed elsewhere, replace the path in the commands. Save the results as part of the investigation so they can later be compared with a follow-up check.
- Compare the MU plugin list with the documentation for the hosting provider, developers, and installed caching systems.
- Pay attention to files that have no clear name, comment, or corresponding plugin.
- Check not only the three best-known drop-in files, but also other PHP files located directly in wp-content.
wp plugin list --status=must-use
find wp-content/mu-plugins wp-content -maxdepth 1 \
-type f \( -name 'advanced-cache.php' -o -name 'object-cache.php' -o -name 'db.php' \) -ls
find wp-content/mu-plugins -type f -name '*.php' -print
Check File Metadata, Ownership, and Content
The modification date helps correlate a file with the attack timeline, but it is not proof: a legitimate cache update or migration may have changed the timestamp. Ownership and permissions must also be considered in the context of the server. It is suspicious if a PHP file belongs to a different system user than the rest of the site files, or if it is writable by the web process without an obvious reason.
When reading the code, look for a chain of behavior rather than a single word: receiving data from a request, decoding it, writing it to a file, executing PHP, or connecting to an external server. Some legitimate libraries may use base64_decode or HTTP requests, so conclusions should be based on the surrounding context.
Pay particular attention to eval, base64_decode, gzinflate, str_rot13, unusually long encoded strings, dynamic include or require statements, calls to shell_exec, system, passthru, curl_exec, and wp_remote_get to unknown domains. Code that checks for a secret GET parameter, cookie, or header and then performs a hidden action is also a dangerous signal.
- Compare the file with a clean version from the official package or repository, using the same version.
- Review the first and last lines: malicious code is often appended to the end of a legitimate file.
- Check PHP files for unnecessary write permissions.
- Do not automatically consider minimized or obfuscated code malicious, but require confirmation of its origin.
stat wp-content/mu-plugins/*.php wp-content/advanced-cache.php \
wp-content/object-cache.php wp-content/db.php 2>/dev/null
find wp-content/mu-plugins wp-content -maxdepth 1 -type f -name '*.php' \
-printf '%M %u:%g %TY-%Tm-%Td %TH:%TM %p\n'
grep -RInE 'eval\s*\(|base64_decode|gzinflate|shell_exec|passthru|system\s*\(|assert\s*\(' \
wp-content/mu-plugins wp-content/*.php 2>/dev/null
Distinguish a Legitimate Drop-In from a Malicious One
A drop-in should be checked together with the component that installed it. Determine whether Redis, page caching, object caching, or other infrastructure is in use, and whether the file matches that component's documentation. If the plugin has been removed but the drop-in remains, it may be either a legitimate deinstallation remnant or persistence.
It is useful to check the file header, comments, namespace, class names, and library version. Then compare its checksum with a clean copy. A direct text comparison is more reliable than trusting the filename, but the versions and configuration must match.
Do not replace object-cache.php or advanced-cache.php arbitrarily. Removing one may disable caching, cause Redis connection errors, or change the site's behavior. Before isolating it, create a copy and coordinate the action with the person responsible for the infrastructure.
- Determine which component created the file and when it was installed or updated.
- Check whether connections to Redis, Memcached, or an external endpoint match the expected infrastructure.
- Compare the suspicious file's hash with the hash of a known-clean copy.
- After replacement, test login, caching, database writes, cron, and critical forms.
sha256sum wp-content/mu-plugins/*.php \
wp-content/advanced-cache.php wp-content/object-cache.php wp-content/db.php 2>/dev/null
wp core verify-checksums
Isolate Suspicious Code and Verify the Result
If a file shows signs of compromise, first move it to quarantine outside the web root or rename it, but only after creating a copy. The safer option is to restore a clean file from a trusted source rather than manually deleting a few suspicious lines. If the origin of a drop-in is unknown, temporarily disable the related mechanism only after assessing the impact.
After isolation, review web server, PHP, WordPress, and WAF logs. Look for requests to suspicious endpoints, attempts to write to wp-content, POST requests to administrative or unknown URLs, errors connecting to external domains, and the file reappearing.
Check the site from an unauthenticated session, separately test the administration panel and key functions, and then repeat the inventory after some time. If the file returns, the problem likely remains in another plugin, account, cron job, theme, or server access path.
- Do not delete the only copy of a suspicious file before evidence collection is complete.
- Check whether new PHP files have appeared in uploads, cache, languages, or temporary directories.
- Check WordPress-level and server-level cron jobs.
- After cleaning, update the core, themes, and plugins from trusted sources, and revoke compromised sessions and keys.
find wp-content -type f -name '*.php' -printf '%TY-%Tm-%Td %TH:%TM %u:%g %p\n' \
| sort
wp cron event list
# After isolation, review new entries in the relevant server and PHP logs.
Common Audit Mistakes
The most common mistake is assuming that the active plugin list represents all executable WordPress code. MU plugins, drop-in files, the theme, wp-config.php, and files in uploads may all be overlooked.
The second mistake is deleting every unfamiliar file without checking it. This can break caching or destroy important evidence. The opposite extreme is leaving a suspicious file in place simply because the site still works. Functionality does not prove that a backdoor is absent.
You should also avoid relying on a single antivirus scanner or searching only for individual signatures. Malicious code may be disguised, distributed across multiple files, or used to download a payload from another source.
- Do not confuse unusual code with proven infection, but do not ignore it without checking its origin.
- Do not restore a backup until its date and state have been verified: it may contain the same persistence mechanism.
- Do not limit remediation to cleaning files; change passwords and keys and review access.
- Do not close the incident as soon as the homepage loads: review logs and check whether changes reappear.
Frequently asked questions
Is every MU plugin malicious?
No. MU plugins are often used for hosting, corporate integrations, security policies, and caching. The plugin type itself is not suspicious; concern arises from unknown origin, obfuscation, unexpected behavior, inappropriate ownership, or a connection with the incident timeline.
Can I simply delete advanced-cache.php or object-cache.php?
You should not. The file may be a legitimate part of a page cache or Redis installation. First create a copy, identify the owning component, compare the file with a clean version, and assess the consequences of disabling it. If the file is infected, replace it with a verified clean copy and test the site.
Do eval and base64_decode prove that malware is present?
No. An individual function is not proof. Legitimate code sometimes decodes data or uses complex libraries. However, combining these functions with hidden parameter handling, file writes, dynamic includes, or external control is a serious reason to isolate the file and perform deeper analysis.
What should I do if the suspicious file reappears after deletion?
This means the source of persistence has not been found or the attacker still has access. Check cron jobs, other plugins and themes, wp-config.php, user accounts, SSH keys, write permissions, logs, and server processes. Change access secrets and, if necessary, restore the site from a verified clean copy.
Conclusion
Auditing MU plugins and drop-in files should be a separate stage of WordPress compromise response. Check the inventory, origin, metadata, code, and hashes, then isolate only what has been confirmed as dangerous or has no legitimate explanation.
After cleaning, it is important not only to restore the site's operation but also to confirm that the file does not return, access has been revoked, and logs show no renewed activity. This approach reduces the risk of reinfection without disabling necessary caching mechanisms without sufficient cause.