"Настоящий жлоб или не дадим врагу контента" || "Защита от парсинга (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 – статус тикета (активен/не активен); Разберемся пошагово, что и как происходит: Пользователь заходит на сайт, ходит по страницам…. Его заинтересовал какой то файл. Он заходит на страницу с информацией о файле, и там происходит создание тикета: 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); } ... ?> Если он (пользователь) захотел скачать файл, то при нажатии на ссылку он попадает на скрипт скачки, где происходят проверка тикета, откуда он пришел и т.д.: 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 за конструктивную критику относительно данной статьи.
ОМФГ, одна ддос атака рушит твой MySQL наповал. Создавать целую строчку в таблице при каждом открытии страницы... мда
Не обязательно ведь в sql. Можно использовать сессию (да и нужно). Статья хорошая (для первой). Хотя это только немного затруднит разработку парсера. При желании все-равно отпарсят Так же ссылку на файл можно генерировать какими-то алгоритмами, в основу которых положить время, ид файла и другую хрень ++