Pull to refresh

Тематические Медиа: задача для собеседования

Reading time3 min
Views2.3K
В свете того, что в последнее время похожая тема довольно часто стала появляться на страницах проекта, опубликую задание, которое на протяжении значительного времени мы предлагали соискателям на позицию php-разработчика в нашей компании.



Итак, вот что требуется: есть журнал обращений к веб-серверу, не важно апач это или nginx (лучше предусмотреть оба варианта), известно, что во время регистрации этого журнала сервер находился под ddos атакой. Требуется написать скрипт, который максимально быстро и по возможности точно определит адреса атакующих узлов для их блокировки.

Подразумевается, что гугл, заблокированный на период бедствия, не является большой бедой. Нетрудно взять список поисковых систем и внести соответствующие поправки.

Разумеется, точность и скорость определения, основанная на заданных параметрах может меняться в зависимости от силы атаки (периодичности, количеству участвующих узлов).

Прилагаю решение, которое сам написал уже после появления задачи, в тот момент, когда особенно «приспичило». Решение довольно простое и грубое, но при этом эффективное (80 мегебайт журнала за 20 секунд). Именно этот скрипт помог защитить один из проектов от одной из атак, когда на сервер ломилось 1500 бездельников.

Со временем стало понятно, что при атаке правильнее оценивать сам трафик (более досканальный и трудозатратный подход, кто чаще с этим сталкивался — так и поступает): содержимое пакетов, заголовки, но, тем не менее, опыт показал, что решение этой задачи однозначно даёт представление о уровне специалиста, утверждающего, что он опытный разработчик и «собаку съел» в области web-программирования.

На решение давалось от часа, до двух времени (в зависимости от хода мысли человека), последний раз она была решена за 1 час 20 минут. Всего решили задачу примерно 10% соискателей.

Теперь у нас будет другая задача.

Исходный код: может читать журнал потоково или из файла, при необходимости добавляет блокирующие правила в брандмауэр. Отказ от регулярных выражений даёт существенный прирост в производительности. Испытывался на файлах размером до 500 мегабайт.

Конечно, всё это можно было написать немного короче и красивее на перле, но хотелось составить некий образец решения задачи именно на php.

#!/usr/local/bin/php
<?php
  if(!empty($argv[1])) $fname = $argv[1]; else $fname = 'access.log';
  $fh = fopen($fname, 'r');
  #$fh = fopen('php://stdin', 'r');
  $timeLimit = 1;
  $countLimit = 50;
  $status = array();

  while ($string = fgets($fh)) {
       $ip = substr($string, 0, strpos($string, ' '));

       if(!empty($status[$ip]['blocked'])) continue;

       $st = strpos($string, '[') + 1;
       $time = strtotime(substr($string, $st, strpos($string, ']', $st + 1) - $st));

       $st = strpos($string, '"') + 1;
       $req = substr($string, $st, strpos($string, '"', $st + 1) - $st);
       $st = strpos($req, " ") + 1;
       $doc = substr($req, $st, strpos($req, " ", $st) - $st);

       $dot = strrpos($doc, ".");
       $dot = $dot ? strlen($doc) - $dot : 0;
       if(!$dot || $dot > 5) {
          if(!empty($status[$ip])) $status[$ip] = array('count'=>0);
          if(!empty($status[$ip]['time']) && $time - $status[$ip]['time'] <= $timeLimit) {
              $status[$ip]['count'] ++;
              if($status[$ip]['count'] >= $countLimit) {
                  echo "$ip : $doc\n";
		  #system ("ipfw table 1 add $ip");
		  #echo "$ip\n";
                  $status[$ip]['blocked'] = 1;
              }
          }

          $status[$ip]['time'] = $time;
          $status[$ip]['doc'] = $doc;
       }
  }
?>
Tags:
Hubs:
Total votes 49: ↑36 and ↓13+23
Comments144

Articles