コメントスパム対策

phpでiptablesを設定するスクリプトを書いて、だいぶ安定してきたようなので。

基本的にはJPNICが逆引き権限を持っているブロックと、APNICがJPにアサインしているブロックについてはFROMJPというChainに追加して許可して、FROMJPをJPFILTERというChainに追加して基本的にドロップするというiptablesコマンドを生成します。ただし、上記ブロックに登録されていないJPなブロックやgooglebotのブロックもあるので、それは適宜調整するという運用になります。要するに日本以外からのアクセスを禁止するということです。

とりあえず/var/log/messagesからドロップされたパケットを抜きだすスクリプトで見る限り、mxやらthやらcnからのお行儀の悪いパケットがドロップされているようなので、よしとするか。

英語でブログを書き始めたら、この対策は出来ないねぇ…。

phpでiptablesを設定するスクリプト

#!/usr/bin/php -q
<?php
/** @file
* @brief 日本以外のIPアドレスからのアクセスを禁止するiptablesスクリプトを作成する
* @author rio fujita
* @date 2005.04.24
* @version 1.0.0
* @note IPv4のみ対応、iptablesが動作している事
*/
//ファイルの場所
define("APNIC_URL", "http://ftp.apnic.net/stats/apnic/");
define("DLGT_LIST", "delegated-apnic-latest");
//define("JPNIC_URL", "http://www.nic.ad.jp/ja/dns/jp-addr-block.html");


//ファイル変更チェック
define("MD5_CACHE", DLGT_LIST."_md5_cache");
define("MD5_LEN", 32+1);


//ファイルのパーサ
define("REC_REG", "/apnic\|JP\|ipv4\|([0-9\.]+)\|([0-9]+)\|[0-9]{8}\|[^\n]+/");


//スクリプトファイル名
define("IPTABLE", "iptables.sh");


//ロギングするかどうか
define("LOGGING", true);


function ipv4_2_cidr($start, $end){
    global $logs;
    return $start."/".$logs[ip2long($end) - ip2long($start) + 1];
}


//CIDRマスクのテーブル作成
for($i = 0; $i < 32; $i++){
    $logs[pow(2, $i)] = 32 - $i;
}


//JPNICが逆引き権限を持つIPアドレス
/*
$list = file(JPNIC_URL, "r");
mb_convert_variables("UTF-8", "JIS", $list);
foreach($list as $line){
    $l = trim($line);
    if(preg_match("/<table summary=\"list\">/", $l)){
        $fl = true;
    } else {
        if($fl){
            if(preg_match("/<td> *([0-9\.]+) *<\/td>/", $l, $c)){
                if($start){
                    $jpnic[] = ipv4_2_cidr($start, $c[1]);
                    unset($start);
                } else {
                    $start = $c[1];
                }
                unset($c);
            }
        }
    }
}
foreach($jpnic as $ip){
    echo($ip."\n");
}
exit();
*/


//許可するIPアドレス群
$allows = array(
    "127.0.0.1/32", //localhost
        
    "10.0.0.0/8", //local IP
    "172.16.0.0/12",
    "192.168.0.0/16",


    "61.112.0.0/12", //JPNIC
    "61.192.0.0/13",
    "61.200.0.0/12",
    "133.0.0.0/8",
    "192.50.0.0/16",
    "192.218.0.0/16",
    "192.244.0.0/16",
    "202.11.0.0/16",
    "202.13.0.0/16",
    "202.15.0.0/15",
    "202.17.0.0/15",
    "202.19.0.0/16",
    "202.23.0.0/14",
    "202.32.0.0/14",
    "202.48.0.0/16",
    "202.208.0.0/11",
    "202.240.0.0/12",
    "203.136.0.0/14",
    "203.140.0.0/15",
    "203.178.0.0/14",
    "203.182.0.0/15",
    "210.128.0.0/11",
    "210.160.0.0/12",
    "210.188.0.0/14",
    "210.196.0.0/14",
    "210.224.0.0/12",
    "210.248.0.0/13",
    "211.0.0.0/13",
    "211.8.0.0/13",
    "211.16.0.0/14",
    "211.120.0.0/12",
    "218.40.0.0/13",
    "218.110.0.0/16",
    "218.216.0.0/12",
    "219.96.0.0/11",
    "219.160.0.0/13",
    "220.96.0.0/14",
    "220.104.0.0/13",
    "220.144.0.0/15",
    "220.208.0.0/12",
    "221.112.0.0/13",


    "43.244.0.0/16", //FreeBit
    "58.0.0.0/8", //USEN ?
    "58.70.0.0/16", //K-opticom
    "66.187.224.0/20", //RedHat
    "66.249.64.0/19", //googlebot
    "66.196.64.0/18", //inktomi search
    "68.142.192.0/18", //inktomi search
    "202.165.96.0/20", //inktomi search
    "207.46.0.0/16", //msn bot
);


