Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #75, ФЕВРАЛЬ 2007 г.

осознанный пример

МИХАИЛ ФЛЕНОВ AKA HORRIFIC

Хакер, номер #075, стр. 010


HTTP://WWW.VR-ONLINE.RU

РЕАЛЬНАЯ УГРОЗА SQL INJECTION

СОБСТВЕННЫЕ ОШИБКИ ПОМОГАЮТ ЛУЧШЕ ПОНЯТЬ ТЕМУ. ОНИ ОТКЛАДЫВАЮТСЯ В ПАМЯТИ И ПОЗВОЛЯЮТ НЕ СОВЕРШАТЬ ПОДОБНОГО В БУДУЩЕМ. НО КОГДА ПРОГРАММИСТ СОЗДАЕТ «БОЕВОЙ» КОД, ТО ОШИБКИ МОГУТ ПРИВЕСТИ К ПЕЧАЛЬНЫМ ПОСЛЕДСТВИЯМ. ПРОЦЕСС ВОССТАНОВЛЕНИЯ НАС СЕЙЧАС НЕ ИНТЕРЕСУЕТ, НАС ИНТЕРЕСУЮТ САМИ ОШИБКИ. ДЛЯ ЭТОГО ПРОТЕСТИРУЕМ НЕСКОЛЬКО САЙТОВ НА ПРЕДМЕТ УЯЗВИМОСТЕЙ И ПРОАНАЛИЗИРУЕМ РЕЗУЛЬТАТ

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

[APA Help Center.]

Чтобы найти первую жертву, запускаем с помощью google.com поиск по Сети, где в URL встречается .php и имя параметра id. Первый сайт, который заинтересовал своим названием, оказался пустым, точнее сценарии не получали параметров, поэтому его отбросили. А вот второй оказался более удачным — APA Help Center (www.apahelpcenter.org). Что такое APA? Оказывается, это The American Psychological Association или американская ассоциация психологов. Протестируем психологов на вшивость...

Для начала ищем сценарии, которые получают какие-либо параметры. Долго искать не пришлось, вот оно - чудо природы: www.apahelpcenter.org/featuredtopics/feature.php. Этот сценарий получает параметр id, которому передается число. Добавляем в конец значения параметра строку «and 1=0» и перегружаем страничку. Та же страничка, только пустая. Попробуем добавить еще union select 'Test' — инжектируется дополнительный запрос, который просто возвращает слово Test. Если это слово появится где-то на странице, значит, нам сопутствует удача. В центре страницы появилась надпись Next page, после которой красовалась ссылка с именем Test. Вот куда попало имя инжектированного запроса. Итак, URL, который подтверждает наличие уязвимости, выглядит следующим образом:

ЛИСТИНГ

http://www.apahelpcenter.org/featuredtopics/feature.php?id=38%20and%201=0%20union%20select%20'Test'--

Теперь поищем что-то более интересное. Посмотрим имя базы данных, версию и имя пользователя. Для этого в URL меняем 'Test' на DATABASE() и получаем искомые данные:

ЛИСТИНГ

База данных: apahelpcenter

Версия: 4.0.20a-debug

Имя пользователя: prac01web@prac01.apa.org

Прекрасненько. А что еще тут есть? Доступа к системной базе данных MySQL не оказалось. Видимо, хостер — не дурак и запретил любые телодвижения в эту сторону. Попробуем подобрать имена таблиц. И снова удача на нашей стороне, потому что американцы не любят использовать префиксы, а используют банальные слова для именования объектов. Пара минут страдания, после которых выясняется, что на сервере есть таблицы с именами articles и users. Конечно же, вторая таблица наиболее интересна. Но какие в ней поля? Методом подбора выясняется, что есть поля id и password. Следующий запрос показал пароль первого пользователя в таблице:

ЛИСТИНГ

http://www.apahelpcenter.org/featuredtopics/feature.php?id=38%20and%201=0%20union%20select%20password%20FROM%20users%20limit%200,1--

Пароль банален — apanick. Американская наивность :). Так как на сайте нет регистрации, а в таблице пользователей всего две записи, можно предположить, что где-то есть админка и это пасс - для доступа в нее. Но админку не нашли. Облазили весь сайт: нигде нет поля для ввода пароля, а сценарии типа /admin/index.php, admin.php — не существуют. Оставим это для других и двигаемся дальше.

[Америка вчера.]

