Guide · Core-file includes

A line of escaped characters above WordPress’s own code is a loader, not a typo.

You opened index.php, wp-blog-header.php, wp-load.php or wp-config.php and found a line above the normal code: an @include followed by a string of \x2f and \x68 sequences, or of three-digit numbers after backslashes. It is an obfuscated path to a second file, usually hidden somewhere with an innocent name such as favicon.ico, and it runs on every page load. This guide covers how to find every copy, read the path without running it, and remove both halves in an order that holds.

Symptom
An @include or @require with an escaped path above the normal code of a root PHP file
Mechanism
A loader in a file WordPress runs on every request, pointing at a hidden payload
Usual target
A file named like an image, icon, or text file, often with a leading dot
First rule
Read and record both files before removing either

What it is

What an obfuscated @include at the top of a core file does

PHP’s include statement loads and runs another file. The @ in front suppresses any error, so if the target ever goes missing the site keeps working and nobody sees a warning. Put that one line at the top of index.php, which runs on every front-end request, and the attacker’s file runs on every front-end request too — before WordPress, its plugins, or any security plugin has loaded.

The path is written as escape sequences so that it does not read as a path. In a PHP double-quoted string, \x2f is a forward slash written in hexadecimal and \057 is the same character in octal; a whole path can be spelled this way, often mixing both forms and plain letters. Searching the site for the name of the payload finds nothing, because the name never appears in readable form.

The target is chosen to be overlooked. A file called .favicon.ico in wp-includes/images, a .png inside an uploads folder, a .txt in a cache directory: PHP does not care about the extension, because include runs whatever the file contains. Image and icon files are also exactly what most people skip when they review a hacked site.

Figure 1 · The same file, clean and injected

One line above the comment block loads a PHP file disguised as an icon.

index.php · clean
<?php
/**
 * Front to the WordPress application. This file
 * doesn't do anything, but loads
 */

The opening tag, then WordPress’s own documentation comment.

