Уязвимости в MySQL и SQL запросах

Владимир | | MySQL, PHP, Web разработка, Безопасность.

mysql vulnerabilities

Эта заметка – вольный перевод статьи Stefan Esser MySQL and SQL Column Truncation Vulnerabilities.

SQL-инъекции (SQL-injection) на сегодняшний день остаются наиболее обсуждаемыми проблемами безопасности web приложений.

В тоже время, другие уязвимости SQL запросов, например, связанные со слишком длинными входными данными, обычно игнорируются, хотя могут привести ко всем видам проблем безопасности.

Первая уязвимость касается движка MySQL

В MySQL существует параметр, который называется max_packet_size. По-умолчанию ему присвоено значение 1МБ. Он контролирует максимальный размер пакетов, которые передаются между SQL клиентом и сервером. Если запросы или результат их выполнения не укладывается в размер пакета – возникает ошибка. Это означает, что слишком длинные SQL запросы никогда не отправляются серверу и, таким образом, никогда не выполняются.

Это может привести к проблемам безопасности, если хакер может передать большие блоки данных, которые потом используются в SQL запросах.

Например, представьте запросы авторизации, которые объединяют информацию из HTTP заголовков, id сессий и логов. В результате получается большой запрос, который не укладывается в размер пакета.

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

Таким образом, web разработчики должны всегда проверять длину запросов. И тут не играет роли, каким образом выполняется подготовка запроса.

Вторая уязвимость касается длины столбцов.

Если не проверять длину данных, полученных от пользователя, может возникнуть уязвимость, связанная с отбрасыванием части данных при вставке в таблицу (в оригинале — SQL Column Truncation Vulnerabilities).

По-умолчанию MySQL обрезает строки, длина которых превышает максимальную длину столбца и при этом отправляет только предупреждение (warning). Обычно web приложение эти предупреждения не видит и, соответственно, не обрабатывает их.

В MySQL можно активировать режим sql_mode STRICT_ALL_TABLES чтобы превратить эти предупреждения в ошибки (errors). Но большинство web приложений работают на серверах, запущенных в обычном режиме.

Таким образом, необходима проверка длины данных, полученных от пользователя.

Например, представьте такую ситуацию.

1) web приложение представляет собой форум;

2) имя администратора известно, например, «admin»;

3) MySQL работает в обычном режиме;

4) в приложении отсутствует ограничение на длину имени пользователя;

5) длина поля в базе данных равна 16-ти символам.

Хакер может попытаться зарегистрироваться под именем «admin » (с пробелом в конце), но это ему не удастся, если выполняется проверка на существование такого имени.

Дело в том, что MySQL не сравнивает данные в двоичном режиме. По-умолчанию используется более гибкая система проверок. При которой пробелы в конце строк отбрасываются. Это означает, что строки «admin» «admin » равны.

Т.е. запрос

SELECT * FROM user WHERE username='admin '

вернет данные реального администратора и в регистрации с таким именем будет отказано.

Но что произойдет, если хакер использует имя «admin           х» (одиннадцать пробелов после слова admin и в конце буква «х»)? Такое имя найдено не будет, потому что невозможно найти пользователя с именем из 17 символов, если поле базы данных имеет ограничение в 16 символов.

Т.е. приложение добавит пользователя с таким именем в базу. Но при этом имя будет обрезано до ширины столбца. Т.е. MySQL отбросит последнюю букву «х» и имя превратится в «admin           » (одиннадцать пробелов в конце).

Вот тут и возникает уязвимость. Например, для авторизации пользователя используется следующий код.

$userdata = null;
if (isPasswordCorrect($username, $password)) {
   $userdata = getUserDataByLogin($username);
   ...
}

Если функция isPasswordCorrect использует запрос вида:

SELECT username FROM users WHERE username = ? AND passhash = ?

то хакер легко пройдет проверку, т.к. он знает пароль, с которым регистрировался.

После этого выполняется функция getUserDataByLogin, которая обычно содержит запрос вроде

SELECT * FROM users WHERE username = ?

Но у нас два имени «admin», которые MySQL считает одинаковыми. Значит в результате выполнения предыдущего запроса, мы получим две записи. И при этом запись настоящего администратора будет первой, т.к. она находится в начале таблицы.

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

Заключение.

Обойти эти проблемы не сложно. Достаточно просто проверять длину запросов и длину данных, полученных от пользователя.

