Будьте внимательны: функции PHP и WordPress, которые могут сделать ваш сайт небезопасным

Безопасность WordPress сайта (и любого другого сайта) – многогранная проблема. Важнейший шаг, который может предпринять каждый, чтобы гарантировать безопасность своего сайта – тщательно следить за функциями, которые могут вести к негативным последствиям. В данной статье мы рассмотрим функции, понимание которых очень важно для WordPress разработчиков.

В WordPress содержится огромная библиотека функций. Некоторые из них могут таить в себе опасность. Также существует масса потенциально опасных PHP функций, с которыми нередко сталкивается WordPress разработчик.

Каждая из таких функций имеет вполне легитимное и безопасное применение, однако эти функции также могут упростить использование вашего кода злоумышленниками. Мы рассмотрим такие «пограничные» функции и расскажем, почему вы должны следить за ними. Сначала мы коснемся PHP-функций, а затем поговорим о WordPress функциях (тоже PHP), которые могут приводить к проблемам.

Содержание
  1. PHP-функции, за которыми стоит присматривать
  2. Использование extract  — тревожный звоночек, особенно в $_POST и подобном
  3. Произвольный код в eval – это всегда страшно
  4. Разновидности eval
  5. Модификаторы /e в регулярных выражениях
  6. create_function с пользовательским вводом данных – тоже плохо
  7. assert – почти как eval
  8. Переменные в именах файлов – возможность бесконтрольного выполнения PHP
  9. Никогда не передавайте пользовательский ввод в shell_exec и аналоги
  10. Следим за unserialize – эта функция выполняет код автоматически
  11. Получение URL через file_get_contents – рискованно!
  12. Не нужно слепо доверять проверке URL в filter_var
  13. Специфичные для WordPress функции, за которыми требуется глаз да глаз
  14. Десериализация в WordPress с помощью maybe_unserialize
  15. is_admin не подскажет, является ли пользователь администратором
  16. add_query_arg() не чистит URL
  17. $wpdb->query() открыт для SQL-инъекций
  18. esc_sql не защищает вас от SQL-инъекций
  19. Это далеко не все, но это хорошая отправная точка

PHP-функции, за которыми стоит присматривать

Как мы уже говорили, PHP содержит много простых в использовании функций. Некоторые из этих функций примечательны тем, что они открывают дорогу для злоупотреблений. Важно использовать эти функции корректно.

Мы будем исходить из предположения, что у вас уже отключены так называемые «волшебные кавычки» и глобальные переменные (Register_Globals), если ваша версия PHP поддерживает их. Они отключены по умолчанию в PHP 5 и выше, однако они могут быть включены для версий ниже 5.4. Лишь немногие хостинги разрешают эти опции, однако я считаю, что без их упоминания статья о безопасности PHP была бы неполной.

Вы должны использовать в данный момент как минимум PHP 5.6. И у вас должен быть план перехода к PHP 7, поскольку в конце 2018 года завершается поддержка версии 5.6.

Итак, давайте перейдем теперь к самим функциям.

Использование extract  — тревожный звоночек, особенно в $_POST и подобном

К счастью, в последнее время эта PHP функция используется не так активно. Суть использования этой функции заключается в том, что ее можно запускать с массивом данных, и все пары ключ-значение массива станут переменными в вашем коде. Таким образом, данный код будет работать:

$arr = array(
    'red' => 5
);
extract($arr);
echo $red; // 5

Возможность крутая, но опасная, особенно если вы извлекаете (делаете extract) $_GET, $_POST и т.д. В этих ситуациях вы вручную воссоздаете проблему с register_globals: злоумышленник может легко изменить значения ваших переменных, добавив строку запроса или поле формы. Решение простое: не используйте extract.

Если вы сами создаете массив, который затем же сами и извлекаете, то это не создает проблем безопасности с extract, и в некоторых случаях это может быть вполне полезной опцией. Однако применение extract ставит в тупик читателей кода. Создание массива и вызов extract – более запутанная штука, чем обычное объявление переменных. Потому я призываю делать ручные объявления переменных, если только это не является неосуществимым.

