Last updated September 29, 2026. The site has stayed clean since we removed it. If that changes, we will update this post, because that outcome is part of the story.

We cleaned a client's WordPress site this month that would not stay clean. We would delete the malicious code, and a day or two later it was back in files we had already fixed. When we searched the strings in it we got nothing. No forum threads, no scanner signatures, no writeups anywhere.

So we are publishing what we found, because if you are looking at /*WDG-CORE-START*/ in one of your PHP files right now, you are getting the same zero results we did.

Two things up front. We are not publishing the full working payload, just the markers, paths, function names and detection commands, which is everything you need to find it and nothing anyone needs to run it. And we never identified how the attacker got in originally. We say more about that near the bottom, because pretending otherwise would make this post less useful, not more.

How do I know if I have this?

The clearest sign is that the malware comes back after you remove it. That is the symptom that brought us in, and it is what separates this from ordinary injected spam code.

Other things we saw, in plain terms:

  • Code you deleted reappears in the same files within a day or two, with no FTP or admin logins in the logs to explain it.
  • A wp-content/mu-plugins folder exists on a site that never had one.
  • wp-config.php or your theme's functions.php has a chunk of PHP stuck on the very bottom, below where the file should end.
  • Your security plugin reports nothing. This one is not obfuscated and does not use the tricks scanners key on, so it reads as ordinary code.

If you want a 30 second check and you have SSH, skip to the grep commands below. If you do not, open your wp-config.php in your host's file manager and search the page for WDG.

What is the WDG-CORE WordPress malware?

WDG-CORE is a self-replicating WordPress backdoor that gives an attacker remote command execution, file upload, and instant admin login on your site. We are calling it WDG-CORE because that is the comment marker it wraps itself in. As far as we can tell it has no public name yet.

Every infected block starts and ends with these two comments:

/*WDG-CORE-START*/
/*WDG-CORE-END*/

Every function in it is prefixed wdg_. The ones you will see are wdg_mk, wdg_core_src, wdg_has_marker, wdg_append_core, wdg_write_core, wdg_reseed_all, wdg_autologin, wdg_shell and wdg_go.

Near the top there is a hardcoded key:

$wdg_k = '3896e6c4462108db';

Yours will probably be different. It is a 16 character hex string and it matters, because the malware builds filenames and usernames from it. Write yours down before you delete anything.

Why does this WordPress malware keep coming back after I delete it?

It comes back because every copy can rebuild every other copy, and one of them runs on roughly 1 percent of your page loads whether or not the attacker is doing anything at all.

The mechanism is simple. A function called wdg_core_src() reads whatever file it is currently sitting in, finds its own START and END markers, and grabs that block as a string. Then wdg_reseed_all() writes that string into every location on its list that does not already have the marker.

There are two ways it fires:

  • The attacker loads any URL on your site with ?wdg=YOURKEY on the end, which runs the whole routine immediately.
  • On every other request, it rolls a 1 in 100 chance and, on a hit, schedules the reseed to run when the page finishes loading.

That second path is what breaks cleanups. On a site doing a few thousand page views a day, missing even one copy means the rest are restored within hours. You did not fail to remove it. You removed four of five, and the fifth put them back.

Every file WDG-CORE reinfects

There are five locations, and they are hardcoded in the malware, so for this variant that is a complete list. Check all five even if you only found code in one.

  1. wp-content/mu-plugins/wdg-XXXXXXXX.php. The filename comes from your key. It creates the mu-plugins folder if you do not have one.
  2. index.php in your WordPress root. Added to the bottom.
  3. wp-config.php. Added to the bottom, but only if the file contains wp-settings.php, which is how it confirms it found the real config file.
  4. functions.php of your active theme.
  5. functions.php of your parent theme. It checks both the stylesheet and template directories, so if you run a child theme that is two separate files, not one.

When it appends to a file that already exists, it writes a closing PHP tag and a fresh opening tag first:

?>
<?php

A stray ?> followed by <?php at the very bottom of wp-config.php or functions.php is a strong tell before you read a single line of the code itself.

How do I work out the mu-plugin filename?

Take the first 8 characters of the MD5 of your key, then put wdg- in front and .php behind. With the key from our sample:

php -r 'echo substr(md5("3896e6c4462108db"), 0, 8);'
# dcc05cf6
# file is wp-content/mu-plugins/wdg-dcc05cf6.php

This is the copy almost everyone misses, and it is worth understanding why. Must-use plugins do not appear in your WordPress plugins list. They cannot be deactivated from the dashboard. Plenty of sites have never had a mu-plugins folder, so nobody thinks to open one. And it loads on every request, before regular plugins do.