А что вы думаете об этих проблемах?

  • dizek

    Спасибо, полезно )

  • dizek

    Спасибо, полезно )

  • Taral

    Спасибо большое за статью. Я много читал статей про инфекцию SQL запросов. Но все они были про скобки ' или «. И ничего нового не говорили.. А Вы описали правда гибкий взлом базы. Теперь буду осторожнее 🙂

    • Честно говоря, большинство «дыр» как раз и возникает из-за кавычек и спецсимволов SQL.

      • Taral

        совершенно согласен. Но почти любая библиотека для работы с базой сводит на нет все эти «дыры». И потому особого интереса на представляет.

        • сводит на нет все эти «дыры»

          Только если их правильно использовать. Практически все библиотеки позволяют выполнить «сырой» запрос.

        • Taral

          Например? Я использую стандартную библиотеку CodeIgniter. И как же так прокинуть скобки внутрь?

        • Стандартный пример

          $this->db->query('SELECT * FROM users WHERE name='.$_POST['name'].' AND pass='.$_POST['pass']);

          В таком запросе нужно использовать функции
          $this->db->escape()
          $this->db->escape_str()

          Но если вы перепишите запрос так:
          $sql = "SELECT * FROM users WHERE name = ? AND pass = ?;
          $this->db->query($sql, array($_POST['name'], $_POST['pass']));
          то CI автоматически экранирует все элементы массива.

          Тоже самое происходит и при использовании Active Record Class.

          Вместо $_POST['name'] лучше использовать $this->input->post('name');

        • Taral

          Ну если постаратся то можно) Только если так писать запросы то зачем вообще использовать библиотеку?)
          Я использую связывание методов. $this->db->select('fff')->from('dddd')…
          Очень удобно и гибко. Ладно что тут спорить. Я говорил не про то как можно сделать ошибку используя библиотеку, а насколько важно знать про инфекцию через скобки используя библиотеки (используя их нормально)) ).

        • ну, вы спросили как, я привел пример 🙂
          А вообще-то вы правы, врядли кто-то будет писать запросы вручную, после того как познакомится с библиотекой.
          Как минимум будет использовать подстановку через знаки вопроса.

  • Taral

    Спасибо большое за статью. Я много читал статей про инфекцию SQL запросов. Но все они были про скобки ' или «. И ничего нового не говорили.. А Вы описали правда гибкий взлом базы. Теперь буду осторожнее 🙂

    • Честно говоря, большинство «дыр» как раз и возникает из-за кавычек и спецсимволов SQL.

      • Taral

        совершенно согласен. Но почти любая библиотека для работы с базой сводит на нет все эти «дыры». И потому особого интереса на представляет.

        • сводит на нет все эти «дыры»

          Только если их правильно использовать. Практически все библиотеки позволяют выполнить «сырой» запрос.

        • Taral

          Например? Я использую стандартную библиотеку CodeIgniter. И как же так прокинуть скобки внутрь?

        • Стандартный пример

          $this->db->query('SELECT * FROM users WHERE name='.$_POST['name'].' AND pass='.$_POST['pass']);

          В таком запросе нужно использовать функции
          $this->db->escape()
          $this->db->escape_str()

          Но если вы перепишите запрос так:
          $sql = "SELECT * FROM users WHERE name = ? AND pass = ?;
          $this->db->query($sql, array($_POST['name'], $_POST['pass']));
          то CI автоматически экранирует все элементы массива.

          Тоже самое происходит и при использовании Active Record Class.

          Вместо $_POST['name'] лучше использовать $this->input->post('name');

        • Taral

          Ну если постаратся то можно) Только если так писать запросы то зачем вообще использовать библиотеку?)
          Я использую связывание методов. $this->db->select('fff')->from('dddd')…
          Очень удобно и гибко. Ладно что тут спорить. Я говорил не про то как можно сделать ошибку используя библиотеку, а насколько важно знать про инфекцию через скобки используя библиотеки (используя их нормально)) ).

        • ну, вы спросили как, я привел пример 🙂
          А вообще-то вы правы, врядли кто-то будет писать запросы вручную, после того как познакомится с библиотекой.
          Как минимум будет использовать подстановку через знаки вопроса.

  • Владимир

    Отличная статейка

  • Владимир

    Отличная статейка

  • Maxim

    Хорошая статья.
    Меня интересует такой вопрос (немного не по теме)
    каким образом можно установить длинну имя поля
    т.е мне надо реализовать довольно сложные таблицы имина полей которых я не занаю(будет добавлять админ) при этом имя проеобразуется

    function code_hex($strim)
    {
    $tmp=$strim ; $tmp=bin2hex($tmp); return $tmp;}
    function decode_hex($strim)
    {
    $tmp=$strim ; $tmp=pack(«H*»,$tmp); return $tmp;}

    т.е имя возрастает в два раза но позволяет использовать любые символы.
    ?

    • Если честно, я не совсем понял, зачем нужно так преобразовывать строки.
      А длину любой строки можно проверить так:
      if (strlen($strim) > $max) {
      //ошибка
      }
      else {
      //создаём поле
      }

  • Maxim

    Хорошая статья.
    Меня интересует такой вопрос (немного не по теме)
    каким образом можно установить длинну имя поля
    т.е мне надо реализовать довольно сложные таблицы имина полей которых я не занаю(будет добавлять админ) при этом имя проеобразуется

    function code_hex($strim)
    {
    $tmp=$strim ; $tmp=bin2hex($tmp); return $tmp;}
    function decode_hex($strim)
    {
    $tmp=$strim ; $tmp=pack(«H*»,$tmp); return $tmp;}

    т.е имя возрастает в два раза но позволяет использовать любые символы.
    ?

    • Если честно, я не совсем понял, зачем нужно так преобразовывать строки.
      А длину любой строки можно проверить так:
      if (strlen($strim) > $max) {
      //ошибка
      }
      else {
      //создаём поле
      }

  • Pingback: jqGrid – поиск данных()

  • А задуматься стоит!
    Спасибо!

  • А задуматься стоит!
    Спасибо!

  • AlexG

    Статья супер

  • В дополнение:
    Не надо определять права юзера только на основании логина.
    Используйте для этого пару md5(логин.пароль) 

  • alibabaevich

    Спасибо! Очень полезно!

  • head Gr.

    addslashes
    select id,pwd … where login = $login
    if md5($pass) pwd // 🙂
    getuserinfo 'select where id'.

    is enough?

    • No. You should check login & pass string length
      if (strlen($login) > 100) {
      //hack attempt, 100 — size of according column in user table
      }

      • head Gr.

        Ok, tnx.

  • Pingback: software security()

  • Pingback: broschüren()

  • Oskar

    Уже не осталось сайтов с такой уязвимостью.

    • Откуда информация? 🙂

    • head Gr.

      Да вы батенька оптимист…