Если вы должны использовать extract для пользовательского ввода, вы всегда должны использовать флаг EXTR_SKIP. От путаницы не избавит, но зато ваши предустановленные значения не будут изменены злоумышленниками через простую строку запроса или модификацию веб-формы.

Произвольный код в eval – это всегда страшно

В PHP есть функция eval(), которая даже в официальной документации приводится с предупреждением:

Языковая конструкция eval() является очень опасной, поскольку позволяет выполнять произвольный PHP-код. Ее использование не рекомендуется. Если у вас нет другого варианта, кроме как использовать эту конструкцию, обратите особое внимание на то, чтобы в ней не передавались пользовательские данные без предварительной проверки.

Eval позволяет запускать любую произвольную строку в вашей программе в виде PHP-кода. Это полезно в мета-программировании, когда вы создаете программу, которая сама может создать программу. Конструкция eval действительно очень опасна, поскольку, если вы разрешите что-либо передавать в свою функцию eval (к примеру, через формы, размещенные на странице), то в таком случае вы упростите жизнь хакерам – они смогут сделать практически что угодно на вашем сервере. Очевидно, злоумышленники смогут подключиться к вашей базе данных, удалить любые файлы – в общем, они смогут сделать все, что можно делать, подключаясь по SSH к машине. И это плохо.

Если вам необходимо использовать eval в своей программе, то в таком случае вам нужно защититься от произвольного пользовательского ввода, передаваемого в эту функцию. Если вы хотите разрешить пользовательский ввод в eval, то в таком случае вам нужно составить черный список команд, которые нельзя выполнять, либо, что сложнее реализовать, белый список команд, которые вы считаете безопасными. Еще лучше: разрешить только некоторые изменения параметров – к примеру, изменения только проверенных целочисленных значений.

Однако помните про следующую фразу от Расмуса Лердорфа:

«Если eval() – это ответ, то практически наверняка вы задаете неправильный вопрос».

Разновидности eval

Наряду с Eval в PHP есть много других исторических сложившихся способов поддержки строк, выполняемых в виде кода. Разработчикам WordPress важно помнить про preg_replace с модификатором /e и create_function. Каждая из них работает по-своему, а preg_replace после выхода PHP 5.5.0 не поддерживается (версии PHP 5.5 и ниже не получают обновлений безопасности, а потому их лучше не использовать).

Модификаторы /e в регулярных выражениях

Если вы работаете с PHP 5.4.x или ниже, то в таком случае вам нужно следить за вашими вызовами preg_replace, которые заканчиваются e. К примеру:

$html = preg_replace(
    '((.*?))e',
    '"" . strtoupper("$2") . ""',
    $html
);)
// or
$html = preg_replace(
    '~(.*?)~e',
    '"" . strtoupper("$2") . ""',
    $html
);)

PHP предлагает разные способы «внедрения» в ваше регулярное выражение, однако важнее всего для нас именно использование «e» в качестве последнего символа для первого аргумента preg_replace. Если символ присутствует, то вы передаете весь сегмент в виде аргумента для встроенного PHP-кода, после чего преобразуете его. Проблемы здесь те же самые, как и в случае с eval, если у вас разрешен пользовательский ввод. Код примера можно скорректировать с помощью preg_replace_callback. Преимущество этого в том, что злоумышленникам сложнее изменить ввод. Поэтому следует использовать такую конструкцию:

// uppercase headings
$html = preg_replace_callback(
    '((.*?))',
    function ($m) {
        return "" . strtoupper($m[2]) . "";
    },
    $html
);

create_function с пользовательским вводом данных – тоже плохо

В PHP также имеется функция create_function. Она не поддерживается в PHP 7.2. Функция работает аналогично eval и имеет те же самые недостатки: позволяет преобразовывать строку (второй аргумент) в исполняемый PHP-код. Риски те же самые: умный злоумышленник может получить доступ к вашему серверу, если вы будете неосторожны.

Если вы работаете с PHP 5.3 и выше, то исправить код легко. Вам надо просто создать анонимную функцию, отказавшись от использования строк в качестве посредников. Это более читабельный и безопасный способ, на мой взгляд.

assert – почти как eval