index.php · injected
<?php
@include "\x2fh\x6fme\x2facct\x2fpublic_html\x2fwp-includes\x2fimages\x2f.favicon.ico";
/**
 * Front to the WordPress application. This file

An error-suppressed include of an escaped path, above the comment, to an icon file that is really PHP.

The top of index.php, clean and injected. The escaped string decodes to /home/acct/public_html/wp-includes/images/.favicon.ico; the account name and path are illustrative. Illustrative reconstruction.

Where to look

Which files carry the include, and how to check each one

Work from the WordPress root over SSH or a file manager with hidden files shown. Every check here only reads.

  1. 01

    Compare the first lines of the root files

    A clean index.php, wp-blog-header.php and wp-load.php start with <?php and a documentation comment. Anything between the opening tag and that comment deserves a closer look:

    shell
    head -n 5 index.php wp-blog-header.php wp-load.php wp-settings.php wp-config.php

    Some injections sit on the same line as the opening tag, padded with spaces so the code runs off the edge of an editor window. Scroll right, or check the line length.

  2. 02

    Search the root files for includes and escaped strings

    WordPress’s own includes use __DIR__ or ABSPATH and plain text; none uses @ or escape sequences:

    shell
    grep -nE '@(include|require)|\\x[0-9A-Fa-f]{2}|\\[0-7]{3}' index.php wp-blog-header.php wp-load.php wp-settings.php wp-config.php

    In wp-config.php, ignore matches inside the authentication keys and salts: those are random strings and can contain anything.

  3. 03

    Search the whole installation for the same pattern

    An escaped @include is rarely placed only once. This lists every PHP file that includes an escaped path:

    shell
    grep -rlE '@(include|require)(_once)?[[:space:]]*[(]?[[:space:]]*.\\([xX][0-9A-Fa-f]{2}|[0-7]{3})' --include='*.php' .

    Theme functions.php files and plugin main files are the usual second places. Each match is a separate loader to record.

  4. 04

    Let WP-CLI compare core against WordPress.org

    Core files that differ from the official release for your version, and files that should not exist in wp-admin and wp-includes:

    WP-CLI
    wp core verify-checksums

    wp-config.php is not part of the release, so this check cannot see an include there — the grep above can. WP-CLI commands that load WordPress also run wp-config.php, so check that file first.

  5. 05

    Find PHP hiding in image, icon and text files

    Files with non-PHP extensions that contain a PHP opening tag:

    shell
    find . -type f \( -name '*.ico' -o -name '*.png' -o -name '*.gif' -o -name '*.jpg' -o -name '*.txt' \) -exec grep -l '<?php' {} + 2>/dev/null

    A real image never contains <?php. Every result is a payload, whether or not an include currently points at it.

Read it without running it

How to decode the escaped path safely

You need the decoded path to find the payload, and you should get it without executing anything. Two safe ways: paste the quoted string into the snippet decoder on this site, which works entirely in your browser, or let the shell’s printf turn the escapes back into characters. Copy only the escaped text between the quotes, and keep it inside single quotes so the shell does not interpret it:

shell
printf '%b\n' '\x2fh\x6fme\x2facct\x2fpublic_html\x2fwp-includes\x2fimages\x2f.favicon.ico'

Do not decode it with php -r or an online PHP sandbox that evaluates code. A PHP double-quoted string can contain expressions that run when the string is evaluated, so an attacker’s string is not safe to hand to PHP even when it looks like data.

Removing it

The order that makes the removal hold

The include and its target are two halves of one mechanism, and something wrote both. Remove them together, after recording them, and then deal with whatever wrote them.

  1. 01

    Preserve both halves

    Copy every file carrying the include, and every target it points to, with their timestamps, into a directory outside the web root, and record the hashes:

    shell
    mkdir -p ~/incident
    cp -p index.php wp-blog-header.php wp-load.php wp-config.php ~/incident/
    sha256sum ~/incident/*

    Add each decoded target path to the copy and the hash list. The modification times bound the window everything else gets checked against.

  2. 02

    Replace the core files from a clean release

    Re-download the version you are running over the existing core files, leaving wp-content alone:

    WP-CLI
    wp core download --skip-content --force --version=$(wp core version)

    This restores index.php, wp-blog-header.php, wp-load.php and the rest of core. It does not touch wp-config.php, which is not part of the release: remove the include line from it by hand, keeping everything else.

  3. 03

    Remove every payload the includes pointed to

    Delete the decoded targets and any other PHP-in-image files the search found, then run the searches again. A clean result on both is the bar, not the absence of the one line you started with.

  4. 04

    Find what wrote the include

    Something had write access to the WordPress root: a vulnerable plugin, a backdoor elsewhere, stolen FTP or control-panel credentials, or a neighbouring site in the same account. Removing the include without finding that mechanism means it comes back.

  5. 05

    Rotate every credential the site holds

    Control panel, SFTP and FTP, the database user, WordPress administrators and application passwords, and the authentication keys and salts in wp-config.php — from a device that is not part of the incident.

  6. 06

    Re-check after a few days

    Repeat both searches and the checksum check. An include that reappears means a persistence layer is still running: a scheduled task, an auto_prepend_file directive, a database loader, or another account.

What else to check

An include in a core file is rarely the only layer

A loader at the top of index.php is the visible end of an infection, and it is usually not alone. In the ClickFix incident documented on this site, the same chain lived in obfuscated PHP files, base64 content in the options table, the active theme’s functions.php, and a .user.ini startup directive at once. Any surviving layer can put the include back.

Drop-ins and must-use plugins

advanced-cache.php, object-cache.php and files in mu-plugins load before ordinary plugins without being activated, and are a natural second loader.

The options table

Encoded loader material stored in autoloaded options survives every file replacement, because it is not a file.

Plugins that are not what they say

A plugin folder named like a real plugin, or a modified hello.php, is a common place for the code that writes includes.

Other sites in the same account

One hosting account is one incident. Run the same searches across every WordPress installation the account holds.

Common questions

Common questions about @include malware in core files

Is a line like @include "\x2fhome\x2f…" ever legitimate?

Not at the top of a WordPress core file. WordPress includes its own files with __DIR__ or ABSPATH and plain text, never with the @ operator and never with an escaped path. Some plugins do use escape sequences for other reasons, but an escaped include placed above the opening comment of index.php, wp-blog-header.php or wp-load.php has no legitimate explanation.

I removed the line and it came back. Why?

Because whatever wrote it is still running. Common causes are a second loader elsewhere — a drop-in, a must-use plugin, an auto_prepend_file directive, or a database loader — a scheduled task that rewrites the file, or credentials the attacker still holds. Remove the include, its target, and the mechanism together.

The include points to a .ico or .png file. Is that file safe to open?

Open it in a text editor from the filesystem, not in a browser. It is a PHP file with an image extension; requesting it over HTTP may execute it, and an image viewer will simply fail to show it. Record its hash and timestamps before you remove it.

Does replacing WordPress core remove it?

Replacing core removes the include from index.php, wp-blog-header.php, wp-load.php and other core files. It does not touch wp-config.php, which is not part of the release, or the payload the include pointed to, or whatever wrote the line. Each of those needs its own step.

Wordfence or my host’s scanner did not flag it. How can it be malware?

Scanners match patterns, and an escaped path is designed not to match. A core-file comparison does catch the change to index.php and wp-load.php, but not to wp-config.php. The searches in this guide look for the shape of the injection rather than a known signature.

Can I just delete the payload and leave the include?

Deleting the payload alone leaves a working loader: the @ hides the error, and the next time anything writes a file to that path, it runs again. Remove both halves, after preserving them, and then find what wrote them.