Поищем ошибки в USA-нете. Нравится американский Net тем, что здесь ошибок на сайтах очень много, видимо, там экономят на программистах. Или программисты - из прошлого века и не знают о существовании таких угроз, как SQL Injection.

Следующую жертву долго искать не пришлось. В процессе поиска натыкаемся на www.newspaperads.com. Заинтересовал тем, что:

1 КРУПНЫЙ;

2 ПРИНАДЛЕЖИТ ЗНАМЕНИТОЙ USA TODAY;

3 ПОСТРОЕН НА ASP + MS SQL SERVER.

Отправим USA Today в Yesterday, ибо дыр здесь превеликое множество. Наугад тыкаем по ссылкам и попадаем на страницу:

ЛИСТИНГ

http://www.newspaperads.com/usatoday/results.asp?subcatid=1600&interfaceid=82&parent=Categories&subcatname=Travel+Specials

Здесь собраны статьи на тему путешествий. Попробуем попутешествовать по базе данных newspaperads.com. Все оформлено в виде таблицы из трех колонок: Advertiser, Summary и Date. Обожаю страницы с таблицами, потому что в них больше пространства для злых манипуляций и можно видеть результат. Странице передается несколько параметров, но самый интересный — subcatid. По имени уже ясно, что это идентификатор, по которому происходит поиск по базе. Попробуем добавить в конец параметра одинарную кавычку. Ага, произошла ошибка запроса. То, что доктор прописал.

Изучаем ошибку и понимаем, что в ней куча всякой ерунды, но чтобы инжектировать SQL-код, необходимо в конец параметра добавить две закрывающиеся круглые скобки и комментарием отшвырнуть все остальные условия. Итак, пробуем внедрить в параметр subcatid следующее значение:

ЛИСТИНГ

1600)) and 1=0 -

Условие «1=0» поставили для того, чтобы результат был заведомо пустым. Нам не нужны статьи про путешествия, нам нужно содержимое базы данных. Теперь подбираем количество полей, которые возвращают запрос. Для этого в запрос внедряем объединение UNION SELECT NULL, ... последовательно увеличивая количество NULL-полей. В итоге получилось 11 полей. Именно при таком количестве NULL-значений ошибка запроса исчезла и появилась таблица, правда, пустая. Теперь нужно узнать, какие поля и куда попадают на форме. Для этого можно во внедренном запросе выбирать не нулевые значения, а какие-то числа, уникальные для каждого поля. Например, следующий запрос выбирает в каждом поле число от 0 до 11:

ЛИСТИНГ

1600)) and 1=0 union all select 1,2,3,4,5,6,7,8,9,10,11--

Ага, число 7 появилось в колонке Summary. Будем использовать седьмое поле для того, чтобы просматривать системные данные. Нас интересуют первым делом имена таблиц, которые используются в базе данных. Их можно получить из таблицы INFORMATION_SCHEMA.TABLES. Инжектируем следующий код в параметр subcatid:

ЛИСТИНГ

1600)) and 1=0 union all select 1,2,3,4,5,6,TABLE_NAME,8,9,0,11 from INFORMATION_SCHEMA.TABLES--

Все прошло удачно и перед нами список таблиц базы данных. Самое интересное, что показываются только первые 20 строк, но внизу страницы есть навигация по страницам 1, 2, 3... Навигация очень хорошая, потому что путешествует даже по инжектированному запросу :). Так что не нужно ограничивать вывод данных с помощью инъекции.

Можно было бы двигаться дальше и организовать дефейс или уничтожить данные, но это не наш метод. Мы написали админам об ошибке. Спасибо newspaperads.com за приятное путешествие по их базе :).

[cold fusion.]

Следующую жертву решили искать среди сайтов, построенных на технологии Macromedia Cold Fusion. Долго искать не пришлось. Первым на глаза попался сайт сената США, а точнее его департамента по коммерции (commerce.senate.gov). Просмотр показал, что большинство параметров не фильтруют одинарную кавычку. Например, в следующем URL уязвим параметр id:

ЛИСТИНГ

commerce.senate.gov/hearings/witnesslist.cfm?id=1705

Если добавить к параметру «id=1705 and 1=1», то сценарий проглотит эту инъекцию и не подавится. Одинарная кавычка также не фильтруется. Но подобрать количество полей не удалось, потому что фильтруются запятые, тире, слеши и знак процента. Шутить с сенатом не особо хочется, потому что это чревато серьезными последствиями — мы оставили сайт в покое.

