Reading WordPress Malware: Decoding eval(base64_decode())
In short
Obfuscated malware in WordPress usually hides behind eval(base64_decode(…)): Base64 is not encryption but an encoding that can be reversed without any key. Decoding the string instead of running it shows what the code does, such as a backdoor, spam links or redirects. A hit for base64_decode alone is no proof, the combination with eval, gzinflate or user input is the real warning sign.
If you examine a hacked WordPress site, you almost always run into the same pattern: a line starting with eval(base64_decode( followed by hundreds of seemingly random characters. It is meant to put you off and to make simple searches come up empty. The code is still readable, and without running it. Below is how to find such spots, decode them safely and what the find tells you about the attack.
Why attackers use Base64
Base64 is not encryption. It is an encoding that translates any data into the characters A–Z, a–z, 0–9, + and /, often padded with one or two = at the end. There is no key: whoever has the string can convert it back.
For attackers this has three advantages:
- Security scanners and simple searches for telltale terms such as
$_POST,systemor foreign domains find nothing in the encoded text. - Special characters don’t get in the way. Quotes and line breaks in the malicious code don’t break PHP syntax when it is injected.
- A quick glance mistakes the block for licence data or an embedded graphic.
The code is only executed by the function in front of it. base64_decode makes the text readable again, eval runs it as PHP. That combination is the real warning sign.
Typical patterns
The basic form is a single line:
<?php eval(base64_decode('aWYoaXNzZXQoJF9QT1NUWydjJ10pKS...')); ?>
So that searches for exactly this text find nothing, it is often varied:
- Several layers:
eval(gzinflate(base64_decode('…')))oreval(str_rot13(base64_decode('…'))). The output of one layer is the input of the next. - Split function names:
$f = 'base'.'64_'.'decode';and later$f('…'). The name never appears in one piece. - Hex or chr() notation:
"\x62\x61\x73\x65\x36\x34\x5f\x64\x65\x63\x6f\x64\x65"is alsobase64_decode. - User input as a command:
eval(base64_decode($_POST['x'])). Here the malicious code isn’t in the file at all, it arrives fresh with every request. That is a backdoor through which the attacker can load any code.
Where to look
Not every hit is malware. WordPress core and reputable plugins use base64_decode as well, for example in bundled libraries. The search therefore gives you candidates, not verdicts.
With SSH access you can find the candidates in one go:
# Suspicious function calls in all PHP files
grep -rnE "eval\s*\(|base64_decode\s*\(|gzinflate\s*\(|str_rot13\s*\(" --include="*.php" .
# PHP files in the uploads folder: they don't belong there
find wp-content/uploads -name "*.php"
# Compare core and plugins with the original files (WP-CLI)
wp core verify-checksums
wp plugin verify-checksums --all
Without SSH, the route is via FTP: sort files by modification date and open the most recently changed ones. Frequently affected are:
- the top of
wp-config.phpandindex.php, - the
functions.phpof the active theme, - the
wp-content/mu-pluginsfolder, whose plugins load automatically and are easy to overlook in the backend, - files with inconspicuous names such as
wp-cache.phporclass-wp-*.phpin places where the original has no such file.
Hits reported by verify-checksums are especially telling: the file differs from the original although that was never intended.
Making the find readable safely
The most important rule: never request the infected file and never test the code with eval. Copy only the string between the quotes, or the whole line, and decode it separately.
Our tool runs entirely in your browser. You can also paste the whole PHP line, the base64_decode(…) call is extracted automatically. Nothing is executed, the result only appears as text.
Base64 tool
Encode or decode Base64
Enter text, pick a direction, copy the result. When decoding, you can also paste a whole PHP line containing base64_decode(…).
# Enter text above … Your input never leaves the browser. Decoded code is only shown as text, never executed.
Three cases are typical:
- Readable PHP code comes out. Then you are done and can interpret it, see the next section.
- The result looks like Base64 again. The code is wrapped several times. “Decode result again” takes you through it layer by layer.
- Only binary data comes out, shown as hex. Then the next layer is usually compressed with
gzinflate. The tool can’t unpack it. On your own machine with PHP installed it works like this, withevalreplaced byecho:
php -r 'echo gzinflate(base64_decode("PASTE_THE_STRING_HERE"));'
This only prints the code and doesn’t run it. Do it on your own machine, not on the infected server.
What the decoded code reveals
The plain text shows what the attacker wanted, and therefore where you need to keep looking:
- Backdoor: code that passes
$_POST,$_GET,$_COOKIEor$_REQUESTtoeval,system,execorfile_put_contents. The attacker can load new code at any time. There are then almost always more files with the same purpose. - Redirects: checks on the user agent or referrer, followed by
header('Location: …'). Typical are redirects only for visitors from Google or only on smartphones, so the owner notices nothing on the desktop. - Spam links: code that writes HTML with foreign links into the output, often only for search engine crawlers.
- New administrators: calls to
wp_insert_userorwp_create_userwith the roleadministrator. Then check the user list and remove unknown accounts. - Foreign domains: note every domain and IP address in the plain text and search for it in all files and in the database. Wherever it still appears, there is usually more malicious code.
After the find: clean up, don’t just delete
Deleting the suspicious line is rarely enough. Where there is one backdoor there are usually more, and the hole it came through is still open. The find is a clue, not a complete diagnosis.
The actual cleanup is described in our article WordPress hacked? Recognising and cleaning up malware: secure, narrow down the damage, replace WordPress core and plugins from clean sources, renew passwords and security keys, close the entry point. How to harden the site permanently afterwards is covered in the WordPress security overview.
The tool is also available on its own under Base64 encode and decode, together with our other free tools.
You found obfuscated code and want to be sure nothing is left behind? Our WordPress emergency service cleans the site and closes the hole, or drop us a line.
Frequently Asked Questions
Is base64_decode in a WordPress file always malware?
No. WordPress core and reputable plugins use base64_decode too, for example in bundled libraries or for embedded graphics. It becomes suspicious when the result goes straight into eval, assert or a variable function, when the string is very long, or when the file sits somewhere PHP does not belong, such as the uploads folder.
Can decoding itself be dangerous?
No, as long as nothing is executed. Decoding only converts characters. It only becomes dangerous when the code is passed to eval or the infected file is requested. Our Base64 tool runs in the browser and only shows the result as text.
What do I do if the result looks like Base64 again?
Decode it again. Malicious code is often wrapped several times so that simple searches miss it. If you only get binary data instead of text, the layer is usually compressed with gzinflate. You can unpack it on your own machine with PHP by replacing eval with echo.
Is it enough to delete the line I found?
Rarely. Where there is one backdoor there are often more, and the hole it came through is still open. The find shows you what the attacker wanted. The actual cleanup means replacing WordPress core and plugins from clean sources, checking accounts and passwords, and closing the entry point.
Related Services
Founder & Full-Stack Developer
20+ years of web development experience. Specialised in Laravel, WordPress and custom software for mid-sized businesses.