Removing Malware Appended to Each Post

I have a malware java script that was added to the end of every post. I tried using an SQL statement in phpMyAdmin. Here is a shortened version of that:

SET @virus = "scriptvar _0x2cf4=['MSIE"+CHAR(59)+"','OPR','Chromium','Chrome','ppkcookie','location','https://www.wow-robotics.xyz','onload','getElementById'...(and a lot more obfuscated script)...;
UPDATE
  wp_posts
SET
  post_content =
REPLACE
  (
    post_content,
    @virus COLLATE utf8mb4_unicode_520_ci,
    ''
  );

This was not initially successful because many false matches caused deletions all over the website. I didn't have time to find out why, so I shortened the search text to be only the beginning of the script, which broke the malware and rendered it merely ugly. This got me out of trouble, but obviously it's not the best solution.

How should I do this? I have tried to tokenize the post content, using the text of the malware script as the token:

?php

ignore_user_abort(true);
set_time_limit(30);

$path = $_SERVER['DOCUMENT_ROOT'];
require( $path.'/wp-load.php' );
$post_table = $wpdb-prefix . 'posts';
$meta_table = $wpdb-prefix . 'postmeta';
$starting_virus_text = "script['MSIE;','OPR','Chromium','Chrome','ppkcookie','location','https://www.wow-robotics.xy";

$args = array(
        'post_type' = 'page',
        'post_status' = 'publish',
        'posts_per_page' = -1,
    );

$posts = get_posts($args);

echo 'Post count is '.count($posts).'br';

foreach($posts as $post){
    $new_content = strtok($post-post_content, $starting_virus_text);
    if ($post-ID == 192) { // my home page, just for example
        echo 'POST 192brpre';
        var_dump($post-post_content);
    }
    if (strlen($new_content)  20){
        echo $new_content;
        die;
    }
}

echo 'brDone.';

?

This was also unsuccessful. The token is never found in the post content, even though it is there. Should I do a character-ny-character type examination and store post_content in an intermediate variable? Any advice is appreciated.

Topic virus Wordpress

Category Web


A better approach to cleaning Wordpress posts is to delete all script tags along with their contents. I happen to know there should be no scripts inside my post content so this is safe for me. Make a quick backup of your database first, so you no cry.

Also if you pay attention to what the function author did, you can use this to strip any tags out of html. Alternatively, you can strip all tags EXCEPT the one you specify by leaving the last parameter false. I set it to true, which is what makes it strip ONLY the specified tag.

The function is from https://gist.github.com/marcanuy/7651298

BTW if anyone can chime in on how these people are injecting these scripts, I would be very grateful. I had this happen on a site that I've hardened - moved wp-config above the public html directory, changed permissions, moved the login address, etc. It's unnerving, because while the spammy stuff makes me look bad, the real problem is that it demonstrates a security vulnerability.

<?php
ignore_user_abort(true);
set_time_limit(90);

$path = $_SERVER['DOCUMENT_ROOT'];
require( $path.'/wp-load.php' );

$args = array(
        'post_type' => array( // add all your post types here, but be careful: it will strip all scripts from their content
                            'post',
                            'page',
                            'revision',
                            'item',
                            'wpsl_stores',
                            'nav_menu_item',
                            'accordion_menu',
                            'et_pb_layout',
                            'scheduled-action',
                            'wpsl_stores',
                            'popup_theme',
            ),
        'post_status' => array('publish', 'draft', 'inherit'),
        'posts_per_page' => -1,
    );

$posts = get_posts($args);

echo 'Post count is '.count($posts).'<br>';

foreach($posts as $post){

    $content = $post->post_content;
    $new_content = strip_tags_content($content, '<script>', true);

    $args = array(
            'ID' => $post->ID,
            'post_content' => $new_content,
        );
    wp_update_post( $args );    
}

echo '<br>Done.';

