OSSEC ではじめるセキュリティログ監視

インフラストラクチャー部の星 (@kani_b) です。

Heartbleed, ShellShock, XSA-108 (a.k.a. EC2 インスタンス再起動祭), POODLE など、今年は話題となるような脆弱性が各地を襲う一年でした。 脆弱性への対応に加え、いわゆるセキュリティ対策に日頃頭を悩ませている方も多いのではないかと思います。 一言にセキュリティ対策と言っても、実際やるべきことは多岐にわたります。今回はそのうちの一つとして、OSSEC という IDS (侵入検知システム) を使ったセキュリティログ監視についてご紹介します。

OSSEC とは

OSSEC は、いわゆるホスト型の IDS (HIDS) です。以下のような機能を持っています。

  • ログ解析、監視
  • ファイルの変更監視
  • rootkit の検知
  • それらをトリガにしたプログラムの自動実行 (Active Response)

もともと OSS として開発がスタートし、米トレンドマイクロ社に買収された後もトレンドマイクロ社をスポンサーとして継続して開発が行われています。 GPLv2 ライセンスで配布されています。GitHub にレポジトリがあります。(こちら)

Linux はもちろんのこと、OSX, Windows, Solaris など様々な OS をサポートしていることも特徴の一つです。混在環境を一つのソフトウェアでモニタリングすることができます。

インストール

ダウンロードページ から最新版をダウンロードしてインストールします。(本記事執筆時点で 2.8.1)
tarball にあるソースコードから自分でビルドすることもできますし、ビルドしたバイナリをサーバに配信することも可能ですが、 CentOS, Fedora, Debian にはパッケージが用意されていますのでそちらを使うと楽です。
詳細なインストール手順については割愛します。

構成

OSSEC の Web サイト にも記載がありますが、基本的には1台マネージャを用意しエージェントを各サーバにインストールする形をとります。
エージェントはマネージャにサーバのログやファイルハッシュなどを送るだけの存在で、ログの分析や変更検知、アラートの発報などは全てマネージャが行います。 そのため、特にマネージャの性能は、監視対象となるサーバの数やログの流量によって調整する必要があります。
クックパッドでは数百台のインスタンスが常に稼働していますが、現在のところそれらのログを一台のマネージャで処理することができています。

マネージャへのエージェント追加

マネージャは、各エージェントの状態も (変更検知など) 含めて監視を行うため、まずマネージャに対してエージェントを登録する必要があります。
このため、マネージャのセットアップとエージェントのインストールが終わったら、マネージャ側で manage_agents コマンドを実行し、発行された鍵をエージェントにコピー、という手順を踏む必要があります。
この手順は ossec-authd を使うことで自動化できます (マネージャで ossec-authd を実行した状態でエージェントから agent-auth コマンドを利用)。
ですが、現在はまだ ossec-authd に認証機構が存在しないことにご留意ください。

設定

OSSEC の設定は、大きく以下のようなものに分かれています。

  • 基本設定
    • サーバ名、マネージャのホスト名、監視対象ファイルなどの設定
  • デコーダ (マネージャのみ)
    • 送信されてきたログファイルのパーサ
  • ルール (マネージャのみ)
    • ログファイルの監視ルール

これらの設定は XML で記述します。 今回は OSSEC でどんなことができるのかを中心にご紹介するため、基本設定などについては割愛します。

ルールについて

まず、デフォルトで用意されているルールからシンプルなものを用いて説明します。 以下の XML は こちら からの引用です。

  <rule id="31110" level="6">
    <if_sid>31100</if_sid>
    <url>?-d|?-s|?-a|?-b|?-w</url>
    <description>PHP CGI-bin vulnerability attempt.</description>
    <group>attack,</group>
  </rule>

このルールは、Web サーバのログを対象とし、URL に ?- という文字列が存在すると発報します。 description に書いてあるように、数年前話題になった PHP (CGI モード) の脆弱性ですね。

