How to Efficiently Clean Hidden Link Injections in Your Hacked WordPress Sites

A lightbulb.

Recently I had a very sobering experience that demonstrated just how serious the threat of hacked WordPress sites is. I discovered hidden link injections in some posts on a few of my old sites. These are sites that I no longer actively maintain, they were kind of neglected, and haven’t been updated in a while.

The hack inserted links to drug sites (the viagra and cialis bunch) using “display: none” attribute to hide them from the page. I think Google disregards these hidden type of links, but having them can penalize your site nevertheless.

Creating a Solution

Having assessed the situation, I decided I needed a code snippet that I can run on these sites to find all suspicious posts and later clean them.

I came up with the snippet below. It has two modes, preview and update. In the preview mode (on by default) it will only list suspicious posts which is useful for testing as you can manually inspect the posts to see if they are indeed infected. When you change $preview_only to 0 you activate the update mode where it will also attempt to clean the posts (effectively replacing the link with blank).

The snippet is currently set to look for “display: none” pattern which is commonly used to hide links.

  1. // By default only preview infected posts. Change to 0 to clean posts
  2. $preview_only = 1;
  3.  
  4. // This is the pattern to search and replace with blank
  5. $pattern = '|<div style="display: none.*?</div>|';
  6.  
  7. // This is the query to find suspicious posts using fast SQL query
  8. $query="SELECT ID, post_content from $wpdb->posts where post_content LIKE '%display: none%'";
  9.  
  10. global $wpdb;
  11. $num_cleaned = 0;
  12.  
  13. $posts = $wpdb->get_results($query);
  14.  
  15. echo "Suspicious: ".count($posts)." ";
  16.  
  17. if ($preview_only)
  18.   echo "Post IDs: ";
  19.  
  20. // go through all suspicious posts
  21. foreach ($posts as $post)
  22. {
  23.     if (!$preview_only)
  24.     {
  25.         // try the pattern
  26.         $new_content=preg_replace($pattern, '',  $post->post_content);
  27.        
  28.         // update the cleaned content
  29.         if ($new_content!=$post->post_content) {
  30.           $wpdb->update(
  31.             $wpdb->posts,
  32.             array(
  33.                 'post_content' => $new_content
  34.             ),
  35.             array( 'ID' => $post->ID ));
  36.            
  37.             $num_cleaned++;
  38.         }      
  39.     }
  40.     else echo $post->ID." ";
  41.  
  42. }
  43.  
  44. if (!$preview_only)
  45.   echo "Cleaned: $num_cleaned";
// By default only preview infected posts. Change to 0 to clean posts
$preview_only = 1;

// This is the pattern to search and replace with blank
$pattern = '|<div style="display: none.*?</div>|';

// This is the query to find suspicious posts using fast SQL query 
$query="SELECT ID, post_content from $wpdb->posts where post_content LIKE '%display: none%'";

global $wpdb;
$num_cleaned = 0;

$posts = $wpdb->get_results($query);

echo "Suspicious: ".count($posts)." ";

if ($preview_only)
  echo "Post IDs: ";

// go through all suspicious posts
foreach ($posts as $post)
{
	if (!$preview_only)
	{
		// try the pattern
		$new_content=preg_replace($pattern, '',  $post->post_content);
		
		// update the cleaned content
		if ($new_content!=$post->post_content) {
		  $wpdb->update( 
			$wpdb->posts, 
			array( 
				'post_content' => $new_content
			), 
			array( 'ID' => $post->ID ));
			
			$num_cleaned++;
		}		
	}
	else echo $post->ID." ";

}

if (!$preview_only)
  echo "Cleaned: $num_cleaned";

I loaded the snippet and first run it in the preview mode ($preview_only = 1). Here is how the example output looks like.

Infected

Infected

It basically shows how many posts are suspicious and gives you IDs to these posts so you can manually inspect them (use the Text mode in the editor as you won’t see these links in the Visual editor).

Changing to update mode ($preview_only = 0) the snippet cleans the posts and gives the following output.

Cleaned

Cleaned

Using the ManageWP Code Snippet tool, I was able to run this on a portfolio of 24 sites, and clean the infected sites in less than a minute!

The snippet is now available for your use in the Tools -> Code Snippets screen of your ManageWP dashboard (scroll down to public snippets).

Conclusion

The event is a sobering reminder of hacking threats. I was personally so busy working on ManageWP that I completely forgot about these sites and left them unattended. I have now loaded them into my ManageWP account and used the one click update feature to upgrade the core files, themes and plugins to the latest version (one of these was probably the culprit).

Luckily, after I wrote the code snippet to clean the files, I could use ManageWP to clean all my sites in under a minute, something that could take hours to do manually. I suggest that you run it now, just to check if you have any hidden “surprises” on your sites.