What does the backdoor actually do?

Once it is triggered with the right key it does three things in order: runs a command handler, reseeds itself everywhere, then logs the attacker in as an administrator.

Remote command execution. It takes a command straight out of the URL and passes it to shell_exec(), printing the output as plain text. The only authentication is the key in the URL.

Arbitrary file upload. It accepts a file upload and drops it into whatever directory the infected file lives in, keeping the original filename. Any copy of the malware is a place the attacker can plant more files.

Instant admin access. It finds your lowest numbered administrator account, sets an auth cookie for it, and redirects to /wp-admin/. No password, and nothing in your failed login logs.

It also has a standalone mode. If the file gets hit directly and WordPress is not loaded, it walks up through parent directories looking for wp-load.php, loads WordPress itself, and then does everything above. Moving or renaming the file does not neutralize it.

Does WDG-CORE create a hidden admin user?

Only if it cannot find an existing administrator, which on a live site usually means no. Check anyway, because the account it would create is predictable.

The username is sys_ plus the first 8 characters of the MD5 of the key, so with our sample key that is sys_dcc05cf6. The email is that same username at localhost.local, and the display name is set to "Site Admin" so it does not stand out in a user list.

wp user list --role=administrator --fields=ID,user_login,user_email,user_registered

Look for anything at localhost.local, anything called "Site Admin", and any admin registered around the time the site started misbehaving.

Indicators of compromise

Here is the whole thing in one block if you want to hand it to someone or drop it into a scanner. Replace KEY with the hex string from your own infection.

Markers:     /*WDG-CORE-START*/  /*WDG-CORE-END*/
Functions:   wdg_mk, wdg_core_src, wdg_has_marker, wdg_append_core,
             wdg_write_core, wdg_reseed_all, wdg_autologin, wdg_shell, wdg_go
Constant:    WDG_RUN
Trigger:     GET ?wdg=KEY on any URL
Command:     GET ?c=<shell command>
Upload:      POST file field named f
Files:       wp-content/mu-plugins/wdg-{substr(md5(KEY),0,8)}.php
             {ABSPATH}index.php          (appended)
             {ABSPATH}wp-config.php      (appended)
             active theme functions.php  (appended)
             parent theme functions.php  (appended)
User:        sys_{substr(md5(KEY),0,8)} / [email protected] / "Site Admin"
Passive:     ~1% of page loads trigger a reseed on WordPress shutdown

How do I find every copy on my server?

Grep for the marker across your entire web root, not just the one site. The marker is a literal string in every copy, so it is reliable for this variant.

grep -rl "WDG-CORE" /home/*/htdocs/ 2>/dev/null
grep -rl "wdg_reseed_all\|wdg_autologin\|wdg_shell" /home/*/htdocs/ 2>/dev/null

Then look at what else was modified recently:

find /home/*/htdocs -name "*.php" -type f -mtime -30 -ls | sort -k8

Then check your access logs for the trigger, which gives you the attacker's IP and roughly when they arrived:

grep -rh "wdg=" /home/*/logs/ /var/log/nginx/ 2>/dev/null | head -50

Run the grep across every site on the box even if you are confident it is contained. Ours turned out to be one site only, but we did not know that until we checked, and if your vhosts share a system user then a shell on one is a shell on all of them.

How do I remove WDG-CORE without it reinfecting?

Stop PHP from executing before you delete anything, or the copies you have not reached will restore the ones you just cleaned. This is the step people skip, and it is the whole reason cleanups fail.

  1. Take the site properly offline. Stop the PHP-FPM pool for that site, or point the vhost at a static maintenance page. A maintenance mode plugin does not count, because PHP is still running underneath it.
  2. Back up the infected state first. Full files and database. You want it for forensics, and you do not want a bad cleanup to be unrecoverable.
  3. Clean all five locations in one pass. Delete the mu-plugin file outright. For the other four, remove everything from /*WDG-CORE-START*/ through /*WDG-CORE-END*/, including the ?> and <?php it inserted above the block.
  4. Re-grep and confirm zero results before PHP comes back up.
  5. Rotate everything. All admin passwords and application passwords, the database user, SFTP and SSH, your control panel login, and any API keys sitting in wp-config.php. A shell could read all of it, so assume it did.
  6. Change your auth salts in wp-config.php, which kills any login cookie the backdoor handed out.
  7. Bring it back up and keep checking. We re-grepped after an hour, after a day, and weekly since. Put a reminder in your calendar, because the first week of silence does not prove much.

Where else should I look besides the five files?