Кстати, сервер senate.gov содержит не только сайты сенатов, но и отдельных сенаторов. И большинство параметров фильтруется не очень качественно. Мы особо не ковырялись, потому что дорожим своей свободой :).

[удобряем.]

Следующая жертва — www.compostingcouncil.org. Сайт посвящен каким-то консультациям по удобрениям. Давай попробуем окучить базу данных, тем более что программер вообще не знает такого слова, как фильтрация. Берем любой параметр и начинаем его удобрять. В качестве целеуказания выбрали следующий сценарий:

ЛИСТИНГ

http://www.compostingcouncil.org/section.cfm

Здесь уязвим параметр id. Если добавить в конец параметра одинарную кавычку, то произойдет ошибка выполнения сценария. Рассмотрим ошибку подробнее:

ЛИСТИНГ

ODBC Error Code = 37000 (Syntax error or access violation)

[Microsoft][ODBC Microsoft Access Driver] Syntax error (missing operator) in query expression 'id = 29'''.

The error occurred while processing an element with a general identifier of (CFQUERY), occupying document position (1:1) to (1:59).

Очевидно, что тут используется ODBC Microsoft Access Driver. То есть в качестве базы данных используется банальный MS Access. Попробуем поковыряться.

Для начала попробуем определить количество полей, возвращаемых запросом. Оказалось, что Access не может возвращать безымянные SELECT, и обязательно должна быть секция FROM с именем таблиц. Будем знать. Так как на сервере есть регистрация, попробуем предположить, что существует таблица users. Вбиваем в браузер следующий URL:

ЛИСТИНГ

http://www.compostingcouncil.org/section.cfm?id=29%20union%20select%201%20from%20users

Великолепно! Такая таблица действительно существует, потому что сервер сообщает нам, что количество полей в объединенном запросе не совпадает. Теперь можно подбирать поля. Получилось 13 штук. Несчастливое число, особенно для программиста этого сайта. Попробуем подобрать поля, которые есть в таблице users. Подбор показал, что здесь присутствуют userid, email и memberpwd. В принципе, для взлома этого уже достаточно, ведь при регистрации спрашивают мыло и пароль, а эти данные уже можно вытащить из таблицы users с помощью инжектирования.

Теперь попробуем определить, какие еще таблицы есть в базе. Справка MS Access подсказала, что есть такая системная таблица MSysObjects, в которой в поле name находятся имена всех таблиц базы данных. Попробуем инжектировать запрос SELECT к этой таблице:

ЛИСТИНГ

http://www.compostingcouncil.org/section.cfm?id=29%20union%20select%201,2,3,4,5,6,name,8,9,10,11,12,13%20from%20MSysObjects

Поле name вставили в седьмую позицию инжектированного запроса. Содержимое этого поля выводится в виде таблицы на результирующей web-странице (результат смотри на рисунке).

Таким образом, есть все мыльники, идентификаторы и пароли зарегистрированных пользователей (зарегистрировано только три человека и один из них явно админ). И есть имена всех таблиц. На этом исследование можно прекращать.

[няньки и акушерки.]

Следующий сайт, который попал на глаза — www.midwife.com. Владелец — колледж нянек и акушерок. Давай исследовать эту жертву. Оформлен сайт неплохо, сценарии на Macromedia ColdFusion написаны достаточно интересно.

Попробуем всадить в какой-нибудь параметр символ одинарной кавычки и посмотрим на результат. На первый взгляд, результат обнадеживающий, потому что произошла ошибка выполнения сценария. Сообщение об ошибке гласит, что на сервере используется MySQL и база данных с именем plasmacms. Может быть, где-то существует такой CMS по имени Plasma, а может быть это самописный движок, просто админ решил его так красиво назвать. Попробуем произвести роды без этого, хотя можно где-нибудь на халате записать информацию, на будущее.

Смотрим, на чем же все-таки заткнулся сценарий. А он заткнулся на запросе:

ЛИСТИНГ

SELECT pageID,pageBody,pageTitle,pageHeader,

pageFooter,pageFolder,pageAccess,pageURL

FROM plasmaContent

WHERE pageID=75'' LIMIT 1

Попробуем подумать, что здесь происходит. Судя по именам полей, которые вытаскиваются из таблицы plasmaContent, CMS действительно хорошая. Дело в том, что по номеру ID из таблицы выбирается заголовок, шапка, подвал и URL страницы, которую нужно отобразить. Это значит, что если попытаться инжектировать свой код, ничего не выйдет. В поле pageURL должен быть какой-то URL страницы, который узнать проблематично. Кто его знает, с каким дефектом няньки и акушерки произвели на свет своего web-программиста, и что он засунул в это поле. Попытки вставить что-либо в это поле не увенчались успехом.

