EC2でネットワークのパケット監視をしたい場合、どうするのがいいんでしょうか。商用だとDeep SecurityとかAlert Logicとかありそうですが、値段もそれなりにします。なんとかOSSでできないかということで、ネットワーク型IDS/IPSであるSnortと、ELK (ElasticSearch, Logstash, Kibana)の組み合わせを試してみました。

構成
Snortを使う場合、監視用のサーバにSnortを入れて、そのサーバにネットワークを流れるパケットを監視させる形がふつうなようです。ただEC2でこの形を実現するのは大変そうなので、今回は各EC2インスタンスにSnortを入れる形にしてみます。各インスタンスは自分のところに来たパケットだけを監視します。
また、これだけだとSnortの出すアラートが各インスタンスに散らばってしまいます。そこで、アラートをlogstashで集めて、Amazon Elasticsearch Serviceに送るようにします。Snortに対応したネットワーク監視WebアプリとしてSnorbyというのもあるようですが、こういうログもKibanaから見たいなーと思ってElasticsearch Serviceにしてみました。
Snortの設定
Snortの設定はまじめにやると面倒そうです。こちらの記事等に手順はまとまってますが、かなり長いです。
今回はお試しということで、こちらのシェルスクリプトを実行すると、aptでのインストール+必要な設定がされるようにしました。OSはUbuntu 14.04です。スクリプト内では、以下の設定をしています。
- 監視範囲のネットワークを絞る(自ホストだけ監視するようにする)
- アラートのフォーマットをlogstashから扱いやすいものにする
- checksumモードの変更。デフォルトのモードだと、Snortルールの
uricontentという記法がうまく機能しなかったため。 - logstashから読めるように、ファイルとディレクトリのパーミッションを変更。
Amazon Elasticsearch Serviceの設定
AWSのコンソールからぽちぽちやって、Elasticsearch+Kibanaを構築しました。ほぼデフォルトのままですが、Access Policyは、Snortを入れたEC2インスタンスのパブリックIPアドレスと、Kibanaでログを見るマシンのIPアドレスからアクセスできるように設定しました。Elasticsearchのエンドポイントは後で使うのでメモしておいて下さい。
構築できたら、次のようなリクエストを出して、snort-alertというインデックスを作成します。stringタイプのフィールドの値をindex化するとき、値を勝手に変更しないように設定しています。これをしないと、a-bという値が、aとbに分割されたりします。また、SrcGeo.locationというプロパティの型も指定しています。これは後で位置情報を使ったグラフを作るときに役立ちます。
curl -XPUT [Elasticsearchのエンドポイント]/snort-alert -d '{
"mappings": {
"logs": {
"dynamic_templates": [
{
"notanalyzed": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"index": "not_analyzed"
}
}
}
],
"properties": {
"SrcGeo": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
}
}
}'
Logstashの設定
Logstashは、こちらのシェルスクリプトを実行すると、elastic社のレポジトリからlogstashをインストール+必要な設定をするようにしました。スクリプト内では、Snortが/var/log/snort/alertというファイルに出したアラートを、Elasticsearchに投げる設定をしています。ENDPOINT変数は、Elasticsearchのエンドポイントを入れて下さい。
擬似的な攻撃をしてみる
では、Snortを入れたサーバに攻撃してみて、Kibanaからログを確認できるか試してみます。と言いたいところですが、EC2インスタンスに攻撃する場合は、事前申請が必要です。
申請して許可を待つのも時間がかかるので、今回は擬似的な攻撃と、擬似的な攻撃に反応してアラートを出すSnortルールを作成します。擬似的な攻撃として、pseudo_web_attackあるいはpseudo_dos_attackという文字列をHTTPリクエストのURIに含める攻撃がある、とします。この攻撃に反応するSnortルールとして、次のようなルールを作成します。
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Pseudo WEB-ATTACKS"; flow:to_server,established; uricontent:"pseudo_web_attack"; nocase; classtype:web-application-attack; sid:1000001; rev:1;) alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Pseudo DoS attack"; flow:to_server,established; uricontent:"pseudo_dos_attack"; nocase; classtype:attempted-dos; sid:1000002; rev:1;)
パッと見はややこしそうですが、実際は、HTTPリクエストのURIに特定の文字列が含まれていないか見ているだけです。このルールは、Snortを入れたサーバの/etc/snort/rules/local.rulesに追記して下さい。追記したらSnortを再起動します。
準備ができたら、http://[Snortを入れたサーバのIPアドレス]/pseudo_dos_attack、あるいはhttp://[Snortを入れたサーバのIPアドレス]/pseudo_web_attackにアクセスします。
成功していれば、KibanaのDiscover画面から、Snortのアラートログが確認できると思います。例えば次のような画面です。

攻撃の様子をグラフで表示する
せっかくKibanaにログを集めたので、色々なグラフを出してみます。まずは直近の攻撃回数の折れ線グラフです。攻撃の種類ごとに線の色を分けてみました。

Web Application Attack(緑色の線)とAttempted Denial of Service(濃い青色の線)は、擬似的な攻撃によるものです。残りは何もしていないのに出ているアラートです。False Positiveなアラートだと思うので、Snortルールのチューニングが必要そうです。
次も直近の攻撃回数の折れ線グラフですが、ホストごとに線の色を分けています。よく攻撃されているホストがわかります。

最後は攻撃元の位置情報を世界地図上にマップした図です。円の色の濃さで攻撃回数の大小がわかります。

まとめて、ダッシュボード化してみました。

まとめ
Snort+ELKでネットワークのパケット監視をやってみました。今回はSnortの設定をかなり省略しましたが、実際はルールの更新とかルールのチューニングが必要そうです。また、Snortを各サーバに入れる形だと、Snortがサーバ本来の処理を阻害しないか不安です。そもそも実際の環境だと、全サーバにSnortを入れるより、入り口付近のサーバにだけ入れれば十分な気がします。このあたりはまた調べたいなーと思います。