従来のHTTP DDoSから保護する簡単な方法

このソリューションでは、ブラウザの操作を完全に模倣するボットを除き、ボットを計算できます。



仕組み



ボットは、 habrahabr.ru / searchなどのページを要求します。 ボットは、画像、スクリプト、CSSなどをページと一緒に読み込む方法を知りません。つまり、/検索/へのリクエストがログに表示され、それだけです。

生きている人がブラウザーを介してhabrahabr.ru/searchを入力すると、/ search /多くの写真、スクリプト、CSSなどがログに記録されます。



カスタマイズ



Mysql



/etc/my.cnf

[mysqld] local-infile=1 # load data #    : max_heap_table_size=1024M tmp_table_size=1024M
      
      





ルートの下:

 UPDATE `mysql`.`user` SET `File_priv` = 'Y' WHERE `user`.`Host` = 'localhost' AND `user`.`User` = '__'; flush privileges;
      
      





sysctl



コメント付きのsysctl.confの詳細(Linux)



ラムドライブ



Ramドライブは 、nginxログの処理を高速化するために必要です。

ファイルに追加/ etc / fstab

 tmpfs /var/log/ram_disk tmpfs size=1024m 0 0
      
      





それから

 mkdir /var/log/ram_disk mount -t tmpfs -o size=1024m tmpfs /var/log/ram_disk
      
      







アルゴリズム





1.トラップ選択



このサイトでは、 habrahabr.ru / styles / fontello / css / habr.cssなど、スピーカーのページを呼び出すときに読み込まれる静的で目立たないファイル(画像、CSS、 JSなど)を取得します。

このファイルはキャッシュ不可にする必要があります。 <?php echo '/styles/fontello/css/habr.css?'。rand(99999999)?>のようなランダムパラメーターを追加します。

参考のため、デフォルトでは、オペラはローカルキャッシュに1時間、css / jsを5分間保存します。



2. nginxの設定を修正します



 #      log_format ddos_log '$remote_addr\t$msec\t$status'; #   location =/styles/1347283218/highlight.css { access_log /var/log/ram_disk/hook_access.log ddos_log; } #    location ~* ^.+\.(class|htc|bmp|cur|jpg|jpeg|gif|png|svg|xls|doc|xhtml|js|css|mp3|ogg|mpe?g|avi|flv|zip|gz|bz2?|rar|ico|txt|jar|swf)$ { access_log off; } #  location / { access_log /var/log/ram_disk/dynamic_access.log ddos_log; }
      
      







3.ログ用のテーブルを作成する



ENGINE = MEMORY-高速化するため。

 CREATE TABLE `dinamic_log` ( `inc` bigint(20) NOT NULL AUTO_INCREMENT, `remote_addr` varchar(20) NOT NULL DEFAULT '0', `time_local` int(20) NOT NULL DEFAULT '0', `status` int(4) NOT NULL DEFAULT '0', PRIMARY KEY (`inc`), KEY `remote_addr` (`remote_addr`), KEY `time_local` (`time_local`) ) ENGINE=MEMORY AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
      
      





 CREATE TABLE `hook_log` ( `inc` bigint(20) NOT NULL AUTO_INCREMENT, `remote_addr` varchar(20) NOT NULL DEFAULT '0', `time_local` int(20) NOT NULL DEFAULT '0', `status` int(4) NOT NULL DEFAULT '0', PRIMARY KEY (`inc`), KEY `remote_addr` (`remote_addr`), KEY `time_local` (`time_local`) ) ENGINE=MEMORY AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
      
      







