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

Опубликовано: 2022-03-10
Краткий обзор ↬ Перед развертыванием нового плагина в WordPress рекомендуется иметь при себе список функций, которые легко использовать не по назначению. Давайте подробнее рассмотрим некоторые функции, которые вы можете и должны использовать как часть более широкой стратегии безопасности.

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

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

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

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

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

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

Еще после прыжка! Продолжить чтение ниже ↓

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

После этого у вас останутся только некоторые рискованные функции. Начиная с…

extract — это плохие новости, особенно $_POST или подобных

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

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

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

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

Если вы должны использовать extract для пользовательского ввода, вы всегда должны использовать флаг EXTR_SKIP . Это все еще имеет проблему путаницы, но избавляет от возможности того, что ваши предустановленные значения могут быть изменены злоумышленником извне с помощью простой строки запроса или модификации веб-формы. (Потому что он «пропускает» уже установленные значения.)

« eval — это зло», потому что произвольный код пугает

eval в PHP и почти в любом другом языке, который его поддерживает, всегда занимает высокие места в подобных списках. И не зря. Официальная документация PHP на PHP.net довольно откровенно говорит:

ВНИМАНИЕ ! Языковая конструкция eval() очень опасна, поскольку позволяет выполнять произвольный код PHP. Таким образом, его использование не рекомендуется. Если вы тщательно проверили, что нет другого варианта, кроме как использовать эту конструкцию, обратите особое внимание на то, чтобы не передавать в нее какие-либо предоставленные пользователем данные без надлежащей предварительной проверки.

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

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

Но всегда помните об этой строке Расмуса Лердорфа, основателя PHP:

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

Вариации на eval

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

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

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

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

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

 // uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html ); // uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html ); // uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html ); // uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html ); // 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, исправить даже проще, чем preg_replace . Вы можете просто создать свою собственную анонимную функцию, не используя строки в качестве посредников. Это и более безопасно, и более читабельно, по крайней мере, для меня.

assert Is также eval -Like

assert не является функцией, которую многие 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 ваша переменная, и рассмотрели, что произойдет, когда это произойдет, все в порядке. Но это плохие новости, если вы не учли, что будет делать 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.

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

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

Следите за unserialize ; Он запускает код автоматически

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

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

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

unserialize отличается от eval -уязвимостей тем, что требует использования магических методов для объектов. Вместо того, чтобы создавать свой собственный код, злоумышленник вынужден злоупотреблять вашими уже написанными методами на объекте. Оба магических метода __destruct и __toString для объектов также опасны, как объясняется на этой странице OWASP Wiki.

В общем, все в порядке, если вы не используете __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. Это означает, что ваш сценарий потенциально может стать жертвой атаки «человек посередине», которая позволит злоумышленнику поместить практически все, что он хочет, в результат вашей страницы file_get_contents .

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

Не доверяйте слепо проверке URL от filter_var

Так что это немного неясно, поэтому спасибо Крису Вейгману за объяснение этого в этом выступлении WordCamp. В общем, filter_var в PHP — отличный способ либо проверить, либо очистить данные. (Хотя не запутайтесь в том, что вы пытаетесь сделать…)

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

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

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

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

WordPress десериализует с помощью maybe_unserialize

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

Это не создает какой-либо новой уязвимости, проблема просто в том, что, как и основная функция unserialize , эта может привести к эксплуатации уязвимого объекта, когда он несериализован.

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

Это довольно просто, но функция неоднозначна в названии, поэтому она может запутать людей или быть пропущенной, если вы спешите. Вы всегда должны проверять, что пользователь, который пытается выполнить действие в WordPress, имеет права и привилегии, необходимые для выполнения этого действия. Для этого вы должны использовать функцию 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-инъекциях, это может показаться глупым и даже излишним для перечисления. Потому что дело в том, что вы можете получить доступ к базе данных любым способом (скажем, используя драйверы базы данных PHP mysqli или PDO) для создания запросов к базе данных, которые позволяют проводить атаки SQL-инъекций.

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

Что делать? Используйте $wpdb->prepare() , а затем $wpdb->query() . Вы также захотите убедиться, что вы подготовились перед другими методами «получения» $wpdb , такими как get_row() и get_var() . В противном случае Bobby Tables может заполучить вас.

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

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

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

Еще многое предстоит сделать, но это большое начало

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

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

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