各ルールは一意の ID を持ちます。level はアラートを発報する閾値として使われます。 group は Active Response やサマリ作成時などのグルーピングに利用できます。

if_sid というパラメータは、それ以前にマッチしたルール ID を指します。 rule id 31100 を見てみると、以下のように記述されています。

  <rule id="31100" level="0">
    <category>web-log</category>
    <description>Access log messages grouped.</description>
  </rule>

このルールでは、新たに category: web-log が参照されています。 category とは、ログがデコーダによって解読される際に付けられるグループ名です。 syslog や web-log (アクセスログ) などが種類として用意されています。
例えば Common Log Format のデコーダ を見てみます。

<decoder name="web-accesslog">
  <type>web-log</type>
  <prematch>^\d+.\d+.\d+.\d+ |^::ffff:\d+.\d+.\d+.\d+ </prematch>
  <regex>^(\d+.\d+.\d+.\d+) \S+ \S+ [\S+ \S\d+] </regex>
  <regex>"\w+ (\S+) HTTP\S+ (\d+) </regex>
  <order>srcip, url, id</order>
</decoder>

送られてきたログは上記のような正規表現にかけられ、ソース IP, URL, ID (HTTP レスポンス) を取り出します。 ここで取り出している URL などが実際にルールに渡されることになります。
紹介が逆順になってしまいましたが、OSSEC ではこのように、送られてきたログはまずデコーダで処理され、 広いルールから細かいルールへと判定されていきます。

デコーダとルールの改良

例えば、ログインページへの攻撃を監視するために、以下のようなルールを作ることを考えてみます。

  • リクエスト先は /login
  • リクエストの種類は POST
  • 同じ IP アドレスから2分以内に20回以上リクエストがあったらアラート

実際のログは以下のようなものになります。

198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -

デコーダやルールは、OSSEC インストールディレクトリの bin/ossec-logtest でテストすることができます。 起動して、ログを貼り付けてみます。

$ bin/ossec-logtest
2014/12/26 10:44:53 ossec-testrule: INFO: Reading local decoder file.
2014/12/26 10:44:53 ossec-testrule: INFO: Started (pid: 27470).
ossec-testrule: Type one log per line.

198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -


**Phase 1: Completed pre-decoding.
       full event: '198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -'
       hostname: 'ossec-test'
       program_name: '(null)'
       log: '198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -'

**Phase 2: Completed decoding.
       decoder: 'web-accesslog'
       srcip: '198.51.100.10'
       url: '/login'
       id: '200'

**Phase 3: Completed filtering (rules).
       Rule id: '31108'
       Level: '0'
       Description: 'Ignored URLs (simple queries).'

Phase 1, 2 でデコードされたログが Phase 3 でルールと照合されます。 ルールで使われるのは Phase 2 でデコードされた srcip, url, id ですので、このままだと HTTP メソッドを判断材料にできません。 そのため、デコーダをちょっと変更します。

<decoder name="web-accesslog">
  <type>web-log</type>
  <prematch>^\d+.\d+.\d+.\d+ |^::ffff:\d+.\d+.\d+.\d+ </prematch>
  <regex>^(\d+.\d+.\d+.\d+) \S+ \S+ [\S+ \S\d+] </regex>
  <regex>"(\w+) (\S+) HTTP\S+ (\d+) </regex>
  <order>srcip, extra_data, url, id</order>
</decoder>

デコーダの syntax にある通り、HTTP メソッドを格納する項目はないため、
今回は自由に使える extra_data という項目に格納します。
内で () でグルーピングされた文字列が 内で指定した項目の順に格納されます。 この変更をした状態で、同じログをテストすると以下のようになります。

**Phase 1: Completed pre-decoding.
       full event: '198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -'
       hostname: 'ossec-test'
       program_name: '(null)'
       log: '198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -'

**Phase 2: Completed decoding.
       decoder: 'web-accesslog'
       srcip: '198.51.100.10'
       extra_data: 'POST'
       url: '/login'
       id: '200'

**Phase 3: Completed filtering (rules).
       Rule id: '31108'
       Level: '0'
       Description: 'Ignored URLs (simple queries).'