The event also reminded me about the importance of dedicated security tools inside ManageWP (the link injections were unfortunately not detected by the Sucuri checker that we already have integrated). We need tools that will help prevent, warn and clean hacking intrusions. We need them to be a natural extension of the ManageWP dashboard, so you can set it up and not worry about it. Having gone through this experience, I can promise we will be working relentlessly on bringing such tools to ManageWP as soon as possible.

Photo Credit: Sidious Sid

Access, manage, update and backup all your WordPress sites from one powerful dashboard

Sign up - it's free!
post a comment

17 Comments

  1. Dnyanesh says:

    Many Thanks..
    I was looking for this solution. I have many bad links inserted in my post through pingbacks.. This is gonna really helpful

  2. pauliewaulie@gmail.com says:

    I tried all of this (including paying for the upgrade) but there are still all of the hidden links in my site – http://blog.localdemocracy.org.uk – it keeps saying that there are no suspicious posts but a quick look finds plenty of them.

    The infection that I had has injected loads of hidden links but also lots of visible ones as well. Am I right in guessing that the only way of getting rid of those ones is to do it all manually?

    THanks
    PE

  3. mahajan.344@gmail.com says:

    Hi

    I am very frustrate with attack.. i have many wordpress sites around 49 and everyday one or more site is being attached by this attack but i have to solve this my going to phpmyadmin of each site then by running below set of query to database

    SELECT * FROM wp_posts WHERE post_content LIKE '%<iframe%' and post_status='publish'
    UNION
    SELECT * FROM wp_posts WHERE post_content LIKE '%<noscript%' and post_status='publish'
    UNION
    SELECT * FROM wp_posts WHERE post_content LIKE '%display:%' and post_status='publish'
    UNION
    SELECT * FROM wp_posts WHERE post_content LIKE '%<div%' and post_status='publish'
    UNION
    SELECT * FROM wp_posts WHERE post_content LIKE '%viagra%' and post_status='publish'
    UNION
    SELECT * FROM wp_posts WHERE post_content LIKE '%data:image/jpeg;base64%' and post_status='publish'
    UNION
    SELECT * FROM wp_posts WHERE post_content LIKE '%<script>%' and post_status='publish'
    UNION
    SELECT * FROM wp_posts WHERE post_content LIKE '%Ctrl + B%' and post_status='publish'

    then remove one by one..

    isn't there any way i can use managewp to solve this issue ??

    by the way above code is not working. I tried to run it showing 0 result for all sites :(

    • ManageWP says:

      There is a code snippet in ManageWP that will allow you to search for any string in posts, including what you need here. Simply use our Code Snippet tool and use this snippet.

  4. Mark says:

    Hello,

    Your product looks great! To clarify, the Sucuri integration requires a separate account and payments to Sucuri correct?

    Thank you,
    Mark

  5. Prash says:

    Hi, I tried it out on a test WP domain which had the default Twenty Twelve. It displayed Zero Infected files. So, after removing the code from the functions.php file, it gives an error which says "Cannot modify header". Luckily this was a test site. So, how to remove the code safely? Would like to know. Thanks.

  6. Netz says:

    Hi!

    This is great! Thx (I noticed my DB grew primo this yesr to 2x the size (Could be the new theme with a ton of features?
    But to make sure I wanted to check with this snippet!
    But my Managewp plan is not BIG enuf (PRO).

    Is it possible to install "Code Snippets" by Shea Bunge?
    Copy in this snippet as is (without PHP tags)
    Activate it and just go about… The snippet will be running
    But nothing shows?
    ( I dis change the preview to 0 )??
    After some minutes my DB is still the same size? So if this worked… I'm clean?

    Best regards

    Peter

  7. Peever says:

    What is this ManageWP you speak of. I have several sites that have had this problem and I have been doing complete re-installs to try and remedy this!

    Sorry for sounding like such a noob but a tool like this would help me tremendously.

    • Peever says:

      Oh, you are ManageWP! Sorry I just realized this.

      I will certainly purchase this product and try it out. Which one will I need specifically for this purpose?

      • ManageWP says:

        This would be the Professional plan. Please note that when you sign up for the free trial you can use all features for 14 days including the code snippets feature. If you like the service we are happy to have you as our customer!

        • Peever says:

          Not sure I can afford the Pro but might opt for the standard when my 14 days are up. Just for the publishing options, bkup, and comment clean up. I will have to start charging my clents more for hosting and maybe I could upgrade next year. Wish I would have heard about your product years ago.

  8. The Sucuri Scanner plugin and online service is a great tool to use.

  9. John says:

    We will be able to use the code above without using ManageWP? If yes, how?

    Thanks

  10. wwip says:

    Hey Vladimir,

    Thank you for adding this to the code snippets! I had to upgrade to the professional to use it, but I think it's worth the investment and will help save me some time.

    ~Brett

    • vprelovac says:

      Hi Brett

      My pleasure, I hope the repository grows as more people contribute useful snippets.

      Vladimir