//ファイルを取得してMD5を生成
if(!$list = file(APNIC_URL.DLGT_LIST, "r")){
    exit();
}
$list_imploded = trim(implode("", $list));
$md5_current = md5($list_imploded);


//キャッシュされたMD5を読む
if(file_exists(MD5_CACHE)){
    if($fp = fopen(MD5_CACHE, "r")){
        $md5_cached = fgets($fp, MD5_LEN);
        fclose($fp);
    }
}


//違っていたらキャッシュに書き出して処理続行
if(!array_key_exists("f", getopt("f"))){
    if($md5_current == $md5_cached){
        exit("cached\n");
    }
}
if($fp = fopen(MD5_CACHE, "w")){
    fwrite($fp, $md5_current);
    fclose($fp);
}


$sh_buf .= "iptables -F\n"; //フラッシュする
//chainを削除
$sh_buf .= "iptables -X JPFILTER\n";
$sh_buf .= "iptables -X FROMJP\n";
//chainを作る
$sh_buf .= "iptables -N JPFILTER\n";
$sh_buf .= "iptables -N FROMJP\n";


//アクセスを許可するIP群
foreach($allows as $al){
    $sh_buf .= "iptables -A JPFILTER -s ".$al." -j FROMJP\n";
}


//リストのパース
foreach($list as $line){
    if(preg_match(REC_REG, $line, $c)){
        //許可するリストに追加
        $range = $c[1]."/".$logs[(int)$c[2]];
        $sh_buf .= "iptables -A JPFILTER -s ".$range." -j FROMJP\n";
    }
}


$sh_buf .= "iptables -A FROMJP -j ACCEPT\n"; //日本国内からのアクセス許可
if(LOGGING){
    $sh_buf .= "iptables -A JPFILTER -j LOG --log-prefix \"Rej-TCP : \"\n"; //それ以外はログとって...
}
$sh_buf .= "iptables -A JPFILTER -j DROP\n"; //落とす
$sh_buf .= "iptables -A INPUT -p tcp -m state --state NEW -j JPFILTER\n"; //INPUT chainにJPFILTERを追加

//ファイルを作成
if($fp = fopen(IPTABLE, "w")){
    fwrite($fp, "# this file was made by make_iptables.php\n\n");
    fwrite($fp, $sh_buf);
    fclose($fp);
    //実行
    exec("sh ".IPTABLE);
}
?>

/var/log/messagesからドロップされたパケットを抜きだすスクリプト

#!/usr/bin/php -q
<?php
    exec("grep Rej-TCP /var/log/messages | cut -d ' ' -f 11 | sort -u | cut -d '=' -f 2", $file);
    foreach($file as $line){
        $ip = trim($line);
        $names[$ip] = gethostbyaddr($ip);
    }
    ksort($names);
    foreach($names as $ip=>$name){
        echo($ip.str_repeat(" ", 15-strlen($ip)+1).": ".$name."\n");
    }
?>