Apache のアクセスログを時折チェックすると,様々なアタックがあることに気付く。公開 Web サーバを運用しているとこれはもうしようがない。GET //
Apache には特定の IP アドレス,ネットワークからの要求を拒絶する機能が備わっている。あるドキュメントに対して deny from IP | CIDR を指定すればよい。ヤクザ・ホストの IP アドレスをログから抽出しここに指定しておけば,Apache は 403 レスポンス(forbidden)を返し,アクセスを拒絶してくれる。今回は IP アドレスそのものではなく,こいつらヤクザが属しているネットワーク全体からの要求を拒絶するように CIDR(ネットワークアドレス/マスクビット)を指定することにした。暴力団排除条例が施行されたことだし,「関係者」ともども皆お断りというわけだ。今日は,その設定変更レシピの備忘録を示しておく。ここで % は UNIX コマンドライン tcsh プロンプトを示している。
- 存在しないドキュメント参照記録の抽出 まず 404 の履歴を Apache のアクセスログから抽出する。user, webserver は,それぞれ Web サーバへのログイン ID,ホスト名である。リモートシェルを使って Mac OS からオペレーションしている。
- アタックの特定 404list.txt をテキストエディタで開いて内容を確認し,明らかにアタックだと思われるアクセス(.cgi, .dll, .php の拡張子をもつ,いかがわしいプログラムを実行するもの)を特定し,それ以外の行を削除して格納する。
- IP アドレス・リストの作成 編集済のログデータ 404list.txt から IP アドレスだけを抽出・編集し,重複を排除し,対象リスト 404ip.
- Whois 情報から Apache denial list の作成 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 の一覧でもありますので,お使いいただいても構いません。
- Apache denial list 重複削除 ipwhois.pl が出力した denial list denylist から CIDR が重複するものを一つに纏める。
- Apache httpd.conf への反映 完成した denial list denylist.txt を Apache のコンフィグレーション(httpd.conf あるいは,これからインクルードされる定義ファイル)中のドキュメントルート Directory ディレクティブのところに,テキストエディタを使って反映する。以下はその例。このあと定義文をチェックし(apachectl -t),問題なければ,Apache を再起動(apachectl restart)して,作業は完了である。
% rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log > 404list.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
% 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;
% cat denylist | sort | uniq > denylist.txt
<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::IP と Net::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 を訂正した。