Для инжектирования SQL-кода нужно использовать следующий URL:

ЛИСТИНГ

http://www.midwife.org/news.cfm?id=75 and 1=0 union select 1,2,3,4,5,6,7,8

Но вместо числа 8 на конце необходимо указать какое-то реальное значение из базы данных, иначе сценарий вываливается в ошибку «URL не найден».

Таким образом построены абсолютно все страницы на сайте. Но не стоит опускать руки: берем скальпель и делаем кесарево сечение. А точнее, - посмотрим, что еще есть в сообщении об ошибке. А там есть ссылка на файл:

ЛИСТИНГ

C:\Inetpub\wwwroot\Clients\midwife.org\www\plasmacms\cfm\page.cfc

Это наиболее интересный сценарий, так как в нем и происходит формирование страницы. Но доступ к нему из строки URL запрещен, и при попытке обратиться к нему вываливается сообщение с просьбой ввести пароль админа. Посмотрим, на какие еще файлы ругается сценарий. Следующим в списке идет:

ЛИСТИНГ

C:\Inetpub\wwwroot\Clients\midwife.org\www\header.cfm

Попробуем банально загрузить его: http://www.midwife.org/header.cfm. В ответ видим ошибку, в которой отображается даже код сценария, который сработал неверно. Самое интересное в этом коде — следующая строка:

ЛИСТИНГ

<cfif isDefined("url.id") and not isDefined("newsPage")>

Проблема явно кроется в том, что в URL нет параметра id. Попробуем его туда впихнуть и загрузим страничку так: http://www.midwife.org/header.cfm?id=75. В ответ загрузилась шапка сайта, но внизу снова сообщение об ошибке и жирным цветом выделена строка:

ЛИСТИНГ

<td class="pageHeader"><cfoutput>#subHeader#</cfoutput></td>

На этот раз проблема в том, что переменная subHeader не имеет значения. Попробуем задать ей значение через URL, то есть добавим в URL следующий текст:

ЛИСТИНГ

subHeader=<h1>Hello%20from%20Horrific</h1>.

Все сработало. Ребенок родился красивым и здоровым, а посреди страницы красуется надпись «Hello from Horrific». Именно этот текст передался через URL параметр в переменную subHeader.

Получается, что мы можем инжектировать в страницу любой HTML-код, в том числе и JavaScript, а это уже пахнет атакой XSS. Если попытаться загрузить следующий URL, то на странице с помощью JavaScript отобразится окно с сообщением:

ЛИСТИНГ

http://www.midwife.org/header.cfm?id=75&subHeader=<script>alert('Привет')</ script>

Так как на сайте есть регистрация и целый раздел member, остается только:

1 ВЫЯСНИТЬ E-MAIL АДРЕСА ЗАРЕГИСТРИРОВАННЫХ ПОЛЬЗОВАТЕЛЕЙ;

2 ПОДГОТОВИТЬ URL, КОТОРЫЙ БУДЕТ ЗАГРУЖАТЬ СТРАНИЦУ С JAVASCRIPT И ОТПРАВЛЯТЬ COOKIE НА ЗАРАНЕЕ ПОДГОТОВЛЕННЫЙ СЦЕНАРИЙ.

Одним словом, классический XSS. Роды прошли удачно. Сообщим нянькам и акушеркам, что их web-программист родился с дефектом мозга :).

[учим Berkeley жизни.]

Следующей жертвой исследований стал знаменитый институт Berkeley. Покажем американцам жестокую действительность! Итак, заходим на сайт http://cshe.berkeley.edu/ и смотрим, что тут есть. Во-первых, сразу бросаются в глаза публикации статей. Почему бросаются? Да потому что они выбираются по параметру s, который передается через url. Проверим этот параметр на вшивость.

Для начала попробуем добавить в его конец одинарную кавычку. В результате грузится страница, на которой сообщают, что нет публикации для отображения. Уже неплохо. А если добавить «and 1=1»? Тогда публикация вернется на родину. Все ясно, диагноз — SQL Injection в скрытом виде. То есть ошибка есть, но сообщения об ошибке нет. Ну, ничего, это не сильно усложнит задачу.

