Авторские статьи "Настоящий жлоб или не дадим врагу контента"

Discussion in 'Статьи' started by VDShark, 31 Mar 2007.

  1. VDShark

    VDShark Elder - Старейшина

    Joined:
    1 Feb 2007
    Messages:
    260
    Likes Received:
    158
    Reputations:
    62
    "Настоящий жлоб или не дадим врагу контента" || "Защита от парсинга (php+MySQL)"​



    В последнее время конкуренты взяли моду парсить контент друг у дуга. В этой статье я собираюсь рассказать о том, как можно попробовать этого избежать. ​
    Немного определений:
    • В информатике, синтаксический анализ (парсинг) — это процесс анализа входной последовательности символов, с целью разбора грамматической структуры, обычно в соответствии с заданной формальной грамматикой.
    Для парсинга пишутся специальные программы, называемые парсерами.
    • Синтаксический анализатор (парсер) — это программа или часть программы, выполняющая синтаксический анализ.
    В php, впрочем как и во многих других языках, парсинг основан на регулярных выражениях. Исследуется структура документа, находятся какие либо закономерности, и создается так называемый «паттерн» - маска, по которой производится поиск.​
    И если с парсингом текста бороться практически бесполезно, то файловое наполнение проекта можно попытаться защитить от парсера, запущенного с какого либо сервера.​
    Как это выглядит в теории
    Есть таблица в БД, где хранится информация о так называемых «тикетах» (ticket - билет).​
    Есть страница, где располагается информация о файле.​
    Есть скрипт скачки.​
    Нормальный пользователь, в отличие от парсера, находится на странице с информацией о файле. Здесь то и создается тикет. При нажатии на ссылку «скачать», пользователь попадает на скрипт скачки файла, где проверяется валидность тикета и пользователя. Если все нормально, то файл запускается на скачку, иначе «пользователь» посылается восвояси.​
    Ну что же, в теории все выглядит довольно легко – посмотрим как это реализовать на практике.​
    Как это выглядит на практике
    Рассмотрим простейший пример. ​
    Создадим такую таблицу:
    Code:
    CREATE TABLE download_tickets (
      id_ticket int(10) unsigned NOT NULL auto_increment,
      path char(250) default NULL,
      add_date int(10) unsigned default NULL,
      ip char(15) default NULL,
      status int(1) unsigned NOT NULL default '0',
      PRIMARY KEY  (id_ticket)
    ) ENGINE=MyISAM DEFAULT CHARSET=cp1251;
    
    Вот как это выглядит графически:​


    -------------------------------------------
    |id_ticket | path | add_date | ip | status|
    -------------------------------------------
    |......... |..... | ........ | .. | ..... |
    -------------------------------------------
    Конечно это не оптимальный вариант, но для понимания – самое то что нужно.​
    Поясним, какое поле за что отвечает:
    • id_ticket – уникальный идентификатор тикета;
    • path – путь до файла;
    • add_date – дата и время добавления тикета;
    • ip – ip-адрес добавившего тикет;
    • status – статус тикета (активен/не активен);
    Разберемся пошагово, что и как происходит:
    1. Пользователь заходит на сайт, ходит по страницам…. Его заинтересовал какой то файл. Он заходит на страницу с информацией о файле, и там происходит создание тикета:

      PHP:
      <?php
      ... 
      $data['path']=$path;//задаем путь к файлу
      $data['add_date']=time();//получаем время
      $ip=$_SERVER['HTTP_X_REAL_IP'];//получаем IP
      if ($ip=='' || $ip=='127.0.0.1'$ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
      if (
      $ip=='' || $ip=='127.0.0.1'$ip=$_SERVER['REMOTE_ADDR'];        
      $data['ip']=$ip;
      $data['status']=0;//статус по умолчанию - 0 (т.е. не активен)
      $sql="INSERT INTO `download_tickets` SET `path`='".$data['path']."', `add_date`='".$data['add_date']."', `ip`='".mysql_escape_string($data['ip'])."', `status`='".$data['status']."'";
      mysql_query($sql);//создаем запись о тикете в базе
      $sql="SELECT MAX(`id_ticket`) FROM `download_tickets`";
      $result=mysql_query($sql);
      $id_ticket=mysql_result($result,0,0);//получаем ID тикета
      $_SESSION['download_ticket']=$id_ticket;//выставляем ID тикета в сессиях
      /*здесь мы создали тикет, но он еще не активирован. Если скачивание должно оплачиваться, то следовательно нужно производить активацию по факту оплаты, если же нет - то активируем его сразу.*/
      $sql="SELECT `status` FROM `download_tickets` WHERE `id_ticket`='".$id_ticket."'";
      $result=mysql_query($sql);
      $status=mysql_result($result,0,0);//получаем статус тикета
      if (!$status) {
          
      $sql="UPDATE `download_tickets` SET `status`='1' WHERE `id_ticket`='".$id_ticket."'";//изменяем статус тикета на "активен"
          
      mysql_query($sql);
      }
      ...
      ?>
    2. Если он (пользователь) захотел скачать файл, то при нажатии на ссылку он попадает на скрипт скачки, где происходят проверка тикета, откуда он пришел и т.д.:
      PHP:
      <?php
      define
      (_DOWNLOAD_TICKETS_LIFETIME_,"время_жизни_тикета_в_секундах");
      ...
      $id_ticket=$_SESSION['download_ticket'];//получаем ID тикета
      $sql="SELECT * FROM `download_tickets` WHERE `id_ticket`='".$id_ticket."'";
      $result=mysql_query($sql);
      $ticket=mysql_fetch_array($result,MYSQL_ASSOC);//получаем сам тикет
      $ip=$_SERVER['HTTP_X_REAL_IP'];//получаем IP
      if ($ip=='' || $ip=='127.0.0.1'$ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
      if (
      $ip=='' || $ip=='127.0.0.1'$ip=$_SERVER['REMOTE_ADDR'];
      //в этой ветке проверяется валидность тикета и настоящий ли пользователь, или нет (тикет - по статусу и времени жизни, пользователь - по ip и странице откуда пришел)
      if ($ticket['status'] and $ticket['ip']==$ip and time() <= ($ticket['add_date']+_DOWNLOAD_TICKETS_LIFETIME_) and urldecode($_SERVER["HTTP_REFERER"])=="путь_до_страницы_откуда_должна_запускаться_скачка"){
          
      $file=file_get_contents($ticket['path']);
          
      $file_name=basename($ticket['path']);
          
      header("Content-Type: application/x-download");
          
      header("Content-Disposition: attachment; filename=$file_name");
          echo 
      $file;//осуществляется отдача файла пользователю
          
      $sql="DELETE FROM `download_tickets` WHERE `id_ticket`='".$id_ticket."' LIMIT 1";
          
      mysql_query($sql);//после отдачи файла - удаляем тикет
      }
      else {
          
      $sql="DELETE FROM `download_tickets` WHERE `id_ticket`='".$id_ticket."' LIMIT 1";
          
      mysql_query($sql);//если найдены несоответствия в данных о пользователе или тикет не валиден - удаляем тикет
          
      header('Location: /index.php');//перенаправляем пользователя на начальную страницу (можно задать какую нибудь другую, поправив /index.php на нужный путь)
      }
      ...
      ?>
    Таким образом, реализуется довольно таки простая, и в то же время эффективная защита от автоматической скачки файлов с сайта. Конечно это далеко не самый оптимальный вариант – но я при написании данной статьи ставил своей целью показать то, как это делается. Основываясь на данном примере можно самому написать хорошую систему, наращивая функциональность. Например добавлять id тикета не только в сессии, но и в кукисы – дополнительная мера безопасности так сказать. И вообще – здесь очень большой простор для полета фантазии и придумывания новых критериев проверки, так что дерзай!​
    P.S. Это моя первая статья, так что сильно уж не пинайте).​
    P.P.S Благодарю Helios’a за конструктивную критику относительно данной статьи.​
     
    #1 VDShark, 31 Mar 2007
    Last edited: 31 Mar 2007
    7 people like this.
  2. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    ОМФГ, одна ддос атака рушит твой MySQL наповал. Создавать целую строчку в таблице при каждом открытии страницы... мда
     
  3. fucker"ok

    fucker"ok Elder - Старейшина

    Joined:
    21 Nov 2004
    Messages:
    580
    Likes Received:
    279
    Reputations:
    91
    Не обязательно ведь в sql. Можно использовать сессию (да и нужно).
    Статья хорошая (для первой). Хотя это только немного затруднит разработку парсера. При желании все-равно отпарсят :)
    Так же ссылку на файл можно генерировать какими-то алгоритмами, в основу которых положить время, ид файла и другую хрень :)
    ++