Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Авторские статьи (https://forum.antichat.xyz/forumdisplay.php?f=31)
-   -   "Настоящий жлоб или не дадим врагу контента" (https://forum.antichat.xyz/showthread.php?t=36793)

VDShark 31.03.2007 01:40

"Настоящий жлоб или не дадим врагу контента"
 
"Настоящий жлоб или не дадим врагу контента" || "Защита от парсинга (php+MySQL)"


В последнее время конкуренты взяли моду парсить контент друг у дуга. В этой статье я собираюсь рассказать о том, как можно попробовать этого избежать.
Немного определений:
  • В информатике, синтаксический анализ (парсинг) — это процесс анализа входной последовательности символов, с целью разбора грамматической структуры, обычно в соответствии с заданной формальной грамматикой.
Для парсинга пишутся специальные программы, называемые парсерами.
  • Синтаксический анализатор (парсер) — это программа или часть программы, выполняющая синтаксический анализ.
В php, впрочем как и во многих других языках, парсинг основан на регулярных выражениях. Исследуется структура документа, находятся какие либо закономерности, и создается так называемый «паттерн» - маска, по которой производится поиск.
И если с парсингом текста бороться практически бесполезно, то файловое наполнение проекта можно попытаться защитить от парсера, запущенного с какого либо сервера.
Как это выглядит в теории
Есть таблица в БД, где хранится информация о так называемых «тикетах» (ticket - билет).
Есть страница, где располагается информация о файле.
Есть скрипт скачки.
Нормальный пользователь, в отличие от парсера, находится на странице с информацией о файле. Здесь то и создается тикет. При нажатии на ссылку «скачать», пользователь попадает на скрипт скачки файла, где проверяется валидность тикета и пользователя. Если все нормально, то файл запускается на скачку, иначе «пользователь» посылается восвояси.
Ну что же, в теории все выглядит довольно легко – посмотрим как это реализовать на практике.
Как это выглядит на практике
Рассмотрим простейший пример.
Создадим такую таблицу:
Код:

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 за конструктивную критику относительно данной статьи.

_Great_ 31.03.2007 09:15

Цитата:

Пользователь заходит на сайт, ходит по страницам…. Его заинтересовал какой то файл. Он заходит на страницу с информацией о файле, и там происходит создание тикета:
ОМФГ, одна ддос атака рушит твой MySQL наповал. Создавать целую строчку в таблице при каждом открытии страницы... мда

fucker"ok 31.03.2007 09:43

Не обязательно ведь в sql. Можно использовать сессию (да и нужно).
Статья хорошая (для первой). Хотя это только немного затруднит разработку парсера. При желании все-равно отпарсят :)
Так же ссылку на файл можно генерировать какими-то алгоритмами, в основу которых положить время, ид файла и другую хрень :)
++


Время: 10:43