Ничего нового не придумываем, а просто подбираем количество полей, возвращаемых запросом в сценарии. Это делается путем добавления в конец параметра объединения select и постепенного увеличения количества полей до тех пор, пока страница снова не отобразится корректно. Получилось четыре поля, и следующий URL отобразился корректно:

ЛИСТИНГ

http://cshe.berkeley.edu/publications/publications.php?s=1%20and%201=1%20union%20select%201,2,3,4

Теперь делаем так, чтобы запрос, прописанный в сценарии, ничего не вернул. Для этого в URL, приведенном выше, условие «1=1» заменяем на «1=0». Теперь исчезнет статья с ID равным 1, а появится то, что возвращает внедренный запрос. А внедренный запрос возвращает четыре поля со значениями от 1 до 4. Это сделано специально, чтобы проще было найти, куда на странице попадают эти поля. Числа 1 не оказалось в форме. Видимо, это идентификатор, который не отображается. А вот числа от 2 до 3 отображаются, и, значит, в любую из этих позиций можно внедрять имена полей или другие функции.

Попробуем выяснить версию, имя пользователя и имя базы данных. Для этого можно вместо последней четверки в URL последовательно поставить имена функций VERSION(), USER() и DATABASE(). Обломись бабка, мы на пароходе! Ни одна из этих функций ничего не вернула, а в ответ браузер грузит страницу с сообщением о том, что ничего нет для отображения. Это действительно облом. Сообщений об ошибках нет, и, значит, мы не можем точно определить, какая перед нами база данных. А что если это не MySQL, и поэтому функций нет?

Проверим, действительно ли перед нами MySQL. Попробуем объединенный запрос связать с таблицей MySQL.user:

ЛИСТИНГ

http://cshe.berkeley.edu/publications/publications.php?s=1%20and%201=0%20union%20select%201,2,3,4%20from%20mysql.user

Запрос проходит успешно, значит, база данных MySQL существует и в ней есть таблица user. Попробуем вывести имя пользователя из этой таблицы. Вместо четверки вставляем имя поля user и снова получаем облом. Да что такое?! Может, это все же другая база данных, просто для маскировки создали базу и таблицу mysql.user? Еще один тест — попробуем вместо цифр во внедренном запросе поставить текст. Вот оно - счастье админа: запрос может возвращать только числа, а если поставить строку, то результат — полный облом.

Что же делать? Допустим все же, что перед нами самый популярный MySQL. Тогда можно будет попробовать передавать текст с помощью функции CHAR. Формируем следующую строку:

ЛИСТИНГ

CHAR(60,72,49,62,117,115,101,114,60,47,72,49,62)

Если перевести в удобочитаемый текст, то эти коды превращаются в строчку:

ЛИСТИНГ

<h1>user</h1>

Ставим этот код вместо третьего параметра внедренного запроса и наслаждаемся. Наконец-то. Заветное слово user появилось на странице, причем с учетом форматирования тега <h1>. Получается, что таким образом можно внедрять и JavaScript-код!

Итак, подведем итог. Мы уже можем инжектировать код и даже научились передавать строки с помощью функции CHAR. Мы также можем быть практически уверенными, что перед нами самая настоящая база данных MySQL. А что это нам дает? А то, что есть еще такая функция, как LOAD_FILE, которая может загружать любой файл на сервере. Попробуем загрузить файл паролей /etc/passwd. Не забываем, что строки нужно кодировать, а значит, обращение к файлу должно быть:

ЛИСТИНГ

LOAD_FILE(char(47,101,116,99,47,112,97,115,115,119,100))

В скобочках закодирован путь /etc/passwd.

Вставляем этот код в URL на место любого из трех параметров (кроме первого) и загружаем страничку. Перед нами список пользователей собственной персоной. Получается, что админам еще учиться и учиться. И это знаменитый Беркли?!

Мы можем просматривать файлы на сервере, на которые хватит прав...

[удачи!]

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

Всем администраторам приведенных сайтов мы разослали сообщение с описанием ошибки... Позор на их седые головы!

НА ОШИБКАХ УЧАТСЯ, А В БОЕВЫХ УСЛОВИЯХ МУЧАЮТСЯ

ЗНАНИЕ ЯЗЫКА SQL И ОСОБЕННОСТЕЙ РАЗЛИЧНЫХ БАЗ ДАННЫХ ПОМОЖЕТ В ИССЛЕДОВАНИЯХ

Содержание