Безопасный SQL, PHP
|
Как же обезопасить SQL запрос в своем web приложении? Об это и пойдет рассказ в этой статье.
Верой и правдой, а самое главное долго и стабильно отработала ниже описанная функция, но пришел новый собрат более удобный и быстрый, поэтому выкладываю это здесь. (функция работала до 12 декабря 2009 года более 3 лет)
История SQL
Итак, в свое время, когда я только начинал изучать MySQL и PHP впервые же дни у меня появилось потребность узнавать по каждому SQL запросу его время, ошибки, описание, количество возвращенных записей. Понятное дело, что вопрос стоял обезопасить запрос и получить максимум debug информации от системы. Итак, многие горе программисты рекомендуют, мол все писец мля буду выставляем в htaccess
php_value display_errors 0
и Вася не царапайся мол Мега супер защита от хакеров. Это было сразу понятно, что в первую очередь это защита от самого себя и от таких горе программеров. От себя хочу заметить, что программист должен вообще избавляться от каких либо ошибок подобного рода под любыми возможными варианта вызова страницы. Вот теперь вопрос как от них избавляться, если их не видно? А находить ошибки без включенного мониторинга ошибок это вообще сверх мазо над своим мозгом, особенно при вычислении логической ошибки.
Извиняюсь, отвлекся от сути темы.
Так вот чтобы вычислять ошибки оперативно, пришло мне в мою больную голову идея о сборе всей sql инфы в кучку для последующего анализа. Так как надо быть качественным онанистом, чтобы сидеть и пощелкивать каждую страницу в проекте и выявлять sql ошибки, особенно когда в проекте более 10 000 страниц, нужно, что-то предпринять. Поэтому мной было принято решение – а давай-ка с коллекционируем все SQL запросы в базе данных – так и сделал, но со временем запросов стало много и разбирать, что где стало трудно. Еще немного проанализировав я понял, что меня (да и многих) именно интересуют 3 категории запросов. А именно: очень часто практически всегда SQL запросы с ошибками, далее SQL запросы с какими либо изменениями типа DELETE UPDATE и так далее и уже менее важные, но важные это SQL запросы аля SELECT, для выяснения, так сказать запросов которые подвешивают систему.
Поэтому из всей каши получилось три таблички sql_select – для обычных select запросов, sql_error – для запросов с ошибками и sql_update, для запросов каким либо образом изменяющие данные в базе данных.
В результате получился не хилый такой обработчик. Ясное дело, что каждый запрос на каждой странице писать такую портянку бред сивой кобылы. Поэтому решил это все вынести в отдельную функцию и вызывать ее вместо обычного mysql_query ну и за одним решил туда впендюрить все правила безопасносго SQL правописания ну и в последствии чтобы это не мешала ко всем запросам добавлять все, что моей душе угодно, особенно когда вычитаю где-нибудь что-нибудь этакое. Также со временем появилась потребность в опции в отключении в некоторых случаях такой подробной слежки за запросами, отвечает за это сейчас входящий, необязательный параметр debug. Особенно это актуально при циклах. Тогда вся debug информация проходила мимо.
Итого, что же у нас получилась:
Функция выполняет:
- делает выполнение кода безопасным и более не требуется нигде на сайте задалбливать себя мега проверками sql запросов в каждом неудобно месте
- сохраняет страницу и url, где происходил вызов sql запроса, чтобы уже конкретно искать его на нужной странице, а не колдовать с бубном «А где?»
- сохраняет параметры вызова страницы, чтобы легко можно было воспроизвести ситуацию, при которой появилась та или иная ситуация.
- сохраняет время, затраченное на выполнение SQL запроса.
- сохраняет дату и время выполнение запроса, дабы вычислить подозрительные переборы или подборы SQL запросов
- все данные складирует и записывает в базу данных, для дальнейшего просмотра и анализа.
- сохраняет сам SQL запрос, чтобы можно было его легко скопировать и вставить в какой-нибудь супер мега дебагер sql запросов.
Функция возвращает:
- результат такой же что возвращает mysql_query, сделано для совместимости и удобства вызова функции
- в переменную error записывает номер ошибки, если такой присутствовал при вызове sql запроса, в случае положительного результата возвращает 0. необязательный параметр
- в переменную count записывает количество возвращаемых записей, если был select – также необязательный параметр
Ну и вот собственно сама функция. Пользуйтесь на здоровье.
// Исполняет SQL запрос и возвращает нужные параметры
function sdf_db_select($_SQL,&$_error=0,&$_count=0,$_debug=true)
// _SQL - сам запрос
// _error - возвращает номер ошибки
// _count - возвращает кол-во записей полученных в результате запроса
// _debug - true - вести debug информацию
// Преимущества:
// разделяет запросы SELECT и INSERT, UPDATE, DELETE
// по разным таблицам для удобства нахождения ошибок
// в случае ошибки, запрос записывает в отдельную базу по ошибкам
{
// если входящий запрос пустой, то сразу прекращаем работу функции
if ($_SQL=='') { return; }
// если в запросе встретится мульти запрос
// тоже чикаем так как не фига в системе у нас не используется такая фича
// Против SQL иньекций
$sd_sql=mysql_real_escape_string($_SQL);
$t=strpos($sd_sql,'SELECT ');
// если встречается SELECT то это пишем в другую таблицу
$table=strpos($sd_sql,'SELECT ')===false?'sql_update':'sql_select';
// формируем строку - с какой страницы был вызвал SQL запрос
// Служит для быстрого нахождения ошибок в системе
// определяем все входящие параметры
$command_line=$_SERVER['REQUEST_URI']!=''?'?'.$_SERVER['REQUEST_URI']:'';
// откуда был вызван SQL запрос
$url='http://'.$_SERVER['SERVER_NAME'].$_SERVER['SCRIPT_NAME'].$command_line;
// запомнить текущее время в микросекундах
// чтобы узнать скорость выполнения SQL запроса
$start_time_sql=microtime();
// ##
// Непосредственно выполняем сам запрос
$res=mysql_query($_SQL);
// ##
$error=mysql_errno();// номер ошибки
$_error=$error;// номер ошибки. 0 все хорошо
$error_text=mysql_error();// текст ошибки
if ($error!=0) { $table='sql_errors'; }// если при запросе была ошибка
// определяемся куда будем писать
$user_id=sdf_user_active_id();// узнаем ID номер пользователя
switch($table)
{ case('sql_errors'):
{ $sq='INSERT `g_sql_errors` SET `text_`="'.$sd_sql.'", `link_`="'.$url.'", ';
$sq.=`error`="'.$error.'", `error_text`="'.$error_text.'"';
$r=mysql_query($sq);// записываем данные по запросу в отдельную таблицу
break;
}
case('sql_update'):
{ $sq='INSERT `g_sql_update` SET `text_`="'.$sd_sql.'", `link_`="'.$url.'"';
if ($_debug) { $r=mysql_query($sq); }// записываем данные по запросу в отдельную таблицу
break;
}
case('sql_select'):
{// UUUUUUUU считаем время выполнения запроса
$te=explode(' ',$start_time_sql);
$now=explode(' ',microtime());
$times=$now[0]-$te[0]+$now[1]-$te[1];
$times=number_format($times,4,'.',' ');
$count_rec=mysql_num_rows($res);// запоминаем кол-во строк
$_count=$count_rec;
$sq='INSERT `g_sql_select` SET `text_`="'.$sd_sql.'", `link_`="'.$url.'", ';
$sq.='`count_rec`="'.$count_rec.'", `times`="'.$times.'"';
if ($_debug) { $r=mysql_query($sq); }
break;
}
}
return $res;// возвращение результата
}