Создаём собственный API – 7

Кирилл Евсеев, April 12, 2011

Приветствую всех, вытерпел до этого момента и не уснул на предыдущих частях статьи :) Сегодняшняя статья последняя в цикле статей об API. Надеюсь, в качестве наглядного материала и источника новых идей вам этого хватит. А если не хватит, – задавайте свои вопросы в комментариях. Если так случится, что понадобятся новые дополнительные статьи на эту тему, то не думаю, что это будет очень большой проблемой :)

Но вернёмся к нашему API. В заключительной части статьи я покажу то, с чего начал во второй статье, а именно – фильтрация кода от нежелательных данных, полученных от пользователей. Прежде всего, давайте прикинем, что мы уже защитили.Итак:

  1. Мы можем без труда зашифровать протокол передачи данных и воспользоваться HTTPS
  2. Мы обеспечиваем жёсткий формат запроса. Любой запрос, в котором отсутствуют необходимые данные, игнорируется. Любой запрос, в котором есть дополнительные данные, непредусмотренные нашей логикой, отсекаются и игнорируются.
  3. Для выполнения запроса мы заставляем пользователя пройти авторизацию. Т.о. мы отфильтровываем пользователей, которым можно доверять от тех, которых мы вообще не знаем.
  4. Авторизованные пользователи отличаются друг от друга уровнем доступа – что позволено Юпитеру, не позволено быку(с).
  5. Запрос содержит пару – имя функции и массив аргументов.Мы проверяем, существует ли в нашем 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 в комментариях. Надеюсь, это будет полезно и интересно всем.

Всем удачи.

В тему:

0комментариев
Ваше имя
Ваш email*
Ваш сайт
Текст вашего комментария:

Поиск по блогу:
Подписаться:
Популярные:
Облако тегов:
Разное:
Счетчик: