Создаём собственный API – 7
Приветствую всех, вытерпел до этого момента и не уснул на предыдущих частях статьи 🙂 Сегодняшняя статья последняя в цикле статей об API. Надеюсь, в качестве наглядного материала и источника новых идей вам этого хватит. А если не хватит, – задавайте свои вопросы в комментариях. Если так случится, что понадобятся новые дополнительные статьи на эту тему, то не думаю, что это будет очень большой проблемой 🙂
Но вернёмся к нашему API. В заключительной части статьи я покажу то, с чего начал во второй статье, а именно – фильтрация кода от нежелательных данных, полученных от пользователей. Прежде всего, давайте прикинем, что мы уже защитили.Итак:
- Мы можем без труда зашифровать протокол передачи данных и воспользоваться HTTPS
- Мы обеспечиваем жёсткий формат запроса. Любой запрос, в котором отсутствуют необходимые данные, игнорируется. Любой запрос, в котором есть дополнительные данные, непредусмотренные нашей логикой, отсекаются и игнорируются.
- Для выполнения запроса мы заставляем пользователя пройти авторизацию. Т.о. мы отфильтровываем пользователей, которым можно доверять от тех, которых мы вообще не знаем.
- Авторизованные пользователи отличаются друг от друга уровнем доступа – что позволено Юпитеру, не позволено быку(с).
- Запрос содержит пару – имя функции и массив аргументов.Мы проверяем, существует ли в нашем API такая функция и тестируем массив аргументов на количество переданных параметров и их типы. Таким образом, мы уже предотвращаем попытки смержить строку с целой переменной id, которые используются у нас в запросах.
Мы не проверяем синтаксис запросов, да это и ненужно. В случае проблем, API просто вернёт вызывающему хосту false. Мы не проверяем результат, который возвращают API функции, хотя у нас есть такая возможность. Однако, мы возвращаем вызывающему хосту результат as is. Для демонстрационной API-системы вполне нормально.
Сейчас мы реализуем последний штрих к этой картине. Вернее, я дам вам идею, как можно это сделать. Скажу сразу, идея не отличается особенной производительностью, т.к. в ней полно вложенных циклов и рекурсии. Если вам покажется эта часть критически важной, оставьте свой отзыв в комментариях и я изменю код на более производительный. Кроме того, в рамках решения потенциальных проблем безопасности, было бы неплохо ограничить длину пользовательского запроса и ввести таймаут. Таймаут – на соединение между Host 1 и Host 2. Длинна (сериализованного или десериализованного) запроса -для того, чтобы вам не устроили DoS. Бывают же доброжелатели.
В общем, эти моменты я оставляю на ваше усмотрение и привожу пару последних классов в этой серии статей, которые позволят добавлять фильтры к аргументам. Кстати, как фильтровать – это тоже вопрос из серии прав доступа. Возможно, у вас появится такой серьёзный и крупный клиент, которому понадобится предоставить метод в духе api_any_query(). Хотя, тогда он сможет у вас украсть всю базу 😉 Но, тем не менее.
В общем, кончаем лирику и смотрим код:
//наследник класса api_executor. чтобы изменения //вступили в силу, достаточно просто вызвать этот //объект, вместо родительского. class api_executor_filtered extends api_executor { protected $api_args_filters = null; //стандартный конструктор. создаёт объект нового класса public function __construct($db_object) { parent::__construct($db_object); $this->api_args_filters = new api_args_filters($this->db_handler); } //старый метод execute предварительно фильтрует аргументы public function execute($func_name, $args=array()) { $args = $this->api_args_filters->filter($args); return parent::execute($func_name, $args); } } //интерфейс класса фильтров содержит единственный метод // array filter(array); interface api_args_filters_itrfc { public function filter($args); } class api_args_filters extends api implements api_args_filters_itrfc { //массив с фильтрами protected $filters = array(); //сюда сохраним исходное значение. вдруг пригодится. protected $temp = array(); //конструктор не отличается новизной public function __construct($db_object) { parent::__construct($db_object); //массив фильтров содержит имя функции в качестве ключа //и булевское значение, которое скажет системе - использовать //этот фильтр или нет. таким образом, наследуя от этого //класса поведение можно менять достаточно гибко, в т.ч. //добавляя новые фильтры $this->filters['impossible_filter'] = false; $this->filters['sql_injections_filter'] = true; $this->filters['xss_filter'] = true; } //имплементируем интерфейс public function filter($args) { //сохраняем исходные данные just 4 lulz $this->temp = $args; //проходимся по всему массиву фильтров и запускаем каждый //для которого существует необходимый метод foreach($this->filters as $key=>$val) { if(method_exists($this, $key) && ($val === true)) { //&& is_callable //переменная $args весьма сильно меняет своё содержимое с //каждой новой итерацией $args = call_user_func(array($this, $key), $args); } } return $args; } //простой но надёжный фильтр для предотвращения SQL-инъекций protected function sql_injections_filter($args=array()) { foreach($args as $k=>$v) { if(is_array($v)) { //рекурсия. оч. сильно тормозззззит и отжирает память. $args[$k] = $this->sql_injections_filter($v); } else { //прежде чем экранировать все спец. символы //избавляемся от ключевого слова UNION $args[$k] = mysql_real_escape_string( str_replace('union', '', strtolower($v))); } } return $args; } //XSS позволяет внедрять свой HTML и, что хуже, //JavaScript код. А если есть JavaScript, то может быть //и AJAX, что рисует весьма мрачные перспективы. protected function xss_filter($args=array()) { foreach($args as $k=>$v) { if(is_array($v)) { $args[$k] = $this->sql_injections_filter($v); } else { //аяксовские JS-классы тоже можно отфильтровать с такой же //лёгкостью. $args[$k] = htmlspecialchars( str_replace('javascript','',strtolower($v))); } } return $args; } }
Резюмируем.
В этой статье мы разработали механизм фильтрации аргументов в вызове API функций. И эта статья стала финальной в серии. В целом же, нам удалось собрать вполне работоспособный API, которой, в принципе, можно даже где-нибудь использовать. Нам так же удалось защитить наш API на достаточном уровне для текущего демонстрационного проекта. Следует отметить, что по всей иерархии классов прошёлся объект базы данных и в реальном дорогом коммерческом проекте этого делать не следует. В сети можно найти несколько очень сильных статей, в который объясняется, как устраняются зависимости между звёздными объектами. В рамках статей об API не был реализован заявленный в первой статье логгер. Я решил, что такому важному механизму можно посвятить одну из будущих отдельных статей. Тем не менее, объект логгера можно создать в базовом классе api и использовать в дочерних классах, для ведения статистики и логгирования. А именно – кто и с какого IP обращается к API, какие запросы выполняет класс simple_db, какие возникают ошибки и т.д. и т.п. Кстати говоря, хороший логгер тоже является важным элементом безопасности, т.к. админу системы можно выдать сигнал, если с какого-то хоста идёт слишком много обращений, которые заканчиваются ошибками – явно систему тестируют на уязвимость, ну и т.п.
На этом я заканчиваю. Код, как обычно, в аттачменте.
Использование фильтров закомментировано в классе шлюза. Прошу учесть такой момент – я не являюсь специалистом в сетевой безопасности, поэтому надеюсь, что читатель, более подготовленный в данном вопросе, сможет выложить конструктивную критику по защите нашего API в комментариях. Надеюсь, это будет полезно и интересно всем.
Всем удачи.
В тему: