Apache IP denial configuration

Apache のアクセスログを時折チェックすると,様々なアタックがあることに気付く。公開 Web サーバを運用しているとこれはもうしようがない。GET //dbadmin/scripts/setup.php HTTP/1.1 などの要求(世に言う php アタック)が頻発していて,すべて 404 レスポンス(document not found)で弾かれている。404 で帰ってくれるならよいのだが,隠れたセキュリティホールを突かれてサーバが乗っ取られないとも限らない。面倒だが,これら前科のあるヤクザ・ホストの IP アドレスを収拾し,こいつらのネットワークから来る HTTP 要求はすべて拒否することにした。

Apache には特定の IP アドレス,ネットワークからの要求を拒絶する機能が備わっている。あるドキュメントに対して deny from IP | CIDR を指定すればよい。ヤクザ・ホストの IP アドレスをログから抽出しここに指定しておけば,Apache は 403 レスポンス(forbidden)を返し,アクセスを拒絶してくれる。今回は IP アドレスそのものではなく,こいつらヤクザが属しているネットワーク全体からの要求を拒絶するように CIDR(ネットワークアドレス/マスクビット)を指定することにした。暴力団排除条例が施行されたことだし,「関係者」ともども皆お断りというわけだ。今日は,その設定変更レシピの備忘録を示しておく。ここで % は UNIX コマンドライン tcsh プロンプトを示している。

  1. 存在しないドキュメント参照記録の抽出
  2. まず 404 の履歴を Apache のアクセスログから抽出する。user, webserver は,それぞれ Web サーバへのログイン ID,ホスト名である。リモートシェルを使って Mac OS からオペレーションしている。
    % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log > 404list.txt
  3. アタックの特定
  4. 404list.txt をテキストエディタで開いて内容を確認し,明らかにアタックだと思われるアクセス(.cgi, .dll, .php の拡張子をもつ,いかがわしいプログラムを実行するもの)を特定し,それ以外の行を削除して格納する。

  5. IP アドレス・リストの作成
  6. 編集済のログデータ 404list.txt から IP アドレスだけを抽出・編集し,重複を排除し,対象リスト 404ip.txt を作成する。
    % cat 404list.txt | cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
    私の場合,上記 1〜3 のオペレーションは以下のワンライナーでよさそうであった(→印は,次の行と一続きで入力することを示す)。
    % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log  | \
    grep -v 'GET .*\.html ' | grep -e 'bbs\.html\+\|\/manager\|\.php→
    \|\.dll\|\.asp\|\.pl\|GET http\|\/user\/\|\/muieblackcat→
    \|blackhats\|++++++*\|GET \/\/\|GET \/crossdomain\|→
    GET \/idle\/\|GET \/appConf' | \
    cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
  7. Whois 情報から Apache denial list の作成
  8. 404ip.txt 中の IP アドレスで whois を参照し,このアドレスが属するネットワーク CIDR や国コードを取得する。国コードはなくてもよいが,いったいどの国にヤクザが多いのか興味はありませんか? この処理は UNIX の whois コマンドを IP アドレス毎にひとつひとつ叩いて CIDR を調べ,Apache の定義文を作成すればよいけれども,IP の数が多いとやってられない。ネットワークの IP アドレス範囲から CIDR を計算しなければならない場合もあり,面倒だ。そこで私は,これらを全部自動的に行う Perl コード ipwhois.pl を書いた。ipwhois.pl コードを記事の最後に掲げておく。このプログラムは IP アドレス・リストを標準入力から読みとって,IP アドレス毎に whois を照会し,当該 IP の国コードと CIDR を取得あるいは計算し,指定出力ファイルに Apache の拒絶定義文(deny from CIDR)の形式で出力するとともに,標準エラー出力に国コード毎の集計データを出力する。CIDR が得られなかったら IP アドレス単体指定を行うものとしている。さて,私の自宅サーバログに基づく実行結果は以下のとおり。標準エラーの全出力を掲げておきます。ヤクザ IP・CIDR の一覧でもありますので,お使いいただいても構いません。
    % ipwhois.pl -o denylist < 404ip.txt 
    113.59.33.9     CN      113.59.0.0/17
    113.84.73.25    CN      113.64.0.0/11
    119.254.72.218  CN      119.254.0.0/15
    121.14.129.100  CN      121.8.0.0/13
    121.141.172.40  KR      121.128.0.0/11
    121.166.70.252  KR      121.160.0.0/11
    140.112.41.58   TW      140.112.0.0/16
    173.162.102.20  US      173.162.64.0/18
    173.245.70.17   US      173.245.70.0/24
    175.177.100.182 JP      175.177.100.0/24
    182.20.206.193  JP      182.20.128.0/17
    184.107.108.37  CA      184.107.108.0/24
    188.127.230.124 RU      188.127.224.0/20
    188.143.233.14  RU      188.143.232.0/23
    188.143.233.160 RU      188.143.232.0/23
    188.143.233.34  RU      188.143.232.0/23
    188.163.105.52  UA      188.163.0.0/17
    188.163.65.234  UA      188.163.0.0/17
    188.163.67.245  UA      188.163.0.0/17
    188.163.67.254  UA      188.163.0.0/17
    188.40.106.11   DE      188.40.0.0/16
    188.40.53.213   DE      188.40.0.0/16
    190.144.175.133 CO      190.144.175.133 (CIDR 190.144/14 not available)
    193.105.210.42  UA      193.105.210.0/24
    195.68.223.44   UA      195.68.222.0/23
    202.19.227.40   JP      202.19.227.40   (CIDR 2 not available)
    203.142.24.17   SG      203.142.24.0/24
    203.180.234.16  JP      203.180.234.0/24
    203.238.185.121 KR      203.238.176.0/20
    204.111.110.29  US      204.111.0.0/16
    204.9.123.122   US      204.9.120.0/21
    208.78.244.107  US      208.78.240.0/21
    209.144.30.98   US      209.144.30.0/24
    209.172.57.77   CA      209.172.32.0/19
    209.190.38.98   US      209.190.38.96/29
    209.236.115.222 US      209.236.112.0/20
    210.165.234.248 JP      210.165.128.0/17
    210.253.108.17  JP      210.253.96.0/19
    211.144.82.8    CN      211.144.64.0/19
    211.233.71.243  KR      211.233.64.0/20
    211.62.35.217   KR      211.62.32.0/21
    211.68.122.12   CN      211.68.120.0/22
    211.72.32.244   TW      211.72.0.0/17
    212.138.82.23   SA      212.138.64.0/18
    212.54.226.117  IT      212.54.224.0/19
    216.171.171.163 US      216.171.160.0/20
    219.136.241.165 CN      219.136.241.165 (CIDR 6 not available)
    221.117.61.178  JP      221.117.61.176/29
    222.186.43.119  CN      222.184.0.0/13
    31.128.142.199  RU      31.128.128.0/19
    31.184.236.13   UA      31.184.236.0/24
    31.184.236.33   UA      31.184.236.0/24
    31.184.236.34   UA      31.184.236.0/24
    31.184.236.36   UA      31.184.236.0/24
    31.184.238.71   RU      31.184.238.0/24
    46.109.177.176  LV      46.109.0.0/16
    46.116.43.68    IL      46.116.0.0/15
    46.21.154.66    US      46.21.144.0/20
    58.191.154.41   JP      58.191.154.40/29
    58.215.12.42    CN      58.208.0.0/12
    59.108.108.100  CN      59.108.0.0/16
    61.187.206.148  CN      61.187.206.148  (CIDR 4 not available)
    61.190.172.2    CN      61.190.0.0/16
    64.124.203.73   US      64.124.0.0/15
    66.87.2.132     US      66.87.0.0/16
    68.15.157.99    US      68.0.0.0/12
    68.58.54.214    US      68.32.0.0/11
    69.133.106.156  US      69.132.0.0/14
    69.26.37.39     US      69.26.32.0/19
    69.73.163.187   US      69.73.128.0/18
    72.29.84.41     US      72.29.64.0/19
    72.44.90.84     US      72.44.80.0/20
    74.217.148.71   US      74.217.0.0/16
    74.217.148.72   US      74.217.0.0/16
    77.207.110.229  FR      77.192.0.0/12
    77.247.181.165  NL      77.247.176.0/21
    77.52.112.197   UA      77.52.64.0/18
    77.92.224.110   GE      77.92.224.0/24
    77.92.233.198   GE      77.92.233.0/24
    81.170.143.96   SE      81.170.128.0/17
    81.25.46.137    BY      81.25.32.0/20
    82.193.117.23   UA      82.193.96.0/19
    85.192.15.82    RU      85.192.0.0/20
    85.88.195.35    IT      85.88.192.0/19
    86.101.224.233  HU      86.101.0.0/16
    86.127.192.137  RO      86.120.0.0/13
    88.40.179.242   IT      88.40.0.0/15
    89.107.227.210  TR      89.107.227.0/24
    89.252.58.228   UA      89.252.0.0/18
    89.28.124.238   MD      89.28.0.0/17
    89.97.190.170   IT      89.97.0.0/16
    91.135.235.139  GB      91.135.224.0/20
    91.207.6.154    UA      91.207.6.0/24
    91.207.6.174    UA      91.207.6.0/24
    91.207.6.82     UA      91.207.6.0/24
    91.214.45.239   LU      91.214.44.0/22
    91.224.160.126  NL      91.224.160.0/23
    91.224.160.127  NL      91.224.160.0/23
    91.224.161.127  NL      91.224.160.0/23
    91.226.165.164  RU      91.226.164.0/22
    92.113.10.162   UA      92.113.0.0/18
    93.182.36.82    RU      93.182.32.0/20
    93.84.116.216   BY      93.84.0.0/15
    94.24.152.61    RU      94.24.128.0/17
    Country list:
    GB = 1; GE = 2; NL = 4; TW = 2; LV = 1; BY = 2; SA = 1; KR = 5; RU = 10; MD = 1;
     TR = 1; JP = 8; CA = 2; SE = 1; CN = 12; DE = 2; LU = 1; CO = 1; RO = 1; SG = 1
    ; IT = 4; UA = 17; US = 21; IL = 1; HU = 1; FR = 1; 
  9. Apache denial list 重複削除
  10. ipwhois.pl が出力した denial list denylist から CIDR が重複するものを一つに纏める。
    % cat denylist | sort | uniq > denylist.txt
  11. Apache httpd.conf への反映
  12. 完成した denial list denylist.txt を Apache のコンフィグレーション(httpd.conf あるいは,これからインクルードされる定義ファイル)中のドキュメントルート Directory ディレクティブのところに,テキストエディタを使って反映する。以下はその例。このあと定義文をチェックし(apachectl -t),問題なければ,Apache を再起動(apachectl restart)して,作業は完了である。
    <VirtualHost *:80>
        ServerName yasuda.homeip.net
        DocumentRoot /usr/local/www/apache22/data
        <Directory />
            Options ExecCGI Includes FollowSymLinks
            AllowOverride None
        </Directory>
        <Directory "/usr/local/www/apache22/data">
            Options ExecCGI Includes FollowSymLinks
            AllowOverride AuthConfig
            Order allow,deny
            Allow from all
            # 以下 denial list 内容の CIDR を指定する
            deny from 113.59.0.0/17
            deny from 113.64.0.0/11
            deny from 119.254.0.0/15
            deny from 121.128.0.0/11
            deny from 121.160.0.0/11
            deny from 121.8.0.0/13
            deny from 140.112.0.0/16
            deny from 173.162.64.0/18
            deny from 173.245.70.0/24
            deny from 175.177.100.0/24
    ...

さて,ipwhois.pl の実行結果から,我が家のサーバ・アタック杯ヤクザ国家の発表です。アタック元 IP の国コードとして多いのは,順に,一位:米国 21 ポイント,二位:ウクライナ 17 ポイント,三位:中国 12 ポイント,四位:ロシア 10 ポイント,五位:日本 8 ポイント,六位:韓国 5 ポイント,七位:オランダ,イタリア 4 ポイントでした。米国,中国,ロシアが上位に来るのは予想がつきましたが,ウクライナの二位は意外でした。もちろんこの国コードはサーバから見ての要求元なので,中国のヤクザが米国のプロクシ経由で攻撃したものは米国に分類されているはずである。

ipwhois.pl コードは以下のとおり。コピペして使っていただいてもよい(無保証)。whois の項目を調べるための Perl モジュール Net::Whois::IPNet::CIDR が必要なので,動かすには事前に cpan コマンドでシステムにモジュールをインストールしておく必要がある。
 

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Get ip-route and generate deny list from whois info
# 2011 (c) isao yasuda.
 
use strict;
use Net::Whois::IP qw(whoisip_query);
use Net::CIDR qw(:all);
use Getopt::Std;    # command line processing
use File::Basename; # get file basename
binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8");
 
# command line.
my %opts = ('o' => "0");
Getopt::Std::getopts('o:', \%opts) ||
    die "Usage: " . basename($0) . " -o denylist \< (stdin)\n";
