| A+ A- |
Детективная история про SQL injection, местами blind
Доброго времени суток!
Не подумал бы писать статью об этом, т.к. думал, что тема довольно заезжена. Но, судя по этой статье аудитории интересно. Окончательно меня убедил в том, что писать надо вот этот комментарий.
Данная история произошла со «знакомым знакомого моего знакомого», но, для краткости буду писать цитатами с его слов, употребляя просто «я». Дело было полторы недели назад. Поехали.
Понадобилось мне изучить один европейский язык, в свете возможного переезда в одну европейскую страну. И нашёл я замечательный сайт, на котором предлагалось учить язык с помощью подкастов. Сами подкасты распространяются бесплатно, но можно купить PDF с записями уроков и упражнениями. Мне эти записи не очень нужны, но моя жена, в отличие от меня, вовсе не аудиал, а язык учить ей тоже надо. Прежде, чем что-то купить в интернете, я внимательно изучаю сайт продавца — не хочется, чтобы мои данные куда-то утекли. И в этом случае все оказалось более чем плохо. Желание покупать что-либо сразу отпало. Но и остаться без PDF неспортивно. В итоге я решил попробовать воспользоваться одной из найденных уязвимостей. Сразу скажу, что я принципиально не пользуюсь никакими автоматическими сканерами уязвимостей и принципиально не причиняю вреда пользователям ресурса — они же не виноваты, что хозяин ресурса коряво его написал. Поэтому моими инструментами являются соображалка и теоретические знания по причинам возникновения и использованию уязвимостей.
Первым делом я посмотрел на несколько демо-примеров доступных для загрузки PDF. Сначала пользователь отправлялся по адресу:
В этот момент проверяется имеет ли текущий пользователь право скачать заданный PDF. Если да — идет редирект по адресу:
Сразу же оказалось, что данный скрипт отдает указанный файл ничего не проверяя. Т.к. доступный пример для урока №1 имел имя файла 001.pdf я решил попробовать получить все файлы перебором. Если бы все было так просто, то и писать было бы не о чем. Но таким образом удалось добыть только первые 100 файлов. Остальные имели в своем имени timestamp времени создания и перебирать их стало невозможно, т.к. время создания отличалось на несколько месяцев.
Довольно скоро обнаружилась банальнейшая SQL injection в GET параметре:
Вроде бы дальше ее использование строится очень просто:
Но проблемы начались с первого пункта — определить количество полей в запросе не удалось. При любом количестве полей в UNION SELECT и при любом номере в ORDER BY n я получал сообщение «You have error in your syntax...»
На самом деле я совершенно случайно догадался в чем именно проблема — попробовав сделать GROUP BY 1. На это я получил ошибку «cannot group by cnt». Оказалось, что уязвимый параметр используется дважды (ну по крайней мере это предположение мне опровергнуть не удалось).
Сначала выбирается количество записей с указанным id:
Если количество записей 0, то считается, что страница не найдена и происходит редирект на главную страницу. Если записей не 0, вытаскивается информация:
Теперь становится понятно, почему не удалось выяснить количество полей в запросе — их 2 и в одном из них всегда окажется неверное количество полей в UNION. Придумать способ, который позволил бы вставить разное количество полей в UNION в первый и второй запрос, мне не удалось. И в этот момент SQL injection стала blind. Мне не удалось подобрать имя таблицы с путями к файлам, но зато удалось подобрать имя таблицы с данными пользователей (MySQL 4.1).
Уважаемые разработчики, не делайте 2 запроса, там где можно сделать один! В этом случае можно было вместо SELECT count(*) проверить количество записей возвращенных запросом SELECT *
Теперь осталось придумать способ получать полезную информацию. Я сделал так:
Что мы здесь видим:
Таким образом по HTTP заголовку можно понять, верное ли условие мы передали. Сначала определяем длину имени пользователя, затем побуквенно двоичным поиском вытаскиваем само имя ( lower(substr(username,1,1)) in ('a','b','c') ). Затем побуквенно вытаскиваем пароль. Но оказывается, что пароль хеширован в md5. И хотя хэширование без соли, но пароли администраторов сайта все равно подобрать не удалось (в rainbow tables нет, а брутфорсом на нетбуке заниматься не хотелось; да и неспортивно это).
После некоторых раздумий было решено идти другим путем. Т.к. в базе оказалось более 60000 пользователей я предположил, что у многих из них популярные пароли. А дальше нужно было всего лишь побуквенно вытащить имена пользователей у которых хэш пароля равен md5('password') — их оказалось больше 100 и среди них были люди купившие нужные PDF. И они любезно согласились ими со мной поделиться.
Все это было сделано с помощью очень простого скрипта, который отправлял HEAD-запрос (а зачем нам тело страницы?) и смотрел заголовок ответа. Если 200 — условие верно, если 302 — неверно.
Зачем все это написано? Чтобы показать, что нужно знать суть и причины возникновения уязвимостей, а не заучивать способы их использования. Все способы использования SQL injection, которые я видел в интернете, предлагали определить количество полей через ORDER BY 5 или UNION SELECT 1,2,3… И человек, не захотевший подумать, ушел бы с сайта ни с чем.
Кроме того, я слегка горд своим обходным маневром вместо взлома хэша. Ну и не так давно высказывался скептицизм по поводу существования таких уязвимостей в современном интернете и по поводу практического применения blind SQL injection.
P.S. Все совпадения с реальностью являются случайными. Голоса знаменитостей сымитированы, причем убого.
Не подумал бы писать статью об этом, т.к. думал, что тема довольно заезжена. Но, судя по этой статье аудитории интересно. Окончательно меня убедил в том, что писать надо вот этот комментарий.
Данная история произошла со «знакомым знакомого моего знакомого», но, для краткости буду писать цитатами с его слов, употребляя просто «я». Дело было полторы недели назад. Поехали.
Понадобилось мне изучить один европейский язык, в свете возможного переезда в одну европейскую страну. И нашёл я замечательный сайт, на котором предлагалось учить язык с помощью подкастов. Сами подкасты распространяются бесплатно, но можно купить PDF с записями уроков и упражнениями. Мне эти записи не очень нужны, но моя жена, в отличие от меня, вовсе не аудиал, а язык учить ей тоже надо. Прежде, чем что-то купить в интернете, я внимательно изучаю сайт продавца — не хочется, чтобы мои данные куда-то утекли. И в этом случае все оказалось более чем плохо. Желание покупать что-либо сразу отпало. Но и остаться без PDF неспортивно. В итоге я решил попробовать воспользоваться одной из найденных уязвимостей. Сразу скажу, что я принципиально не пользуюсь никакими автоматическими сканерами уязвимостей и принципиально не причиняю вреда пользователям ресурса — они же не виноваты, что хозяин ресурса коряво его написал. Поэтому моими инструментами являются соображалка и теоретические знания по причинам возникновения и использованию уязвимостей.
Начало
Первым делом я посмотрел на несколько демо-примеров доступных для загрузки PDF. Сначала пользователь отправлялся по адресу:
/guide.php?id=lesson_idВ этот момент проверяется имеет ли текущий пользователь право скачать заданный PDF. Если да — идет редирект по адресу:
/download.php?f=filename.pdfСразу же оказалось, что данный скрипт отдает указанный файл ничего не проверяя. Т.к. доступный пример для урока №1 имел имя файла 001.pdf я решил попробовать получить все файлы перебором. Если бы все было так просто, то и писать было бы не о чем. Но таким образом удалось добыть только первые 100 файлов. Остальные имели в своем имени timestamp времени создания и перебирать их стало невозможно, т.к. время создания отличалось на несколько месяцев.
Раскручиваем SQL injection
Довольно скоро обнаружилась банальнейшая SQL injection в GET параметре:
/some_script.php?id=123Вроде бы дальше ее использование строится очень просто:
- Определить количество параметров в запросе
- Подобрать имена таблиц и полей (в случае MySQL 5.0 и выше — выбрать их из information_schema)
- Получить нужные имена файлов
- Скачать сами файлы
Но проблемы начались с первого пункта — определить количество полей в запросе не удалось. При любом количестве полей в UNION SELECT и при любом номере в ORDER BY n я получал сообщение «You have error in your syntax...»
На самом деле я совершенно случайно догадался в чем именно проблема — попробовав сделать GROUP BY 1. На это я получил ошибку «cannot group by cnt». Оказалось, что уязвимый параметр используется дважды (ну по крайней мере это предположение мне опровергнуть не удалось).
Сначала выбирается количество записей с указанным id:
SELECT count(*) FROM table where id=123Если количество записей 0, то считается, что страница не найдена и происходит редирект на главную страницу. Если записей не 0, вытаскивается информация:
SELECT * FROM table where id=123Теперь становится понятно, почему не удалось выяснить количество полей в запросе — их 2 и в одном из них всегда окажется неверное количество полей в UNION. Придумать способ, который позволил бы вставить разное количество полей в UNION в первый и второй запрос, мне не удалось. И в этот момент SQL injection стала blind. Мне не удалось подобрать имя таблицы с путями к файлам, но зато удалось подобрать имя таблицы с данными пользователей (MySQL 4.1).
Уважаемые разработчики, не делайте 2 запроса, там где можно сделать один! В этом случае можно было вместо SELECT count(*) проверить количество записей возвращенных запросом SELECT *
Теперь осталось придумать способ получать полезную информацию. Я сделал так:
/script.php?id=123 limit 0,0 union all select length(username)>4 from tablename limit 0,1--Что мы здесь видим:
- 123 limit 0,0 — т.к. count(*) всегда вернет нам ровно одну запись и мы не узнаем, что именно было возвращено нашей частью запроса, нужно убрать его из результата
- union all select length(username)>4 from tablename limit 0,1-- — если длина имени пользователя больше 4, то условие верно, MySQL вернет единицу, а затем ошибку, при попытке выполнить второй запрос. Если условие неверно — вернет 0 и произойдет редирект. Ну и '--' для комментария в конце
Таким образом по HTTP заголовку можно понять, верное ли условие мы передали. Сначала определяем длину имени пользователя, затем побуквенно двоичным поиском вытаскиваем само имя ( lower(substr(username,1,1)) in ('a','b','c') ). Затем побуквенно вытаскиваем пароль. Но оказывается, что пароль хеширован в md5. И хотя хэширование без соли, но пароли администраторов сайта все равно подобрать не удалось (в rainbow tables нет, а брутфорсом на нетбуке заниматься не хотелось; да и неспортивно это).
После некоторых раздумий было решено идти другим путем. Т.к. в базе оказалось более 60000 пользователей я предположил, что у многих из них популярные пароли. А дальше нужно было всего лишь побуквенно вытащить имена пользователей у которых хэш пароля равен md5('password') — их оказалось больше 100 и среди них были люди купившие нужные PDF. И они любезно согласились ими со мной поделиться.
Все это было сделано с помощью очень простого скрипта, который отправлял HEAD-запрос (а зачем нам тело страницы?) и смотрел заголовок ответа. Если 200 — условие верно, если 302 — неверно.
Заключение
Зачем все это написано? Чтобы показать, что нужно знать суть и причины возникновения уязвимостей, а не заучивать способы их использования. Все способы использования SQL injection, которые я видел в интернете, предлагали определить количество полей через ORDER BY 5 или UNION SELECT 1,2,3… И человек, не захотевший подумать, ушел бы с сайта ни с чем.
Кроме того, я слегка горд своим обходным маневром вместо взлома хэша. Ну и не так давно высказывался скептицизм по поводу существования таких уязвимостей в современном интернете и по поводу практического применения blind SQL injection.
P.S. Все совпадения с реальностью являются случайными. Голоса знаменитостей сымитированы, причем убого.
—
19 декабря 2011, 20:51
21

вторник, декабря 20, 2011
Unknown

Posted in: 



комментарии (17)