Эту функцию разработчики PHP и WordPress используют редко. Функция проверяет, является ли утверждение ложным. В качестве бонуса она поддерживает операции по типу eval. Потому к ней применимы все те же правила безопасности, что и к eval. Строковые утверждения (суть проблемы) в PHP 7.2 также не поддерживаются, т.е. в будущем эта опасность станет менее значимой.

Переменные в именах файлов – возможность бесконтрольного выполнения PHP

Мы уже рассказали о том, почему eval является плохой практикой, однако это далеко не все проблемные ситуации. К примеру, что-то по типу include или require($filename’.php’) может вызывать определенные риски, особенно когда $filename берется из пользовательского ввода. Причина тут немного отлична от случая с eval. Переменные в именах файлов часто используются для маршрутов «от URL к файлу» в PHP-приложениях за пределами WordPress. Однако то же самое можно встретить и в WordPress.

Когда вы выполняете include или require (include_once или require_once), ваш скрипт выполняет подключенный файл. По сути происходит выполнение (eval) данного файла, хотя мы редко представляем себе это таким образом.

Важно всегда изучить то, что будет подключаться (include) в password.php или wp-config.php. Также плохая новость – если злоумышленник может добавить вредоносный файл и выполнить его (через include). Правда, в таком случае у вас однозначно имеются более серьезные проблемы.

Решение это задачи: прописывать все в коде вручную, когда это возможно. Если это не получается сделать, вы можете использовать белый список (по возможности) или черный список файлов, которые могут быть подключены. Если файлы в белом списке, вы знаете, что они безопасны. Если нет, то в таком случае ваш скрипт не будет включать их. Простая настройка, повышающая безопасность. Белый список будет иметь примерно такой вид:

$white_list = [‘db.php’, filter.php’, ‘condense.php’]
If (in_array($white_list, $file_to_include)) {
include($file_to_include);
}

Никогда не передавайте пользовательский ввод в shell_exec и аналоги

Это очень важно. shell_exec, system, exec и обратные кавычки в PHP позволяют коду обращаться к базовой оболочке (обычно Unix). По опасности это напоминает eval, но здесь риски удваиваются. Удваиваются, т.к. если вы неаккуратно обработаете пользовательский ввод, то в таком случае злоумышленник даже не будет связан ограничениями PHP.

Возможность запуска shell-команд из PHP может быть очень полезна для разработчика. Однако если вы позволите сторонним пользователям работать с такими командами, то у них в руках будет огромная мощь. Поэтому пользовательский ввод не должен передаваться в такие функции, как shell_exec и подобные.

Лучший выход из подобной ситуации – предоставление пользователям доступа к ограниченному набору безопасных предопределенных команд оболочки. Однако и в таком случае нужно проявлять осторожность.

Следим за unserialize – эта функция выполняет код автоматически

Популярный список действий: вызов serialize для объекта PHP, сохранение его данных где-либо еще, после чего передача этих данных в unserialize, чтобы вернуть объект обратно к жизни. Достаточно популярный подход, но рискованный. Почему? Если ввод для вызова unserialize не является полностью безопасным (к примеру, он хранился в cookie, а не в вашей базе данных), то в таком случае злоумышленник может изменить внутреннее состояние вашего объекта так, чтобы unserialize делал что-то плохое.

Этот эксплойт более скрытый и менее популярный, чем проблема с eval. Если вы используете cookie для хранения сериализованных данных, не применяйте serialize к этим данным. Используйте что-то вроде json_encode и json_decode. В случае с этими PHP-функциями автоматического выполнения кода не произойдет.

Уязвимость здесь заключается в том, что при десериализации строки в класс PHP вызывает магический метод __wakeup для данного класса. Если в unserialized будет разрешен непроверенный пользовательский ввод, то в таком случае в методе __wakeup может произойти вызов базы данных или удаление файла, что несет в себе потенциальную опасность.

Unserialize отличается от уязвимостей с eval, поскольку требует использования магических методов для объектов. Вместо того, чтобы создавать свой собственный код, злоумышленник вынужден подстраиваться под уже существующие методы объекта, эксплуатируя их. Также рискованными являются магические методы __destruct и __toString.