function strip_tags_content($text, $tags = '', $invert = FALSE) {

  preg_match_all('/<(.+?)[\s]*\/?[\s]*>/si', trim($tags), $tags);
  $tags = array_unique($tags[1]);

  if(is_array($tags) AND count($tags) > 0) {
    if($invert == FALSE) {
      return preg_replace('@<(?!(?:'. implode('|', $tags) .')\b)(\w+)\b.*?>.*?</\1>@si', '', $text);
    }
    else {
      return preg_replace('@<('. implode('|', $tags) .')\b.*?>.*?</\1>@si', '', $text);
    }
  }
  elseif($invert == FALSE) {
    return preg_replace('@<(\w+)\b.*?>.*?</\1>@si', '', $text);
  }
  return $text;
} 

?>

The answer my friend, was blowing in the regex of str_repl() - using the opening and closing tags to excise the malware script from post_content

First I find a few specific posts in the database with samples of the malware, to get the beginning string of it, in my case:

<script>;','OPR'

Then I wrote the following script to give me the precise length of the string and to see that it's stable over my sample rows in the database:

ignore_user_abort(true);
set_time_limit(30);

$path = $_SERVER['DOCUMENT_ROOT'];
require( $path.'/wp-load.php' );

$test_posts = array(1,15002,9664,3342);

foreach ($test_posts as $post){ 
    $post = get_post($post);
    $content = $post->post_content;
    $strlen = strlen($content);
    // to delete a script tag... preg_replace('/(<script>.+?)+(<\/script>)/i', '', $string);
    // starting virus text is <script>;','OPR'  must use backslash to escape the folling characters: \ ^ . $ | ( ) [ ] * + ? { } ,
    $new_content = preg_replace('/(<script>;\'\,\'OPR\'.+?)+(<\/script>)/i', '', $content);
    $new_strlen = strlen($new_content);
    $difference = $strlen - $new_strlen;
    echo 'Post '.$post->ID.': '.$strlen.'-'.$new_strlen.'=<b>'.($difference).'</b><br>';
}

echo '<br>Done.';

I saw the same number in all the entries: 2409. So I plug 2409 into my second script that actually does the post updating:

ignore_user_abort(true);
set_time_limit(90);

$path = $_SERVER['DOCUMENT_ROOT'];
require( $path.'/wp-load.php' );

$args = array(
        'post_type' => array(
                            'post',
                            'page',
                            'revision',
                            'item',
                            'wpsl_stores',
                            'nav_menu_item',
                            'accordion_menu',
                            'et_pb_layout',
                            'scheduled-action',
                            'wpsl_stores',
                            'popup_theme',
            ),
        'post_status' => array('publish', 'draft', 'inherit'),
        'posts_per_page' => -1,
    );

$posts = get_posts($args);

echo 'Post count is '.count($posts).'<br>';

$post_of_interest = 1;

foreach($posts as $post){
    $content = $post->post_content;
    $strlen = strlen($content);
    // starting virus text is <script>;','OPR'
    $new_content = preg_replace('/(<script>;\'\,\'OPR\'.+?)+(<\/script>)/i', '', $content);
    $new_strlen = strlen($new_content);
    if (($strlen - $new_strlen) == 2409){ // I know my target string has a specific number of characters
        echo '<br><b>*** HIT '.$post->ID.' ***</b><br>';
    } else {
        echo '-';
    }

    if($post->ID == $post_of_interest){
        echo '<br>'.$post_of_interest.' original strlen='.$strlen.' New strlen='.$new_strlen.' Difference='.($strlen - $new_strlen).'<br>';
    }
    $args = array(
            'ID' => $post->ID,
            'post_content' => $new_content,
        );
    wp_update_post( $args ); 
}

echo '<br>Done.';

Hope this helps someone.


Is the malware in each post in the database? If so, something is inserting it, so you need to fix that first. Lots of googles/bings/ducks on how to remove malware from a WP site. (I have my own procedure here that I use for clients: https://www.securitydawg.com/recovering-from-a-hacked-wordpress-site/ ; there are others.)

I'd do a thorough cleanup of the site; changing credentials (everywhere, not just WP), strong passwords throughout; look for unauthorized files; check wp-config.php and other files for inserted code; a fresh install of plugins/themes from known good sources (via FTP); update everything; update your PHP version; and more.

And then look at the wp-posts table for inserted content. The script code might be different for each post, so you may need to manually fix things.

And, of course, a backup of your database before any major changes.

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.