Phase 2 で extra_data にメソッドが格納されていることがわかります。 これでデコーダの準備はできたので、ルールを作成していきます。

<rule id="91000" level="10" frequency="20" timeframe="120">
  <if_sid>31108</if_sid>
  <url>/login</url>
  <extra_data>POST</extra_data>
  <same_source_ip />
  <description>Multiple login challenge from same source ip.</description>
  <group>attack,</group>
</rule>

ルールのパラメータとして frequency と timeframe があります。 今回の設定では、120秒以内に20回このルールに合致するログがあった場合にアラートが発報されます。

これでルールも完成したのでテストをしてみます。 bin/ossec-logtest を実行して、ログを20行流してみると、以下のようにアラートが上がることが確認できます。

**Phase 1: Completed pre-decoding.
       full event: '198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -'
       hostname: 'ossec-test'
       program_name: '(null)'
       log: '198.51.100.10 - - [24/Dec/2014:23:43:56 +0900] "POST /login HTTP/1.1" 200 -'

**Phase 2: Completed decoding.
       decoder: 'web-accesslog'
       srcip: '198.51.100.10'
       extra_data: 'POST'
       url: '/login'
       id: '200'

**Phase 3: Completed filtering (rules).
       Rule id: '91000'
       Level: '10'
       Description: 'Multiple login challenge from same source ip.'

実運用ではさらに細かく、ログインページへの POST を攻撃ではないイベントとしてルール化し、 その繰り返しをアラートとするなど、ルールの作り方を工夫すればより見通しが良くなると思います。

デフォルトルール

ここまで、カスタマイズしたルールを作ることを前提に説明してきましたが、 それ以外にも OSSEC は色々なルールをデフォルトで持っています。

  • SSH ログインの連続失敗
  • XSS, SQL インジェクション
  • 長すぎるパラメータ
  • SEGV

これら、直接何かの異変に繋がるようなものの他にも、 通常のログインや root 権限の取得、ユーザの追加/削除 などをイベントとするルールも用意されています。

可視化

公式に Web UI は存在しますが、FAQ に書かれているように あまりコミットされておらず、 また実装もアラートのテキストファイルを直接読むような実装ですので、ある程度以上の環境になった途端に破綻します。
アラートの出力は MySQL や PostgreSQL にも可能 で、 Analogi という Web UI も作られてはいますが、 最近ではみんな大好き ELK (Elasticsearch + Logstash + Kibana) を使うのが良さそうです。
Logstash 側で OSSEC のログを拾えるようにする設定 などが公開されています。

OSSEC に (そのままでは) できないこと

これまでご紹介してきた通り、OSSEC のログ監視機能はあくまでシステムのログを用いるものであり、 実際の通信を監視するようなことはできません。
例えば HTTP POST パラメータに含まれる攻撃を監視したい場合などは、別途 mod_security や Snort などを導入する必要があるでしょう。
ただし、mod_security が出す BAN ログなどを監視することはもちろん可能です。ある程度対応するルールは標準で用意されています。

まとめ

OSSEC という HIDS で、ログを主にセキュリティの視点から監視する方法について紹介しました。 実際にクックパッドでは、2011年頃から OSSEC をサーバ監視の一部として利用しています。

AWS EC2 では、例えば全トラフィックが通るミラーポートなどを作成する、などの方法が取れないため セキュリティ面での監視も基本的に全ホストにノードを導入して行うような構成になることが多いです。 商用ソフトウェアだと DeepSecurity や AlertLogic, Sourcefire などがネットワーク監視も含めた形で AWS 界隈では有名ですね。
OSSEC は、それ単体でセキュリティ機能を完結させるのではなく、他のセキュリティソフトウェアも含めてログを流し、 全サーバのイベントを監視してみるとなかなか有用なのではと思います。

変更監視や rootkit 検知などの機能、Active Response など今回紹介できなかったこともありますが、 興味のある方は是非試してみてください。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/