Резюмируем: у вас все ОК, если вы не используете методы __wakeup, __destruct или __toString в своих классах. Однако поскольку кто-то может добавить их в класс впоследствии, мы рекомендуем не разрешать пользователям обращаться к serialize и unserialize. Все публичные данные для такого рода использования мы советуем перекладывать на JSON (json_encode и json_decode), где не происходит автоматического выполнения кода.

Получение URL через file_get_contents – рискованно!

Обычная практика при быстром написании PHP кода, который должен вызывать сторонний URL – обращение к file_get_contents. Это просто, легко, но не слишком безопасно.

Проблема с file_get_contents достаточно хитра. Чаще всего хостинги настраивают PHP так, чтобы запретить вам обращаться к сторонним URL. Это делается для защиты.

Суть в том, что file_get_contents будет получать удаленные страницы для вас. Однако, когда функция это делает, она не проверяет целостность соединения по HTTPS-протоколу. Это означает, что ваш скрипт потенциально может пасть жертвой атаки MitM, которая позволит злоумышленнику внести в file_get_contents любой свой результат.

Это более сложная атака. Чтобы защититься от нее, при написании современного PHP-кода (на базе Composer) я практически всегда обращаюсь к Guzzle для обертывания кода в более безопасный cURL API. В WordPress это еще проще: используйте wp_remote_get. Функция работает более последовательно, чем file_get_contents, и по умолчанию проверяет SSL-соединение (вы можете отключить такую проверку, но лучше этого не делать). Еще лучше использовать wp_safe_remote_get – она работает практически так же, как и функция без safe в своем имени, но еще и позволяет избежать небезопасных редиректов и переадресаций.

Не нужно слепо доверять проверке URL в filter_var

Здесь потребуется небольшой ликбез. В целом, filter_var – это отличный способ проверить и очистить данные.

Проблема здесь состоит в следующем: если вы попытаетесь использовать filter_var для проверки безопасности URL, вам нужно будет помнить о том, что filter_var не проверяет протокол. Зачастую проблем с этим нет, но если вы передадите пользовательский ввод в этот метод для проверки, и используете FILTER_VALIDATE_URL, то произойдет что-то вроде javascript://comment%0aalert(1). Это может стать хорошим вектором XSS-атак там, где вы этого не ожидали.

Для проверки URL в WordPress есть функция esc_url, которая действует аналогично, но разрешает только некоторые протоколы. Javascript не входит в базовый список протоколов, потому вы будете в безопасности. Однако, в отличие от filter_var, функция esc_url возвращает пустую строку (а не false) для переданного неподдерживаемого протокола.

Специфичные для WordPress функции, за которыми требуется глаз да глаз

В дополнение к базовым потенциально уязвимым PHP функциям, в WordPress есть свои функции, которые могут нести в себе некоторые риски. Зачастую эти функции перекликаются с теми, что уже были описаны выше.

Десериализация в WordPress с помощью maybe_unserialize

Вероятно, функция будет очевидна для вас, если вы читали текст выше. В WordPress есть функция, которая называется maybe_unserialize, которая выполняет десериализацию того, что было передано в нее.

Новых уязвимостей она не создает, проблема заключается в том же, что и в случае с базовой функцией unserialize — уязвимый объект может эксплуатироваться злоумышленниками, если он десериализован.

is_admin не подскажет, является ли пользователь администратором

Функция достаточно неоднозначна и зачастую путает людей. Вы всегда должны выполнять проверку того, что пользователь, который совершает какое-либо действие в WP, имеет права и привилегии, необходимые для данного действия. Для этого используется функция current_user_can.

Однако вы можете ошибочно подумать, что is_admin сообщит вам, является ли текущий пользователь администратором, т.е. он может задавать опции, используемые в вашем плагине. Это ошибка. Что делает is_admin в WordPress? Он сообщает, загружается ли текущая страница на стороне администратора (в противовес фронтэнду). Таким образом, любой пользователь, имеющий доступ к страницам администратора (к примеру, к «Консоли»), сможет потенциально пройти эту проверку. Вам нужно помнить всегда о том, что is_admin относится к типу страницы, а не к текущему пользователю.

add_query_arg() не чистит URL