検索ボットのIPを配置するテーブル

 CREATE TABLE `white` ( `remote_addr` bigint(20) NOT NULL, PRIMARY KEY (`remote_addr`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1
      
      





ロックされたテーブル

 CREATE TABLE `black` ( `remote_addr` bigint(20) NOT NULL, `time_local` int(20) NOT NULL DEFAULT '0', PRIMARY KEY (`remote_addr`), KEY `time_local` (`time_local`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1
      
      







4.メインスクリプト



理解を容易にするために、phpで記述されています。 ほとんどの人がこの言語を知っています。 また、理解しやすいようにエラー処理も削除されます。



 //    $dinamic_log = $argv[1]; //    $hook_log = $argv[2]; // -     ,    . $r_stop = $argv[3]; //      (   ) $load_time = $argv[4]; //         $wait_sec = $argv[5]; function load_log($log, $table) { $tmp = '/var/log/ram_disk/tmp_ddos_file'; //      copy ($log, $tmp); //   file_put_contents($log, "", LOCK_EX); //     mysql_query('LOAD DATA CONCURRENT INFILE "'.$tmp.'" IGNORE INTO TABLE '.$table.' FIELDS TERMINATED BY \'\t\' (`remote_addr`, `time_local`, `status`) SET `remote_addr` = INET_ATON(`remote_addr`)'); //    unlink($tmp); } //     while (true) { //    load_log($dinamic_log, 'dinamic_log'); //    load_log($hook_log, 'hook_log'); //  . nginx    $status,  200  304  . $res = mysql_query('SELECT dinamic_log.remote_addr FROM `dinamic_log` WHERE (`status` = 200 OR `status` = 304) AND`remote_addr` NOT IN (SELECT `remote_addr` FROM `hook_log`) AND`remote_addr` NOT IN (SELECT `remote_addr` FROM `white`) GROUP BY `remote_addr` HAVING count(inc)>'.$r_stop); while ($row = mysql_fetch_array($res)) { //   ip mysql_query('INSERT INTO black(`remote_addr`) VALUES ('.$row['remote_addr'].')'); //  ip switch (PHP_OS) { case "FreeBSD": system('/sbin/route add -host '.$row['remote_addr'].' 127.0.0.1 -blackhole'); break; case "Linux": system('/sbin/ip route add blackhole '.long2ip($row['remote_addr'])); break; } } //      mysql_query('DELETE FROM `log` WHERE `time_local` < (UNIX_TIMESTAMP() - '.$load_time.')'); //  sleep($wait_sec); }
      
      





以下を開始します。

 php ddoshook.php /var/log/ram_disk/dynamic_access.log /var/log/ram_disk/hook_access.log 5 300 3
      
      







5.解禁



 $block_time = $argv[1]; //     ip. $res = mysql_query('SELECT `remote_addr` FROM black WHERE time_local < (UNIX_TIMESTAMP() - '.$block_time.')'); while ($row = mysql_fetch_array($res)) { //  ip switch (PHP_OS) { case "FreeBSD": system('/sbin/route delete '.$row['remote_addr']); break; case "Linux": system('/sbin/ip route delete '.long2ip($row['remote_addr'])); break; } }
      
      





王冠に置く

 * * * * * /usr/bin/php unban.php 86400
      
      







それだけです、ボットは禁止され、人々はスキップされます。



次の問題:





更新した

検索ボットのIPを見つける方法は?



  1. 希望する検索エンジンとしてASを探しています。例: bgp.potaroo.net/cidr/autnums.html
  2. ASのIPアドレスの取得: stat.ripe.net/data/announced-prefixes/data.json?resource=AS15169


ASおよびIPリストは常に更新する必要があります。



upd 2



OS:FreeBSD 8.3

CPU:E5-2620 2.00GHz



テスト1



行(dinamic_log):100,000(3秒でサイトのダイナミクスに対する100,000のHTTP要求)

行(hook_log):1000(3秒で1000件のユーザーからの正当なリクエスト)



#php /root/scripts/php/imgtest/ddos_hook.php /tmp/d20.log /tmp/h20.log 5 300 3

LOAD DATAの経過時間:0.29秒。

LOAD DATAの経過時間:0.003秒。

経過時間の選択:0.017秒。

行(禁止):1800

経過時間:0.313秒。



テスト2



行(dinamic_log):1,000,000(3秒で100万のサイトダイナミクスへのHTTP要求)

行(hook_log):10,000(3秒で10,000件のユーザーからの正当なリクエスト)



#php /root/scripts/php/imgtest/ddos_hook.php /tmp/d2.log /tmp/h2.log 5 300 3

LOAD DATAの経過時間:2.878秒

LOAD DATAの経過時間:0.023秒。

経過時間の選択:0.501秒。

行(禁止):12402

経過時間:3.54秒。



PS

この記事に記載されている解決策は実験的なものです。 ご自身の責任で使用してください



All Articles