Как определить, что сайт подвергся SQL инъекции

На главном сервисе моей основной работы произошел сбой. Надо сказать, весьма критичный для нашей компании.

Разбор логов показал, что была проведена SQL инъекция вредоносного эксплойта в один из SQL запросов на сайте.

Дело осложнялось тем, что до меня сайтом заведовали люди, которые собственно на нем и учились программировать. Море кода, на который мой взгляд даже не падал. И где-то в нем, была дырка.

О том, как я решил эту проблему и пойдет речь в данной статье.

Что такое SQL инъекция
Возьмем некий псевдо код

if( intval($_GET['id']) )
    mysql_query('select * from news where id="'.$_GET['id'].'"';

Здесь есть проверка на то, что intval должен вернуть целое число, и если в id лежит не число, то условие не сработает и запрос не будет исполнен.

И это верно работает, если сделать такой запрос

http://sitename.ru/index.php?id=new
то условие не выполнится: intval('new') вернет 0.

Кажется, что этого достаточно. Но вопрос, что произойдет при следующем запросе

http://sitename.ru/index.php?id=39new
Удивительно, но условие сработает. intval в этом плане, очень крутая функция. В отличии к примеру от JavaScript'овского parseInt, который вернет в таком случае NaN. Она спокойно переварит такие входные данные, и возьмет из них только число (заметим, что intval('new39') вернет 0.)

Отсюда вытекает дыра в безопасности. Через нее, можно вставить любой SQL запрос. Вплоть до delete from news

Это очень распространенная ошибка в web'деве. Подвержены ей не только проекты начинающих программистов. Если Вы используете CMS с открытым исходным кодом, то такая вероятность тоже есть. Ведь злоумышленник легко может изучить исходники этой системы или воспользоваться опубликованной другими хакерами информацией и найти ее слабое место.

Говорят, таким атакам подвержены даже смартфоны и подобные девайсы. Большинство из них, в своей операционной системе данные хранят в SQLLite, а следовательно и сопутствующие неприятности связанные с SQL инъекциями возникнуть могут. Вероятно какой-нибудь samsung galaxy s iv этому не подвержен, но не стоит забывать, что старые устройства никуда не уходят, и подвержены риску.

Методы борьбы с SQL инъекциями
Самым верным способом противодействия данного рода атакам - это экранирование всего и вся. Т.е. все переменные, которые вставляются в код пропускаются через функцию mysql_real_escape_string

mysql_query('select * from news where id="'.mysql_real_escape_string($_GET['id'])).'"';

такой запрос ни чем серьезным, кроме как ошибки в запросе, не грозит. В данном конкретном примере, мы видим, что id может быть исключительно целым числом, поэтому еще более верным решением будет такой код

mysql_query('select * from news where id="'.intval($_GET['id']).'"';

Я не дублирую код для проверки, но его тоже ни в коме случае опускать не стоит. Проще проверить переменную, чем заставлять mysql сервер выполнять лишний запрос.

Рано или поздно такой метод вставки войдет у Вас в привычку. В моем сервисе, как раз такая ошибка и была сделана. В код, просто шла переменная из get запроса.

Со своим кодом разобрались. Тут мы хозяева положения. В более менее крупном проекте, используется больше количество сторонних библиотек или приложений: ckeditor, ckfinder, jquery плагины, сборщики, упаковщики, даунлоудеры, системы статистики, phpmyadmin и т.д.

И у них тоже есть дырки. Править их руками возможности разумеется нет, а если и есть то так делать нежелательно.

Гораздо проще, защитить ресурс в целом. К примеру, в моей любимой CMS Danneo, это реализовано на уровне API и в GET и POST запрос, просто нельзя вставлять ключевые слова для SQL запросов.

/**
 * badcount
 */
$badcount = 0;
/**
 * badops
 */
$badops = array("UNION",
                "OUTFILE",
                "FROM",
                "CREATE",
                "SELECT",
                "WHERE",
                "SHUTDOWN",
                "UPDATE",
                "DELETE",
                "CHANGE",
                "MODIFY",
                "RENAME",
                "RELOAD",
                "ALTER",
                "GRANT",
                "DROP",
                "INSERT",
                "CONCAT",
                "cmd",
                "exec",
                "\([^>]*\"?[^)]*\)",
                "<[^>]*body*\"?[^>]*>",
                "<[^>]*script*\"?[^>]*>",
                "<[^>]*object*\"?[^>]*>",
                "<[^>]*iframe*\"?[^>]*>",
                "<[^>]*img*\"?[^>]*>",
                "<[^>]*frame*\"?[^>]*>",
                "<[^>]*applet*\"?[^>]*>",
                "<[^>]*meta*\"?[^>]*>",
                "<[^>]*style*\"?[^>]*>",
                "<[^>]*form*\"?[^>]*>",
                "<[^>]*div*\"?[^>]*>"
                );
/**
 * foreach REQUEST
 */
foreach ($_REQUEST as $params => $inputdata) {
    for ($i = 0; $i < sizeof($badops); $i++) {
            if (is_string($inputdata) && preg_match('/'.$badops[$i].'/i',$inputdata)) {
                 $badcount = 1;
            }
    }
}
 
if( $badcount  ){
     header('HTTP/1.1 500 Internal Server Error');
     throw new Exception('Divizion by zerro');
    exit();
}

Вставляем этот код в начало входного скрипта вашей системы и от 99% атак вы защищены. Конечно, такой код налагает определенные ограничения на названия переменных, и если Ваша система очень большая, то вероятно половину проверок вы в нем закомментируете.

Помимо прочего, этот код защищает Вас от других атак, css и т.п.

В результате, если в запросе были запрещенные слова, код вернет ошибку 500 и выкинет исключение. Это не самое лучшее решение. Если весь Ваш код эскейпит переменные, и с этим все в порядке, то будет не лишним оповестить админа о попытках инъектирования, а самому злоумышленнику отдавать к примеру 404.

К примеру, в моем классе надстройке на mysql, я просто дополнил метод escape и если в него попадает данные, которые содержат запрещенные слова, отправляется письмо на ящик администратора сайта, ему присылается ip пользователя, переменная на которую сработала защита.

....
function myIP(){
  $ipa = explode( ',',@$_SERVER['HTTP_X_FORWARDED_FOR'] );
  $ip = isset($_SERVER['HTTP_X_REAL_IP'])?$_SERVER['HTTP_X_REAL_IP']:(isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:(!empty($ipa[0])?$ipa[0]:'127.0.0.1'));
  return preg_match('#^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$#',$ip)?$ip:'127.0.0.1';
}
 
public $checkAttack = true;
public $attackBuffer = array();
public $stopAttack = false;
 
function isItAttack( $id ){
  if( $this->checkAttack and !in_array($id,$this->attackBuffer) ){
    $hook = 0 ;
    if(
      (preg_match('/[\']/', $id) and $hook=1) or
      (preg_match('/(and|null|not)/i', $id) and $hook=3) or
      (preg_match('/(union|select|from|where)/i', $id) and $hook=4)or
      (preg_match('/(group|order|having|limit)/i', $id) and $hook=5) or
      (preg_match('/(into|file|case)/i', $id) and $hook=6) or
      (preg_match('/--|#|\/\*/', $id) and $hook=7)
    ){
      $this->attackBuffer[] = $id;
      mail('mail@ya.ru','Attack rutaxi.ru',"Site was be attacked - \n\n".
        "value:".$id."\n\n".
        "hook:".$hook."\n\n".
        "ip:".$this->myIP()."\n\n".
        "request:".$_SERVER['REQUEST_URI']."\n\n".
        "REQUEST:".var_export($_REQUEST,true));
      if( $this->stopAttack ){
        header('HTTP/1.1 500 Internal Server Error');
        throw new Exception('Divizion by zerro');
        exit();
      }
      return true;
    }
  }
  return false;
}
function escape($value){
  $this->isItAttack($value);
  return mysql_real_escape_string($value,$this->handle);
}
...

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

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

Желаю Вам удачи, в нашей опасной профессии)
Теги:
sql, sql инъекция, injection, атака, защита сайта, защита от sql инъекций, защита
Добавлено: 20 Апреля 2018 20:29:22 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...