Не такая популярная вещь сейчас, но пару лет назад в экосистеме WordPress она наделала много шуму, поскольку публичная документация по этой функции была неправильной. Проблема состоит в том, что функция add_query_arg (и ее противоположность remove_query_arg) не выполняет автоматическую очистку URL-адреса сайта, если URL не передавался ей (а люди думали, что она делает это). Многие разработчики плагинов опирались на ошибочное описание этой функции в Кодексе, а потому использовали ее небезопасно.

Что нужно было сделать: очистить результат вызова данной функции перед использованием этого результата в дальнейшем. Если вы производили очистку, то в таком случае вы защищены против XSS-атак. Выглядит это следующим образом:

echo esc_url( add_query_arg( 'foo', 'bar' ) );

$wpdb->query() открыт для SQL-инъекций

Если вы знаете про SQL-инъекции, то этот пункт может показаться вам лишним в списке. Поскольку в любом случае вы обращаетесь к базе данных (через mysqli или PDO) для создания запросов к ней, что открывает путь для SQL-инъекций.

Почему я отдельно выделил $wpdb->query? Суть в том, что остальные методы (такие как insert, delete и т.д.) в $wpdb защищают от инъекций. Кроме того, если вы выполняете базовые запросы с помощью WP_Query или подобного, вам не нужно думать про SQL-инъекции. Вот почему я и акцентировал внимание на $wpdb->query: инъекция возможна в том случае, если вы попытаетесь использовать $wpdb для создания своего запроса.

Как быть? Используйте $wpdb->prepare(), а потом уже $wpdb->query(). То же самое важно и для других методов наподобие $wpdb — get_row() и get_var().

esc_sql не защищает вас от SQL-инъекций

Как уже отмечалось выше, вы должны использовать wpdb->prepare() перед любым запросом к вашей базе данных. Это поможет вам оставаться в безопасности. Однако вместо этого разработчик может обратиться к esc_sql. И он будет ожидать, что этот подход тоже безопасен.

Проблема в том, что esc_sql не имеет надежной защиты от SQL-инъекций. Это просто версия PHP функции add_slashes, которая не используется уже много лет.

Это далеко не все, но это хорошая отправная точка

Обеспечение безопасности – это не только поиск функций, которые могут использоваться злоумышленниками. К примеру, мы не обсуждали в деталях о необходимости очистки и проверки всех данных, которые вы получаете от пользователей, а также об эскейпинге данных перед публикацией их на странице. Однако вы можете использовать приведенные примеры как часть более комплексной стратегии обеспечения безопасности.

Всегда полезно держать под рукой список функций, которые могут быть неверно использованы. Однако важно также убедиться в том, что безопасность реализована и на уровне хостинга, что у ваших пользователей безопасные пароли и т.д.

Будьте внимательны при использовании описанных выше функций – и тогда вы отсеете большую часть потенциальных проблем с безопасностью!

Источник: https://www.smashingmagazine.com

Блог про WordPress
Комментарии: 8
  1. Алекс

    Наконец-то интеерсный пост ;)

    1. Дмитрий (автор)

      У нас нет неинтересных постов.

      1. HyipMmgp

        Я часто называю статьи неинтересными только потому, что тема статьи и обсуждения мне непонятна и у меня мало знаний в той области, которой посвящена та или иная статья. А не потому, что статьи действительно неинтересные. :)
        Обзоры тем и плагинов WordPress или как что видоизменить на сайте — это мне по нраву и тут я охотно читаю статьи на подобные темы.

        1. Дмитрий (автор)

          Тогда ок :)

  2. Андрей

    Отличная подборка подборка тонкостей кода. Спасибо, с интересом прочитал!

  3. Nokey

    Очень познавательной и полезный пост для вордпресс разработчиков, к сожалению в этой CMS нужен глаз да глаз за многими вещами)) Но внимательным нужно быль в любой CMS и тем более самописной!

    1. Дмитрий (автор)

      В самописной будет еще больше разных потенциальных дыр и уязвимостей.

  4. Алексей

    Не так давно работаю с WordPress, но уже не по наслышке знаю о уязвимости платформы. Мне кажется самое главное — это почаще обновлять версии

Добавить комментарий для HyipMmgp Отменить ответ

Получать новые комментарии по электронной почте.