my %ccode; # 国コード集計カウンタ配列
if ($opts{'o'}) { # deny list open
    open(DL, ">> $opts{'o'}") || die "$opts{'o'} open failed.\n";
} else { # 指定がなければ標準出力
    open(DL, ">-");
}
# IP 毎の処理
while (<STDIN>) {
    chomp($_); $_ =~ s/\s+//g;
    # Whois 情報取得
    my $ip = $_;
    my $response = whoisip_query($ip);
    # 国コード取得
    my $country;
    if ($response->{country}) {
        $country = $response->{country};
    } else {
        $country = $response->{Country};
    }
    $country =~ s/\s+//g; $country =~ tr/a-z/A-Z/;
    # CIDR 取得 なければ IP をセット
    my $cidr; my $comm = "";
    if ($response->{route}) {
        $cidr = $response->{route};
    } else {
        if ($response->{CIDR}) {
            $cidr = $response->{CIDR};
        } else { # IP 範囲から CIDR を計算
            my $range = $response->{inetnum}; $range =~ s/\s+//g;
            $cidr = Net::CIDR::range2cidr($range);
            unless ($cidr =~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+$/) {
                $comm = "\t(CIDR $cidr not available)";
                $cidr = $ip;
            }
        }
    }
    # 複数 CIDR が得られた場合はじめの一つのみにする
    my @acidr = split(/,/, $cidr);
    if ($#acidr) {
        $cidr = $acidr[0]; $cidr =~ s/\s+//g;
        $comm = "\t(Some CIDRs found. Selected one)";
    }
    # 端末出力
    print STDERR "$ip\t$country\t$cidr$comm\n";
    # Apache 用 deny list 出力
    print DL "deny from $cidr\n";
    # 国コード集計
    if (exists($ccode{$country})) {
        $ccode{$country} += 1;
    } else {
        $ccode{$country} = 1;
    }
}
 
# 国コード集計出力.
print STDERR "Country list:\n";
foreach (keys %ccode) { print STDERR "$_ = $ccode{$_}; "; }
print STDERR "\n";
close(DL); 

追記:

こんなことをやっていたために,クラブワールドカップ・柏レイソル vs 北中米カリブ海代表モンテレイの試合を見忘れてしまった。柏レイソルが PK で勝利した。すげぇ。ベスト 4 進出。次はブラジル王者サントスとの対戦。楽しみじゃ。
 

12.12 追記:

whois から複数の CIDR が返却される場合があるため,その場合一つだけ採用するよう ipwhois.pl を訂正した。