PDA

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


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 – статус тикета (активен/не активен);


Разберемся пошагово, что и как происходит:

Пользователь заходит на сайт, ходит по страницам…. Его заинтересовал какой то файл. Он заходит на страницу с информацией о файле, и там происходит создание тикета:

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