Look anywhere a shell could have written, because the five files are only the malware's own persistence, not a record of what the attacker did with command execution. We can enumerate the five for you. The rest depends entirely on what they decided to do.

  • PHP files in wp-content/uploads/. There is never a good reason for one to be there.
  • WordPress drop-ins: wp-content/object-cache.php, advanced-cache.php and db.php. These load automatically and get overlooked constantly.
  • .htaccess and .user.ini, specifically any auto_prepend_file directive, which loads a PHP file before every single request.
  • The wp_options table. Check active_plugins, look for unexpected entries in the cron option, and scan for long base64 blobs in option values.
  • Crontab for the site user and /etc/cron.d/.
  • ~/.ssh/authorized_keys for every user on the server.
  • Inactive themes and all plugin folders. The reseed routine ignores them, which makes them a decent hiding spot for a second stage.

Should you clean in place or rebuild the server?

We cleaned this one in place and the site is still running, so let us be straight about that tradeoff rather than recommending something we did not do.

Cleaning in place is defensible when all of these are true, and on this site they were:

  • The infection is contained to one site, and the others on the server check clean.
  • The site holds no customer accounts, orders, payment data, or anything under a compliance obligation.
  • The server holds no credentials or API keys that reach other systems you care about.
  • You can take the site fully offline for the cleanup, so nothing races you.
  • Everything you find is the known payload, with no second stage that looks hand written.

Break any one of those and you should be rebuilding instead: fresh server, WordPress and plugins installed from clean sources, only the content tables and media migrated across after scanning, and every credential rotated on the way over.

The honest summary is that cleaning in place is a risk you accept, not a guarantee you earn. A server that ran attacker supplied commands for an unknown period cannot be proven clean by grepping for one known string. We are comfortable with the call we made on this particular site. We would not be comfortable making it on a store.

We never found out how they got in

We did not identify the entry vector, and that is the weakest part of this cleanup. We could not trace the activity back to a first request, so we cannot tell you which door they used.

This matters, so we want to be direct about it. WDG-CORE is persistence and access. It is what the attacker installed after they already had a way in. Removing it closes their convenient door. It does not close the one they originally walked through.

What we did instead, which is the realistic fallback when the evidence is gone:

  • Updated everything and removed plugins the site was not actually using, which shrinks the attack surface even without knowing which one was the problem.
  • Rotated every credential attached to the site and the server.
  • Turned on 2FA for all admin accounts.
  • Extended log retention, so that if there is a next time we are not in this position again.
  • Set a recurring grep for the marker, so reinfection gets caught in days rather than whenever somebody notices.

If you are earlier in this than we were, pull your logs before they rotate. Find the earliest request containing wdg=, note the IP, and look at everything that IP did in the hours beforehand. That window is where your entry point is, and it is worth more than anything else in this post.

Quick answers

Will a security plugin catch WDG-CORE?

Probably not by signature, at least not today. We found no existing detections for it. The code uses no obfuscation, no base64 and no eval(), which is exactly what generic heuristic scanners look for. It is plain readable PHP calling ordinary WordPress functions, which makes it quieter than most malware rather than louder.

Can I just restore from a backup?

Only if you are certain the backup predates the infection, and most people are not. Search your backup for WDG-CORE before restoring it. Restoring also does not close whatever hole they came in through, so it is a starting point and not a fix.

Does changing my WordPress admin password stop it?

No. The backdoor never uses your password. It sets a login cookie directly for an existing admin account, so it gets in regardless of what your password is. Rotate passwords during cleanup anyway, but do not treat that as the fix.

What if my key is different from the one in this post?

That is expected and it changes nothing important. The markers, the function names, the five file locations and the behavior are all identical. Only the trigger value, the mu-plugin filename and the generated username come from the key, and this post shows you how to derive all three from whatever key you find.

Is it safe to leave the site up while I clean it?

No, and this is the biggest reason cleanups fail. The reseed fires on about 1 percent of page loads. Serving traffic while you clean means racing the malware, and it is faster than you are.

Did it come back after you cleaned it?

Not so far. We removed it in September 2026 and the site has been clean on every check since. We are not treating that as proof of anything given that we never found the entry vector, and we will update this post either way.

If you are dealing with this

We handle WordPress cleanup and hardening as part of our hosting and maintenance work at The 215 Guys, so if this is sitting on a client site and you would rather hand it off, get in touch.

And if you run into a variant with different markers, a sixth reseed location, or you manage to trace the entry vector on your own infection, we would like to hear about it. As far as we can tell nobody has documented this one yet, and this post gets more useful the more of us have seen it.