Snortを利用したIPSの構築

オープンソースソフトウェアSnortを利用したIPSの構築を行う機会があったので、その構築手順をまとめます。
長編です。

Snort
https://www.snort.org/

isdSnortとIDS, IPS

SnortはIDS(Intrusion Detection System, 不正侵入検知システム)機能をもつソフトウェアとして有名ですが、「Inlineモード」で起動することで、IPS(Intrusion Prevention System, 不正侵入防止システム)として利用できます。

IDSは不正なアクセスを検知してログに書き出すだけで実際にはそのアクセスは行われてしまいますが、IPSは、不正なパケットを破棄することで不正なアクセスを自動的に防止します。

isdサーバー構成

今回説明するサーバーの構成を図1に示します。
(クリックすると大きく表示されます。)

図1. Snortを利用したIPSのサーバー構成
図1. Snortを利用したIPSのサーバー構成

Webサーバーでは、ApacheなどのWebソフトウェアが起動しており、Webコンテンツや(必要に応じて)DBソフトウェアが設置されているものとします。
IPSサーバーでは、Snortによるパケット検査と、iptablesによるパケット転送を行います。
インターネットとWebサーバーの間にIPSサーバーを挟み込む(=inline)ように設置することで、Webサーバーを攻撃から守ることができます。

IPSサーバーにおいて、外部からグローバルIPアドレスへのアクセスは下記のように転送します。

  • TCP/80 (HTTP) → WebサーバーのTCP/80
  • TCP/443 (HTTPS) → WebサーバーのTCP/443
  • TCP/10022 (SSH) → WebサーバーのTCP/22

また、Webサーバーーからインターネットへのアクセスを転送します。

  • Webサーバー → IPSサーバー → インターネット

このうち、HTTPとHTTPSのパケットを検査し、SSHのパケットは検査しないものとします。

※SSHについては、パケット検査を行ったところまったく通信できなくなったことと、今回はアクセス元が特定のIPアドレスのみで制限されるということで、検査しないことにしました。

サーバーのOSはCentOS 6.4 (64bit)とし、必要なソフトウェアは極力CentOSリポジトリからyumコマンドでインストールします。
CentOSやApache, MySQL等、基本的なソフトウェアの設定については、ここでは記載しません。

※この構成の検証は、「さくらの夕べ in 札幌」に参加したときにいただいた「さくらのクラウド」2万円分無料提供アカウントによる環境を利用して行いました。さくらインターネットさん、ありがとうございます!

isd構築手順

おおまかに下記の4つの手順となります。

1. Snortのインストール
2. PulledPorkによるルールファイルの自動更新設定
3. IPSとしての動作設定
4. Swatchによるアラート通知設定

1. Snortのインストール

まず、SnortとSnortの起動に必要なソフトウェアをインストールします。
インストールにあたっては、下記がとても参考になりました。

1-1. 準備

ビルドや稼働に必要なパッケージ gcc, flex, bison, pcre, pcre-devel, libpcap, libpcap-devel, zlib, zlib-devel をインストールします。

 # yum install gcc flex bison pcre pcre-devel libpcap libpcap-devel zlib zlib-devel

...
Installed:
  libpcap-devel.x86_64 14:1.4.0-1.20130826git2dbcaa1.el6

Updated:
  gcc.x86_64 0:4.4.7-4.el6   libpcap.x86_64 14:1.4.0-1.20130826git2dbcaa1.el6

Dependency Updated:
  cpp.x86_64 0:4.4.7-4.el6              gcc-c++.x86_64 0:4.4.7-4.el6
  gcc-gfortran.x86_64 0:4.4.7-4.el6     libgcc.x86_64 0:4.4.7-4.el6
  libgfortran.x86_64 0:4.4.7-4.el6      libgomp.x86_64 0:4.4.7-4.el6
  libstdc++.x86_64 0:4.4.7-4.el6        libstdc++-devel.x86_64 0:4.4.7-4.el6

1-2. インストール

libdnet, DAQ, Snortを順にインストールします。

・libdnetのインストール
 # cd /usr/local/src/
 # wget http://prdownloads.sourceforge.net/libdnet/libdnet-1.11.tar.gz
 # tar zxvf libdnet-1.11.tar.gz
 # cd libdnet-1.11/
 # ./configure --with-pic
 # make
 # make install

 #  ls -l /usr/local/lib/libdnet*

lrwxrwxrwx 1 root root     13  1月 12 11:46 2014 /usr/local/lib/libdnet -> libdnet.1.0.1
lrwxrwxrwx 1 root root     13  1月 12 11:46 2014 /usr/local/lib/libdnet.1 -> libdnet.1.0.1
-rwxr-xr-x 1 root root 160620  1月 12 11:46 2014 /usr/local/lib/libdnet.1.0.1
-rw-r--r-- 1 root root 272878  1月 12 11:46 2014 /usr/local/lib/libdnet.a
-rwxr-xr-x 1 root root    778  1月 12 11:46 2014 /usr/local/lib/libdnet.la

・libnetfilter_queue, libnfnetlink, libmnlのインストール

DAQのNFQモジュールをビルドするためには、libnetfilter_queue が必要です。
さらに、libnetfilter_queue のビルドには、libnfnetlink, libmnl が必要です。

※NFQモジュールは、今回SnortをInlineモードで起動するために必要となります。

(参考)
 http://netfilter.org/projects/libnetfilter_queue/

※configureの実行時に –libdir=/usr/local/lib64 を指定しないとエラーします。

 # cd /usr/local/src/
 # wget http://netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.1.tar.bz2
 # tar jxvf libnfnetlink-1.0.1.tar.bz2
 # cd libnfnetlink-1.0.1/
 # export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig
 # ./configure --libdir=/usr/local/lib64
 # make
 # make install

 # ls -l /usr/local/lib64/libnfnetlink*

-rwxr-xr-x 1 root root   957  1月 12 11:49 2014 /usr/local/lib64/libnfnetlink.la
lrwxrwxrwx 1 root root    21  1月 12 11:49 2014 /usr/local/lib64/libnfnetlink.so -> libnfnetlink.so.0.2.0
lrwxrwxrwx 1 root root    21  1月 12 11:49 2014 /usr/local/lib64/libnfnetlink.so.0 -> libnfnetlink.so.0.2.0
-rwxr-xr-x 1 root root 79017  1月 12 11:49 2014 /usr/local/lib64/libnfnetlink.so.0.2.0

 # ls -l /usr/local/include/libnfnetlink/

-rw-r--r-- 1 root root 7970  1月 12 11:49 2014 libnfnetlink.h
-rw-r--r-- 1 root root 2574  1月 12 11:49 2014 linux_nfnetlink.h
-rw-r--r-- 1 root root 2407  1月 12 11:49 2014 linux_nfnetlink_compat.h

 # cd /usr/local/src/
 # wget http://netfilter.org/projects/libmnl/files/libmnl-1.0.3.tar.bz2
 # tar jxvf libmnl-1.0.3.tar.bz2
 # cd libmnl-1.0.3/
 # export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig
 # ./configure --libdir=/usr/local/lib64
 # make
 # make install

 # ls -l /usr/local/lib64/libmnl*

-rwxr-xr-x 1 root root   919  1月 12 11:50 2014 /usr/local/lib64/libmnl.la
lrwxrwxrwx 1 root root    15  1月 12 11:50 2014 /usr/local/lib64/libmnl.so -> libmnl.so.0.1.0
lrwxrwxrwx 1 root root    15  1月 12 11:50 2014 /usr/local/lib64/libmnl.so.0 -> libmnl.so.0.1.0
-rwxr-xr-x 1 root root 56727  1月 12 11:50 2014 /usr/local/lib64/libmnl.so.0.1.0

 # cd /usr/local/src/
 # wget http://netfilter.org/projects/libnetfilter_queue/files/libnetfilter_queue-1.0.2.tar.bz2
 # tar jxvf libnetfilter_queue-1.0.2.tar.bz2
 # cd libnetfilter_queue-1.0.2/
 # ./configure

...
checking for LIBNFNETLINK... yes
checking for LIBMNL... yes
...

 # make
 # make install

 # ls -l /usr/local/lib/libnetfilter_queue*

-rwxr-xr-x 1 root root  1070  1月 12 11:51 2014 /usr/local/lib/libnetfilter_queue.la
lrwxrwxrwx 1 root root    27  1月 12 11:51 2014 /usr/local/lib/libnetfilter_queue.so -> libnetfilter_queue.so.1.3.0
lrwxrwxrwx 1 root root    27  1月 12 11:51 2014 /usr/local/lib/libnetfilter_queue.so.1 -> libnetfilter_queue.so.1.3.0
-rwxr-xr-x 1 root root 90331  1月 12 11:51 2014 /usr/local/lib/libnetfilter_queue.so.1.3.0

・DAQのインストール

Snort公式サイトhttps://www.snort.org/downloads より、最新の daq-2.0.1.tar.gz をダウンロードします。

コマンドラインでのダウンロード方法は以下に説明があります。
https://www.snort.org/

 # cd /usr/local/src/
 # wget http://www.snort.org/dl/snort-current/daq-2.0.1.tar.gz -O daq-2.0.1.tar.gz
 # tar zxvf daq-2.0.1.tar.gz
 # cd daq-2.0.1/
 # ./configure

このとき、SnortをNFQモジュールを使用したInlineモードで起動するためには、’Build NFQ DAQ module’が yes となっていることを確認してください。

Build AFPacket DAQ module.. : yes
Build Dump DAQ module...... : yes
Build IPFW DAQ module...... : yes
Build IPQ DAQ module....... : no
Build NFQ DAQ module....... : yes //★ここ
Build PCAP DAQ module...... : yes

 # make
 # make install

 # ls -l /usr/local/lib/daq/

-rwxr-xr-x 1 root root   958  1月 12 11:53 2014 daq_afpacket.la
-rwxr-xr-x 1 root root 47724  1月 12 11:53 2014 daq_afpacket.so
-rwxr-xr-x 1 root root   914  1月 12 11:53 2014 daq_dump.la
-rwxr-xr-x 1 root root 25382  1月 12 11:53 2014 daq_dump.so
-rwxr-xr-x 1 root root   934  1月 12 11:53 2014 daq_ipfw.la
-rwxr-xr-x 1 root root 28295  1月 12 11:53 2014 daq_ipfw.so
-rwxr-xr-x 1 root root  1099  1月 12 11:53 2014 daq_nfq.la
-rwxr-xr-x 1 root root 88921  1月 12 11:53 2014 daq_nfq.so // ★daq_nfq.so が作成された。
-rwxr-xr-x 1 root root   914  1月 12 11:53 2014 daq_pcap.la
-rwxr-xr-x 1 root root 30092  1月 12 11:53 2014 daq_pcap.so

・Snortのインストール

Snort公式サイト
https://www.snort.org/downloads/
より、最新の Snort をダウンロードします。

 # cd /usr/local/src/
 # wget http://www.snort.org/dl/snort-current/snort-2.9.5.6.tar.gz \
    -O snort-2.9.5.6.tar.gz
 # tar zxvf snort-2.9.5.6.tar.gz
 # cd snort-2.9.5.6/
 # ./configure \
    --enable-gre \
    --enable-targetbased \
    --enable-active-response \
    --enable-normalizer \
    --enable-reload \
    --enable-react \
    --enable-zlib

 # make  // 2分ぐらいかかる。
 # make install

 # ldconfig -v /usr/local/lib

Snortプログラム、ライブラリなどが /usr/local/{bin,include,lib,src} にインストールされます。

 # ls -l /usr/local/bin/snort

-rwxr-xr-x 1 root root 8570216  1月 12 11:57 2014 /usr/local/bin/snort

これで、SnortとSnortの起動に必要なソフトウェアのインストールは完了です。

1-3. Configの編集とSnortルールファイルの設置

Snort公式サイトが発行するルールファイルを設置します。
あわせて、Configの編集を行います。

・サインアップ

ルールファイルを取得するため、Snort公式サイトでサインアップします。
https://www.snort.org/users/sign_up

ID、メールアドレス、パスワードを入力します。

・Oinkcodeの取得

Snort公式サイトで、ルールファイルの自動更新のための Oinkcode というコードを取得します。

https://www.snort.org/users/sign_in
で sign in します。

https://www.snort.org/account/oinkcode
で、’Generate code’ とするとOinkcodeが発行されるのでコードを控えておきます。

・ルールファイルのダウンロード

Snort公式サイト
https://www.snort.org/
でログインし、
https://www.snort.org/snort-rules/
で、’Registered User Release’ の中の最新ルール snortrules-snapshot-2955.tar.gz をPCにダウンロードしてから、サーバーにアップロードします。

※Snortのバージョン2.9.5.6よりひとつ古いバージョンですが、’Registered User Release’ で無償で配布されているルールファイルは有償のものよりも少し古いバージョンということですので、問題ありません。

または、下記に書かれているとおり、Oinkcodeを使用してコマンドラインでダウンロードします。
https://www.snort.org/snort-rules/cli

 # cd /usr/local/src/
 # wget http://www.snort.org/reg-rules/snortrules-snapshot-2955.tar.gz/<oinkcode> \
    -O snortrules-snapshot-2955.tar.gz

・展開

ダウンロードしたファイルは、いったんまとめて /etc/snort/ に配置します。
ただし、Configファイルについては、ダウンロードしたものではなく、Snortソースセットに含まれるConfigファイルで上書します。
Makefileは不要なので削除します。

 # mkdir /etc/snort
 # cd /etc/snort/
 # tar zxvf /usr/local/src/snortrules-snapshot-2955.tar.gz
 # cp ./etc/* .
 # cp /usr/local/src/snort-2.9.5.6/etc/* .
 # rm /etc/snort/Makefile*

空のブラックリストファイル、ホワイトリストファイルを生成します。
(生成しないと、Snortが起動しません。)

 # touch /etc/snort/rules/white_list.rules /etc/snort/rules/black_list.rules

 # ls -l

-rw-r--r-- 1 root root    1281  1月 12 12:09 2014 attribute_table.dtd
-rw-r--r-- 1 root root    3793  1月 12 12:09 2014 classification.config
drwxr-xr-x 2 1210 1210    4096 12月 12 04:48 2013 etc
-rw-r--r-- 1 root root   31002  1月 12 12:09 2014 gen-msg.map
drwxr-xr-x 2 1210 1210    4096 12月 12 04:48 2013 preproc_rules
-rw-r--r-- 1 root root     687  1月 12 12:10 2014 reference.config
drwxr-xr-x 2 1210 1210    4096  1月 12 12:10 2014 rules
-rw-r--r-- 1 root root 3908947  1月 12 12:09 2014 sid-msg.map
-rw-r--r-- 1 root root   27587  1月 12 12:10 2014 snort.conf
drwxr-xr-x 4 1210 1210    4096 12月 12 04:21 2013 so_rules
-rw-r--r-- 1 root root    2556  1月 12 12:10 2014 threshold.conf
-rw-r--r-- 1 root root  160606  1月 12 12:10 2014 unicode.map

・ダイナミックルールファイルのコピー

snort_dynamicrules ファイルを、/usr/local/lib/snort_dynamicrules にコピーします。

 # mkdir /usr/local/lib/snort_dynamicrules
 # cp /etc/snort/so_rules/precompiled/RHEL-6-0/x86-64/2.9.5.5/* \
    /usr/local/lib/snort_dynamicrules/

・グループ、ユーザーの作成

Snortの実行グループsnort、ユーザーsnortを作成します。

 # groupadd -g 511 snort
 # useradd snort -u 511 -d /var/log/snort -s /sbin/nologin \
    -c 'Snort User' -g snort
 # chown -R snort.snort /etc/snort
 # chown -R snort.snort /var/log/snort

 # ls -lR /etc/snort

・Configの編集
 # cp -p /etc/snort/snort.conf /etc/snort/snort.conf.default

 # vi /etc/snort/snort.conf

ここでは、デフォルトの設定から下記の箇所のみ変更します。

# 内部ネットワーク -> パケット検査対象のIPアドレス
#ipvar HOME_NET any
ipvar HOME_NET <IPSサーバーのグローバルIPアドレス>/32

# 外部ネットワーク -> $HOME_NET以外
#ipvar EXTERNAL_NET any
ipvar EXTERNAL_NET !$HOME_NET

# ルール、ホワイトリスト、ブラックリストのパス
#var RULE_PATH ../rules
#var SO_RULE_PATH ../so_rules
#var PREPROC_RULE_PATH ../preproc_rules
var RULE_PATH /etc/snort/rules
var SO_RULE_PATH /etc/snort/so_rules
var PREPROC_RULE_PATH /etc/snort/preproc_rules

#var WHITE_LIST_PATH ../rules
#var BLACK_LIST_PATH ../rules
var WHITE_LIST_PATH /etc/snort/rules
var BLACK_LIST_PATH /etc/snort/rules

念のため、以下のパスが正しいことも確認します。

dynamicpreprocessor directory /usr/local/lib/snort_dynamicpreprocessor/
dynamicengine /usr/local/lib/snort_dynamicengine/libsf_engine.so
dynamicdetection directory /usr/local/lib/snort_dynamicrules

・パーミッションの変更
 # cd /usr/local/lib/
 # chown -R snort.snort snort* pkgconfig
 # chmod -R 700 snort* pkgconfig
 # ls -l

...
drwx------ 2 snort snort   4096  1月 12 11:57 2014 pkgconfig
drwx------ 4 snort snort   4096  1月 12 11:57 2014 snort
drwx------ 2 snort snort   4096  1月 12 11:57 2014 snort_dynamicengine
drwx------ 2 snort snort   4096  1月 12 11:57 2014 snort_dynamicpreprocessor
drwx------ 2 snort snort   4096  1月 12 12:11 2014 snort_dynamicrules

 # cd /usr/local/bin/
 # chown snort.snort daq-modules-config u2*
 # chmod 700 daq-modules-config u2*
 # ls -l

-rwx------ 1 snort snort     662  1月 12 11:53 2014 daq-modules-config
-rwxr-xr-x 1 root  root   113429  1月  7 16:52 2014 dbench
-rwxr-xr-x 1 root  root      741  1月 12 11:46 2014 dnet-config
-rwxr-xr-x 1 root  root  8570216  1月 12 11:57 2014 snort
-rwxr-xr-x 1 root  root   103080  1月  7 16:52 2014 tbench
-rwxr-xr-x 1 root  root    33692  1月  7 16:52 2014 tbench_srv
-rwx------ 1 snort snort   22698  1月 12 11:57 2014 u2boat
-rwx------ 1 snort snort   32645  1月 12 11:57 2014 u2spewfoo

1-4. 起動と動作確認

ここではSnortをIDSとして起動し、基本的な動作確認を行います。

・テストコマンドの実行
 # cd /usr/local/bin/
 # ./snort -T -i eth0 -u snort -g snort -c /etc/snort/snort.conf

標準出力のおわりのほうに’Snort successfully validated the configuration!’が表示されればOKです。

Running in Test mode

        --== Initializing Snort ==--
Initializing Output Plugins!
Initializing Preprocessors!
Initializing Plug-ins!
...
Snort successfully validated the configuration!
Snort exiting

・起動

Snortを起動します。

 # cd /usr/local/bin/
 # ./snort -A fast -b -d -D -i eth0 -u snort -g snort \
    -c /etc/snort/snort.conf -l /var/log/snort

Spawning daemon child...
My daemon child 17458 lives...
Daemon parent exiting (0)

※子プロセスとしてデーモンを起動して、このコマンド自体のプロセスは終了しています。

psコマンドでプロセスを確認します。

 # ps aux | grep snort

snort    17458  0.0  2.9 601252 237872 ?       Ssl  12:31   0:00 ./snort -A fast -b -d -D -i eth0 -u snort -g snort -c /etc/snort/snort.conf -l /var/log/snort

Snortの検知ログを確認します。
何も検知していないので、ファイルサイズは0となっています。

 # ls -l /var/log/snort/

-rw-r--r-- 1 root  root  0  1月 12 12:30 2014 alert
-rw------- 1 snort snort 0  1月 12 12:31 2014 snort.log.1389497466

・検知テスト

ローカルルール定義ファイルに、ICMPパケットを検知する、以下のルールを追記します。

 # vi /etc/snort/rules/local.rules

alert icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)

追加したルールを反映させるため、先ほど起動したSnortプロセスをいったんkillして再度起動します。

 # ps aux | grep snort // SnortプロセスのPIDを取得
 # kill <pid>
 # ps aux | grep snort // Snortプロセスが存在しないことを確認

 # ./snort -A fast -b -d -D -i eth0 -u snort -g snort \
    -c /etc/snort/snort.conf -l /var/log/snort

Spawning daemon child...
My daemon child 17479 lives...
Daemon parent exiting (0)

外部からIPSサーバーにpingを打ってみます。
また、サーバーから外部にpingを打ってみます。
検知ログ /var/log/snort/alert にメッセージが出力されていればOKです。

 # tail -f /var/log/snort/alert

01/12-12:34:02.194757  [**] [1:1000001:1] ICMP Testing Rule [**] [Priority: 0] {ICMP} <アクセス元IPアドレス> -> <IPSサーバーのグローバルIPアドレス>
01/12-12:34:02.218999  [**] [1:1000001:1] ICMP Testing Rule [**] [Priority: 0] {ICMP} <IPSサーバーのグローバルIPアドレス> -> <アクセス元IPアドレス>

/var/log/snort/snort.log. ファイルはpcap形式で書かれており、tcpdumpコマンドで参照できます。

 # tcpdump -r /var/log/snort/snort.log.<xxx>

1-5. 起動スクリプトの設置

Snort公式サイトより、CentOS 6用起動スクリプトをダウンロードします。

 # cd /usr/local/src/
 # wget http://s3.amazonaws.com/snort-org/www/assets/208/snort-centos-6x.sh
 # cp snort-centos-6x.sh /etc/init.d/snort
 # cp snort-centos-6x.sh /etc/sysconfig/snort

このファイルは、/etc/init.d/snort と /etc/sysconfig/snort が一緒になっているので、編集が必要です。

/etc/init.d/snort を編集します。

 # vi /etc/init.d/snort

1番目、2番目の —– CUT HERE —– の間のみ残して、ほかの行は削除します。

続いて、/etc/sysconfig/snort を編集します。

 # vi /etc/sysconfig/snort

3番目、4番目の —– CUT HERE —– の間のみ残して、ほかの行は削除します。

Owner, Permissionを設定します。

 # chown snort.snort /etc/init.d/snort /etc/sysconfig/snort
 # chmod 700 /etc/init.d/snort /etc/sysconfig/snort

/usr/local/bin/snort を /usr/sbin/snort で実行できるよう、シンボリックリンクを作成します。

 # cd /usr/sbin/
 # ln -s /usr/local/bin/snort

Snortが起動中であれば、killコマンドで停止したうえで、起動スクリプトで起動します。

 # ps aux | grep snort
 # kill <pid>

 # /etc/init.d/snort start

snort を起動中: Spawning daemon child...
My daemon child 17551 lives...
Daemon parent exiting (0)
                                                           [  OK  ]

起動時のログは /var/log/messages に出力されるので確認します。
また、起動スクリプトのstatusコマンドで状態を確認します。

 # less /var/log/messages

...
Jan 12 12:40:16 ips snort[17551]:         --== Initialization Complete ==--
Jan 12 12:40:16 ips snort[17551]: Commencing packet processing (pid=17551)


 # /etc/init.d/snort status

snort (pid  17551) を実行中...

ここまでで、Snortを利用したIDSの設定ができました。

2. PulledPorkによるルールファイルの自動更新設定

常に最新のSnortルールファイルを適用するため、PulledPorkというツールを使い、毎日Snortルールファイルを自動更新します。

(参考)

・準備

PulledPorkの動作に必要となる perl-Crypt-SSLeay, perl-libwww-perl, perl-Archive-Tar をインストールします。

 # yum install perl-Crypt-SSLeay perl-libwww-perl perl-Archive-Tar

Package perl-Crypt-SSLeay-0.57-16.el6.x86_64 already installed and latest version
Package perl-libwww-perl-5.833-2.el6.noarch already installed and latest version
Package perl-Archive-Tar-1.58-136.el6.x86_64 already installed and latest version
Nothing to do

・Pulledporkのインストール

/usr/local/pulledpork/ にインストールするものとします。

 # cd /usr/local/src/
 # wget https://pulledpork.googlecode.com/files/pulledpork-0.7.0.tar.gz
 # cd /usr/local/
 # tar zxvf /usr/local/src/pulledpork-0.7.0.tar.gz
 # mv pulledpork-0.7.0 pulledpork
 # chmod 755 /usr/local/pulledpork/pulledpork.pl

・PulledPork Configの編集
 # vi /usr/local/pulledpork/etc/pulledpork.conf

rule_url では、<oinkcode> の箇所に先ほど取得したOinkcodeを指定します。
また、ほかのrule_url 行はすべてコメントアウトします。
その他、各ファイルのパス設定など、このサーバー環境にあわせて下記のように書き換えます。

rule_url=https://www.snort.org/reg-rules/|snortrules-snapshot.tar.gz|<oinkcode>
# get the rule docs!
#rule_url=https://www.snort.org/reg-rules/|opensource.gz|<oinkcode>
#rule_url=https://rules.emergingthreats.net/|emerging.rules.tar.gz|open
# THE FOLLOWING URL is for etpro downloads, note the tarball name change!
# and the et oinkcode requirement!
#rule_url=https://rules.emergingthreats.net/|etpro.rules.tar.gz|<et oinkcode>
...
rule_path=/etc/snort/rules/snort.rules
...
local_rules=/etc/snort/rules/local.rules
...
sid_msg=/etc/snort/sid-msg.map
...
snort_path=/usr/local/bin/snort
...
config_path=/etc/snort/snort.conf
...
sostub_path=/etc/snort/rules/so_rules.rules
...
distro=RHEL-6-0
...
black_list=/etc/snort/rules/iplists/default.blacklist
...
IPRVersion=/etc/snort/rules/iplists
...
pid_path=/var/run/snort_eth0.pid
...
snort_version=2.9.5.5
--

※snort_version のところは、今回のSnortのバージョンは 2.9.5.6 ですが、ルールセットの Registered User Release は 2.9.5.5 なので、こちらを記述します。そうしないと、下記のように 2.9.5.6 のルールファイルを取りに行ってエラーします。

Checking latest MD5 for snortrules-snapshot-2956.tar.gz....
        A 403 error occurred, please wait for the 15 minute timeout
        to expire before trying again or specify the -n runtime switch
        You may also wish to verfiy your oinkcode, tarball name, and other configuration options
        Error 403 when fetching https://www.snort.org/reg-rules/snortrules-snapshot-2956.tar.gz.md5 at /usr/local/pulledpork/pulledpork.pl line 463
        main::md5file('<oinkcode>', 'snortrules-snapshot-2956.tar.gz', '/tmp/', 'https://www.snort.org/reg-rules/') called at /usr/local/pulledpork/pulledpork.pl line 1847

・snort.confの修正

$RULE_PATH (/etc/snort/rules) 配下の各ファイルのincludeを削除し、snort.rules, local.rules, so_rules.rules ファイルのみincludeするよう変更します。

これは、自動更新用のルールファイルは、すべてのルールが snort.rules の1ファイルにまとめられているためです。

 # cp -p /etc/snort/snort.conf /etc/snort/snort.conf.bak

 # sed -i '/^include $RULE_PATH/d' /etc/snort/snort.conf
 # echo "include \$RULE_PATH/snort.rules" >> /etc/snort/snort.conf
 # echo "include \$RULE_PATH/local.rules" >> /etc/snort/snort.conf
 # echo "include \$RULE_PATH/so_rules.rules" >> /etc/snort/snort.conf

/etc/snort/rules/so_rules.rules ファイル、/etc/snort/rules/snort.rules ファイルを生成します。
これらのファイルがないと、Snortが起動しません。

 # touch /etc/snort/rules/so_rules.rules /etc/snort/rules/snort.rules
 # chown snort.snort /etc/snort/rules/so_rules.rules /etc/snort/rules/snort.rules
 # chmod 700 /etc/snort/rules/so_rules.rules /etc/snort/rules/snort.rules

Snortを起動していれば、再起動します。

 # /etc/init.d/snort restart

※この時点でうまく起動できなければ、snort.confの記述に問題があるので、/var/log/messages に出力されたメッセージを参考にして対処します。

・Pulledporkの実行

Perlスクリプトを実行します。

 # /usr/local/pulledpork/pulledpork.pl \
    -c /usr/local/pulledpork/etc/pulledpork.conf

    http://code.google.com/p/pulledpork/
      _____ ____
     `----,\    )
      `--==\\  /    PulledPork v0.7.0 - Swine Flu!
       `--==\\/
     .-~~~~-.Y|\\_  Copyright (C) 2009-2013 JJ Cummings
  @_/        /  66\_  cummingsj@gmail.com
    |    \   \   _(")
     \   /-| ||'--'  Rules give me wings!
      \_\  \_\\
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Checking latest MD5 for snortrules-snapshot-2955.tar.gz....
Rules tarball download of snortrules-snapshot-2955.tar.gz....
        They Match
        Done!
Prepping rules from snortrules-snapshot-2955.tar.gz for work....
        Done!
Reading rules...
Generating Stub Rules....
        An error occurred: WARNING: ip4 normalizations disabled because not inline.

        An error occurred: WARNING: tcp normalizations disabled because not inline.

        An error occurred: WARNING: icmp4 normalizations disabled because not inline.

        An error occurred: WARNING: ip6 normalizations disabled because not inline.

        An error occurred: WARNING: icmp6 normalizations disabled because not inline.

        Done
Reading rules...
Reading rules...
Setting Flowbit State....
        Enabled 65 flowbits
        Done
Writing /etc/snort/rules/snort.rules....
        Done
Generating sid-msg.map....
        Done
Writing v1 /etc/snort/sid-msg.map....
        Done
Writing /var/log/sid_changes.log....
        Done
Rule Stats...
        New:-------19887
        Deleted:---0
        Enabled Rules:----4597
        Dropped Rules:----0
        Disabled Rules:---15290
        Total Rules:------19887
No IP Blacklist Changes

Done
Please review /var/log/sid_changes.log for additional details
Fly Piggy Fly!

ルールファイル/etc/snort/rules/snort.rules が更新されたことを確認します。

 # ls -l /etc/snort/rules/snort.rules

-rwx------ 1 snort snort 9218130  1月 12 12:58 2014 /etc/snort/rules/snort.rules

/etc/snort/rules/so_rules.rules のほうは更新されていないが、これで問題ないようです。

 # ls -l /etc/snort/rules/so_rules.rules

-rwx------ 1 snort snort 0  1月 12 12:56 2014 /etc/snort/rules/so_rules.rules

※同じOinkcodeによるルールファイルの取得は、15分以上の間隔を時間を空けないと403エラーとなるので注意してください。

・自動更新スクリプトの設置

ルールファイルを自動更新するスクリプトを設置します。

 # mkdir /root/bin/
 # vi /root/bin/update_snort_rule.sh

#!/bin/sh
#
LOGFILE=/var/log/snort/update_snort_rule.log
SNORT_RULEDIR=/etc/snort/rules
SNORT_RULEFILE=${SNORT_RULEDIR}/snort.rules

PULLEDPORKDIR=/usr/local/pulledpork
PULLEDPORKAPP=${PULLEDPORKDIR}/pulledpork.pl
PULLEDPORKCONF=${PULLEDPORKDIR}/etc/pulledpork.conf

export LANG=C

echo "`date` Update Snort Rule start" >> ${LOGFILE}

${PULLEDPORKAPP} -c ${PULLEDPORKCONF} >> ${LOGFILE}

chown snort.snort ${SNORT_RULEDIR}/*.rules
chmod 700 ${SNORT_RULEDIR}/*.rules

echo "`date` Update Snort Rule end" >> ${LOGFILE}

# EOF

 # chmod 700 /root/bin/update_snort_rule.sh

・cronに登録

自動更新スクリプトを毎日3時に実行するようcronに登録します。

 # crontab -e

0 3 * * * /root/bin/update_snort_rule.sh > /dev/null

テストとして時刻を3分後などに設定して、実際にcronでスクリプトを実行させてみます。
実行後はルールファイル自動更新処理のログを確認します。

 # less /var/log/snort/update_snort_rule.log

3. IPSとしての動作設定

いよいよ、SnortをIDSではなくIPSとして動作させるための設定を行います。

3-1. Snortとiptables

SnortをInlineモードで起動し、iptablesと組み合わせてパケットの検査、転送を行います。
iptablesとSnortのパケットの受け渡しのイメージを図2に示します。
(クリックすると大きく表示されます。)

図2. iptablesとSnortのパケットの受け渡し
図2. iptablesとSnortのパケットの受け渡し

iptablesのFORWARDチェーンでターゲットとしてNFQUEUE, number=2を指定し、Snort起動時のDAQオプションでも、NFQ, number=2を指定することで、検査対象のパケットをSnortに渡します。

IPSサーバーでは、HTTP(TCP/80), HTTPS(TCP/443)のパケットを検査し、ルールにマッチした不正なパケットを破棄し、ルールをパスした正常なパケットをWebサーバーに転送します。
WebサーバーへのSSHは、TCP/10022を使用し、パケットを検査しません。

3-2. Webサーバーの設定

Webサーバーでは、デフォルトゲートウェイとしてIPSサーバーを指定し、eth0のインタフェースを落とします。

※WebサーバーでデフォルトゲートウェイとしてIPSサーバーを指定しないと、IPSサーバーから転送されたパケットの戻りパケットが、IPSサーバーに向きません。

※IPSサーバーのiptablesで転送する際にSNATの設定を行うと、Webサーバー側でデフォルトゲートウェイの変更が不要となります。しかしそうすると、Webサーバー側に届くパケットのアクセス元IPアドレスがすべてIPSサーバーのアドレスとなってしまうため、セキュリティ的に好ましくありません。

グローバル側のネットワークインタフェースを落とすので、以下の操作は、IPSサーバーからWebサーバーにSSHログインして行います。

プライベート側のeth1インタフェース設定で、デフォルトゲートウェイとしてIPSサーバーのプライベートIPアドレスを記載します。

 # vi /etc/sysconfig/network-scripts/ifcfg-eth1

GATEWAY=192.168.0.10  // ★IPSサーバーのプライベートIPアドレス

※GATEWAY= 行は、ifcfg-ethx に記載がなければ、/etc/sysconfig/network に記載しても構いません。

OSやネットワークサービスの再起動時にeth0インタフェースが起動しないよう、インタフェース設定でONBOOTパラメーターをnoとして、ネットワークサービスを再起動します。

 # vi /etc/sysconfig/network-scripts/ifcfg-eth0

ONBOOT=no

 # /etc/init.d/network restart

ifconfigコマンドで、eth0インタフェースが起動していないことを確認します。

 # ifconfig

eth1      Link encap:Ethernet  HWaddr XX:XX:XX:XX:XX:XX
          inet addr:192.168.0.11  Bcast:192.168.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:7168549 errors:0 dropped:0 overruns:0 frame:0
          TX packets:7296415 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:3719156342 (3.4 GiB)  TX bytes:4775426165 (4.4 GiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:51411 errors:0 dropped:0 overruns:0 frame:0
          TX packets:51411 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:4894708 (4.6 MiB)  TX bytes:4894708 (4.6 MiB)

ルーティングテーブル情報を確認します。

 # netstat -rn

Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
192.168.0.0     0.0.0.0         255.255.255.0   U         0 0          0 eth1
169.254.0.0     0.0.0.0         255.255.0.0     U         0 0          0 eth1
0.0.0.0         192.168.0.10    0.0.0.0         UG        0 0          0 eth1

3-3. IPSサーバーの設定

・OSの設定変更

カーネルのip転送を有効にします。

 # sysctl -w net.ipv4.ip_forward=1

再起動後も有効にするための設定を行います。

 # vi /etc/sysctl.conf

#net.ipv4.ip_forward = 0
net.ipv4.ip_forward = 1

/etc/sysctl.confへの変更をすぐにサーバーに反映させます。

 # sysctl -p /etc/sysctl.conf

・iptablesの設定

グローバル側インタフェースeth0で、セッション確立後のアクセスはすべて許可します。

 # iptables -A INPUT -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
 # iptables -A OUTPUT -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

プライベート側インタフェースeth1のアクセスはすべて許可します。

 # iptables -A INPUT -i eth1 -s 192.168.0.0/24 -j ACCEPT
 # iptables -A OUTPUT -o eth1 -d 192.168.0.0/24 -j ACCEPT

HTTP(TCP/80), HTTPS(TCP/443)はWebサーバーに転送し、Snortで検査します。
FOWARDチェーンで、ターゲットとして、Snort Inlineモードの起動オプション
として設定した NFQUEUE, queue number=2 を指定するのがポイントです。

 # iptables -t nat -A PREROUTING -i eth0 -d <IPSサーバーのグローバルIPアドレス> \
    -p tcp --dport 80 -j DNAT --to 192.168.0.11:80
 # iptables -A FORWARD -p tcp --dport 80 -j NFQUEUE --queue-num 2

 # iptables -t nat -A PREROUTING -i eth0 -d <IPSサーバーのグローバルIPアドレス> \
    -p tcp --dport 443 -j DNAT --to 192.168.0.11:443
 # iptables -A FORWARD -p tcp --dport 443 -j NFQUEUE --queue-num 2

SSH(TCP/10022)はWebサーバーに転送し、Snortで検査しません。
FORWARDチェーンで、ターゲットをACCEPTとします。
(FORWARDチェーンのターゲットのデフォルトがACCEPTであれば、FORWARDチェーンのルールは不要です。)

 # iptables -t nat -A PREROUTING -i eth0 -d <IPSサーバーのグローバルIPアドレス> \
    -p tcp --dport 10022 -j DNAT --to 192.168.0.11:22
 # iptables -A FORWARD -p tcp --dport 22 -j ACCEPT

Webサーバーに転送後、セッションが確立されたあとのパケットを許可します。

 # iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

Webサーバーからインターネットへのアクセスを転送します。

 # iptables -t nat -A POSTROUTING -o eth0 -s 192.168.0.11 -j MASQUERADE
 # iptables -A FORWARD -i eth1 -j ACCEPT

・Snortの設定

IDSではなくIPS(Snort Inline)で動作させるための設定を行います。

 # vi /etc/snort/snort.conf

検査対象のIPアドレスを指定する HOME_NET にプライベート側のネットワークを含めます。
これは、iptablesのPREROUTINGチェーンで宛先IPアドレスを転送先WebサーバーのIPアドレスに変更したのち、FORWARDチェーンでSnort Inlineによる検査への転送を行っているからです。

また、DAQのモード指定などを変更します。

#ipvar HOME_NET <IPSサーバーのグローバルIPアドレス>/32
ipvar HOME_NET [<IPSサーバーのグローバルIPアドレス/32,192.168.0.0/24]
...
config daq: nfq
config daq_dir: /usr/local/lib/daq
config daq_mode: inline
config policy_mode: inline

いったんSnortを停止して、コマンドラインでInlineモードで起動してみます。

 # /etc/init.d/snort stop

 # snort -Q --daq nfq --daq-var queue=2 -c /etc/snort/snort.conf \
    -l /var/log/snort -A fast

Enabling inline operation
Running in IDS mode
...
Commencing packet processing (pid=27668)
Decoding Raw IP4

・テスト用Snortローカルルールの設定

テストのため、HTTPとHTTPSのパケットを検知するローカルルールを作成します。

 # vi /etc/snort/rules/local.rules

alert icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"HTTP/80 Testing Rule"; sid:1000021; rev:1;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 443 (msg:"HTTPS/443 Testing Rule"; sid:1000022; rev:1;)

3-4. 動作確認(アラートのみ)

WebサーバーへのHTTP, HTTPS, SSHアクセスと、Webサーバーからインターネットへのアクセスを確認します。

・外部からのWebアクセス

PCのWebブラウザで、IPSサーバーのグローバルIPアドレスを指定して、HTTP, HTTPSアクセスを行ってみます。
ブラウザにWebサーバーのコンテンツが正しく表示されればOKです。
また、Snortのログに書き出されたことを確認します。

 # tail -f /var/log/snort/alert

01/12-16:05:11.612815  [**] [1:1000021:1] HTTP/80 Testing Rule [**] [Priority: 0] {TCP} <アクセス元IPアドレス>:54682 -> 192.168.0.11:80
01/12-16:06:18.696955  [**] [1:1000022:1] HTTPS/443 Testing Rule [**] [Priority: 0] {TCP} <アクセス元IPアドレス>:49625  -> 192.168.0.11:443

Webサーバー側でtcpdumpでパケットが正しく転送されていることを確認します。
SrcIPアドレスは、IPSサーバーのものではなく、アクセス元IPアドレスであることを確認します。

 (Webサーバー)
 # tcpdump -i eth1 port 80

16:14:22.694695 IP <アクセス元IPアドレス>.54682 > 192.168.0.11.http: Flags [.], ack 3408, win 819, options [nop,nop,TS val 1542530040 ecr 427884550], length 0
16:14:23.763152 IP <アクセス元IPアドレス>.54682 > 192.168.0.11.http: Flags [R.], seq 732, ack 3408, win 819, options [nop,nop,TS val 1542531109 ecr 427884550], length 0

また、WebサーバーのApache等のアクセスログで、アクセス元IPアドレスが記録されていることを確認します。

 # less /var/log/httpd/access_log

・外部からのSSHアクセス

PCでTeraterm等のソフトやSSHコマンドを使用して、IPSサーバーのグローバルIPアドレスのTCP/10022を指定して、WebサーバーにSSHログインできることを確認します。

・Webサーバーからインターネットへのアクセス

Webサーバー上で(IPSサーバーを経由して)インターネットにアクセスできることを確認します。
例えば、elinks, lynxなどのテキストブラウザツールを使用して、Yahoo! Japanにアクセスしてみます。

 (Webサーバー)
 # lynx http://www.yahoo.co.jp/

3-5. 動作確認(パケット破棄)

ローカルルールファイルで、alertアクション(ログに書き出すのみ)ではなく、dropアクション(ログに書き出しパケットを破棄する)ルールを記述します。
先ほど記述した ‘alert’ の部分を ‘drop’ に変更します。

 # vi /etc/snort/rules/local.rules

#alert icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)
#alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"HTTP/80 Testing Rule"; sid:1000021; rev:1;)
#alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 443 (msg:"HTTPS/443 Testing Rule"; sid:1000022; rev:1;)drop icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)
drop icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)
drop tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"HTTP/80 Testing Rule"; sid:1000021; rev:1;)
drop tcp $EXTERNAL_NET any -> $HTTP_SERVERS 443 (msg:"HTTPS/443 Testing Rule"; sid:1000022; rev:1;)

設定を反映させるため、Ctrl-CでSnortを停止し、再び起動します。

 # snort -Q --daq nfq --daq-var queue=2 -c /etc/snort/snort.conf \
    -l /var/log/snort -A fast

PCのWebブラウザで、IPSサーバーのグローバルIPアドレスを指定して、HTTP, HTTPSアクセスを行ってみます。
また、PCからpingを打ってみます。
Webコンテンツが表示されず、接続タイムアウトとなればOKです。

Snortのアラートログを確認します。
破棄したパケットについては [Drop] と記録されています。

 # less /var/log/snort/alert

01/12-16:21:49.962534  [Drop] [**] [1:1000021:1] HTTP/80 Testing Rule [**] [Priority: 0] {TCP} <アクセス元IPアドレス>:54716 -> 192.168.0.11:80
01/12-16:21:54.430659  [Drop] [**] [1:1000022:1] HTTPS/443 Testing Rule [**] [Priority: 0] {TCP} <アクセス元IPアドレス>:49703 -> 192.168.0.11:443

これで、SnortによるIPSでパケット検査を行いつつ、WebアクセスをWebサーバーに転送する動作が確認できました。

確認が終わったので、追加したローカルルール行をすべてコメントアウト(または削除)します。

 # vi /etc/snort/rules/local.rules

#alert icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)
#alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"HTTP/80 Testing Rule"; sid:1000021; rev:1;)
#alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 443 (msg:"HTTPS/443 Testing Rule"; sid:1000022; rev:1;)drop icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)
#drop icmp any any -> any any (msg:"ICMP Testing Rule"; sid:1000001; rev:1;)
#drop tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"HTTP/80 Testing Rule"; sid:1000021; rev:1;)
#drop tcp $EXTERNAL_NET any -> $HTTP_SERVERS 443 (msg:"HTTPS/443 Testing Rule"; sid:1000022; rev:1;)

設定を反映させるため、Ctrl-CでSnortを停止し、再び起動します。

 # snort -Q --daq nfq --daq-var queue=2 -c /etc/snort/snort.conf \
    -l /var/log/snort -A fast

PCのWebブラウザで、IPSサーバーのグローバルIPアドレスを指定して、HTTP, HTTPSアクセスを行い、正しくコンテンツが表示されることを確認します。

3-6. 運用に向けた設定

・iptablesの設定

iptablesの設定を保存し、iptablesサービスやOSを再起動しても設定がクリアされないようにします。

 # /etc/init.d/iptables save

/etc/sysconfig/iptable に保存されます。

・Snort起動スクリプトの変更
 # vi /etc/init.d/snort

NFQモードで起動する際は $INTERFACE の設定は不要なので、DAQのパラメータを指定した DAQMODE という変数を追加し、$INTERFACE 変数と入れ替えます。

DAQMODE="-Q --daq nfq --daq-var queue=2"
...
#    daemon --pidfile=$PID_FILE $SNORTD $ALERTMODE $BINARY_LOG $LINK_LAYER $NO_PACKET_LOG $DUMP_APP -D $PRINT_INTERFACE $INTERFACE -u $USER -g $GROUP $CONF -l $LOGDIR $PASS_FIRST $BPFFILE $BPF && success || failure
    daemon --pidfile=$PID_FILE $SNORTD $ALERTMODE $BINARY_LOG $LINK_LAYER $NO_PACKET_LOG $DUMP_APP -D $PRINT_INTERFACE $DAQMODE -u $USER -g $GROUP $CONF -l $LOGDIR $PASS_FIRST $BPFFILE $BPF && success || failure
--

次に、PIDファイル名を明示的に指定します。
NFQモードでSnortを起動するときはネットワークインタフェースを指定しないため、このPIDファイルは /var/run/snort_.pid というファイル名で生成されてしまいます。
起動スクリプトではこの部分がうまく解決できないため、PID_FILEのパスを明示的に指定する必要があります。
(この設定を行わないと stop コマンドで正しく停止できません。)

-- 変更前
if [ "$INTERFACE"X = "X" ]; then
   HW_INTF="eth0"
   INTERFACE="-i eth0"
   PID_FILE="/var/run/snort_eth0.pid"
else
   HW_INTF=$INTERFACE
   PID_FILE="/var/run/snort_$INTERFACE.pid"
   INTERFACE="-i $INTERFACE"
fi
-- 

-- 変更後
if [ "$INTERFACE"X = "X" ]; then
   HW_INTF="eth0"
   INTERFACE="-i eth0"
   PID_FILE="/var/run/snort_eth0.pid"
else
   HW_INTF=$INTERFACE
   PID_FILE="/var/run/snort_$INTERFACE.pid"
   INTERFACE="-i $INTERFACE"
fi

PID_FILE=/var/run/snort_.pid  // ★この行を追加
--

Ctr-CでSnortを停止して、スクリプトで起動します。

 # /etc/init.d/snort start

snort を起動中: Spawning daemon child...
My daemon child 18951 lives...
Daemon parent exiting (0)
                                                           [  OK  ]

・ルールファイルのアクション alert -> drop への変更

Snort公式サイトで配布されているルールファイルのままでは、不正アクセスを検知しても、アクションが alert のためパケットは破棄されずログに書き出されるだけです。
これを drop アクションに変更します。
ルールファイルを自動更新するスクリプトを編集します。

 # vi /root/bin/update_snort_rule.sh

sedコマンドで行頭の alert を drop に置換し、Snortを再起動してルールを反映させます。

-- 変更前
${PULLEDPORKAPP} -c ${PULLEDPORKCONF} >> ${LOGFILE}

chown snort.snort ${SNORT_RULEDIR}/*.rules
chmod 700 ${SNORT_RULEDIR}/*.rules
--

-- 変更後
${PULLEDPORKAPP} -c ${PULLEDPORKCONF} >> ${LOGFILE}

chown snort.snort ${SNORT_RULEDIR}/*.rules
chmod 700 ${SNORT_RULEDIR}/*.rules

/bin/sed -i 's/^alert /drop /' ${SNORT_RULEFILE} >> ${LOGFILE}

/etc/init.d/snort restart >> ${LOGFILE}
--

ルールファイルの自動更新を実行します。

 # /root/bin/update_snort_rule.sh

ルールファイルのルール行で、アクションが alert ではなく drop となっていることを確認します。

 # less /etc/snort/rules/snort.rules

drop ip any any -> any any (msg:"BAD-TRAFFIC Windows remote kernel tcp/ip igmp vulnerability exploit attempt"; sid:13287; gid:3; rev:6; classtype:attempted-admin; reference:cve,2007-0069; reference:url,technet.microsoft.com/en-us/security/bulletin/MS08-001; metadata: engine shared, soid 3|13287, policy security-ips drop;)
...

・動作確認

再度、ひととおりの動作確認を行います。

  • PCのブラウザからHTTP, HTTPSでアクセス
  • PCのTeratermなどからTCP/10022を指定して、SSHログイン
  • Webサーバーからインターネットにアクセス
・ログローテート設定

Snortのソースセットにある snort-2.9.5.6/rpm/snort.logrotate を参考にします。
アラートログを毎週ローテート、105世代(=2年分)gzip圧縮して保存する例です。

 # vi /etc/logrotate.d/snort

/var/log/snort/alert /var/log/snort/update_snort_rule.log {
    weekly
    rotate 105
    missingok
    compress
    sharedscripts
    postrotate
        /etc/init.d/snort restart 1>/dev/null || true
    endscript
}

・Snortの自動起動設定
 # chkconfig snort on

 # chkconfig --list snort

snort           0:off   1:off   2:on    3:on    4:on    5:on    6:off

4. Swatchによるアラート通知設定

運用環境においては、Snortが不正アクセスを検知して防御したことを何らかの方法で管理者に通知する必要があるでしょう。

現在、Snort公式サイトには適切なアラートツールがないようなので、ログ監視ツールSwatchを利用します。

※以前は snort_stat.pl というツールがあったようですが、現在は配布されていないようです。(参考)Snortによる不正侵入行為の検知結果の通知方法 (1)

Snortのアラートログ /var/log/snort/alert でパケット破棄を意味する ‘[Drop]’ が出力されたときにアラートメールを送信する設定を行います。

 (参考)

・Swatchのインストール

EPELリポジトリよりSwatchをインストールします。

※EPELリポジトリの追加手順については、下記などを参考にしてください。
(参考)
 CentOS 外部レポジトリの追加(EPEL)

 # yum --enablerepo=epel install swatch

Installed:
  swatch.noarch 0:3.2.3-7.el6

Dependency Installed:
  perl-Bit-Vector.x86_64 0:7.1-2.el6      perl-Carp-Clan.noarch 0:6.03-2.el6
  perl-Date-Calc.noarch 0:6.3-2.el6       perl-Date-Manip.noarch 0:6.24-1.el6
  perl-TimeDate.noarch 1:1.16-11.1.el6    perl-YAML-Syck.x86_64 0:1.07-4.el6

perl-File-Tail が必要なのにインストールされなかったので、明示的に指定してインストールします。

 # yum --enablerepo=epel install perl-File-Tail

Installed:
  perl-File-Tail.noarch 0:0.99.3-8.el6

・SwatchのSnortログ監視ルールファイルを作成
 # mkdir /etc/swatch

 # vi /etc/swatch/snort.conf

throttle で、同じメッセージを検出しても無視する時間(ここでは15秒)を指定します。
また、key=regex を指定して、メッセージが同じ正規表現の場合は無視するよう設定します。

※key=regex を指定しないと、1パケットの検知につき1通の大量のメールが届いてしまいます。また、インターネット上の情報では、 use=regex と記載されているものが多いのですが、今回インストールしたSwatchでは、key=regex としないと意図した動作をしませんでした。)

# logfile /var/log/snort/alert

# [Drop] を検知したらアラートメールを送信する
watchfor /\[Drop\]/
    mail addresses=aaa\@example.com:bbb\@example.com,subject="[IPS Drop Log]"
    throttle=00:00:15,key=regex

・Swatch起動スクリプトの作成
 # vi /etc/init.d/swatch

Swatchの起動オプションで、–tail-args= で、tailコマンドでログをWatchする際のオプションを指定します。
–follow=name を指定することで、ログがローテートされても追従できます。

#!/bin/bash
#
# swatch
#
# chkconfig: 2345 90 35
# description: swatch start/stop script

# Source function library.
. /etc/rc.d/init.d/functions

PATH=/sbin:/usr/local/bin:/bin:/usr/bin

mkdir -p /var/log/swatch

start() {
    # Start daemons.
    ls /var/run/swatch_*.pid > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo -n "Starting swatch"
        pno=0
        for conf in /etc/swatch/*.conf
        do
            pno=`expr $pno + 1`
            WATCHLOG=`grep "^# logfile" $conf | awk '{ print $3 }'`
            swatch --config-file $conf --tail-file $WATCHLOG \
            --script-dir=/tmp --awk-field-syntax --use-cpan-file-tail --daemon \
            --tail-args='--follow=name --retry -n 0' \
            --pid-file /var/run/swatch_$pno.pid \
            >> /var/log/swatch/swatch.log 2>&1
            RETVAL=$?
            [ $RETVAL != 0 ] && return $RETVAL
        done
        echo
        [ $RETVAL = 0 ] && touch /var/lock/subsys/swatch
        return $RETVAL
    else
        echo "swatch is already started"
    fi
}

stop() {
    # Stop daemons.
    ls /var/run/swatch_*.pid > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo -n "Shutting down swatch"
        for pid in /var/run/swatch_*.pid
        do
           kill $(cat $pid)
           rm -f $pid
        done
        echo
        rm -f /var/lock/subsys/swatch /tmp/.swatch_script.*
    else
        echo "swatch is not running"
    fi
}

status() {
    ls /var/run/swatch_*.pid > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo -n "swatch (pid"
        for pid in /var/run/swatch_*.pid
        do
           echo -n " `cat $pid`"
        done
        echo ") is running..."
    else
        echo "swatch is stopped"
    fi
}

case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart)
        stop
        start
        ;;
  status)
        status
        ;;
   *)
        echo "Usage: swatch {start|stop|restart|status}"
        exit 1
esac

exit $RETVAL

 # chmod 755 /etc/init.d/swatch

・Swatchを起動
 # /etc/init.d/swatch start

 # ps aux | grep swatch

root      1540  0.0  1.3 156372 14064 ?        Ss   17:17   0:00 /usr/bin/swatch --config-file /etc/swatch/snort.conf --tail-file /var/log/snort/alert --script-dir=/tmp --awk-field-syntax --use-cpan-file-tail --daemon --tail-args=--follow=name --retry -n 0 --pid-file /var/run/swatch_1.pid

Swatchプロセスが存在しない場合は、Swatch自身のログを参照して対処します。

 # less /var/log/swatch/swatch.log

・動作確認

/var/log/snort/alert ログに書き出すような操作をしてみます。
一時的にテスト用ローカルルールを有効化するとよいでしょう。

 # vi /etc/snort/rules/local.rules

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"HTTP/80 Testing Rule"; sid:1000021; rev:1;)

 # /etc/init.d/snort restart

HTTPへのアクセスで、正しくアラートメールが送信されたことを確認します。
メールのSubjectと本文を確認します。

-- メール本文
01/12-18:32:18.320219  [Drop] [**] [1:1000021:1] HTTP/80 Testing Rule [**] [Priority: 0] {TCP} <アクセス元IPアドレス>:54763 -> 192.168.0.11:80 (seen 22 times)
--

(seen 22 times) は「ログで同じメッセージを22行連続で検知した」という意味です。

・Swatchのログローテート設定
 # vi /etc/logrotate.d/swatch

毎週ローテート、105世代(=2年分)gzip圧縮して保存する例です。

/var/log/swatch/swatch.log {
    weekly
    rotate 105
    missingok
    compress
    sharedscripts
    postrotate
        /etc/init.d/swatch restart 1> /dev/null || true
    endscript
}

・Swatchの自動起動設定
 # chkconfig swatch on

 # chkconfig --list swatch

swatch          0:off   1:off   2:on    3:on    4:on    5:on    6:off

isdフォールスポジティブ(誤検知)への対処

Snortのデフォルト設定や公式サイトが配布するルールファイルをそのまま使うと、正常なアクセスが不正なアクセスとみなされ、ブロックされてしまうことがあります。
そのため、Snortでパケット検査する対象のプロトコルに関わるあらゆる機能で十分動作確認を行うべきでしょう。

今回構築したWebサイトで動作を確認した際に発生した誤検知と、その対処方法を記載します。

1. ‘Reset outside window’ のアラート
2. ‘TCP small segments exceeding threshold’ のアラート
3. その他、ルールファイルのルールにマッチする場合
4. 一時的にIPS機能を無効とする

1. ‘Reset outside window’ のアラート

ユーザーがHTTPアクセスするたびに、下記のような、’Reset outside window’を検知しました。
ユーザー側のブラウザ表示は問題ないのですが、Swatchが大量のアラートメールを送信してしまいます。

01/17-13:55:52.765127  [Drop] [**] [129:15:1] Reset outside window [**] 
[Classification: Potentially Bad Traffic] [Priority: 2] 
{TCP} <アクセス元IPアドレス>:63207 -> 192.168.0.11:80 (seen 1 times)

「TCP spoofed reset denial of service」?あたりの攻撃の防御ルールだと思われますが、アラートが多すぎて運用に支障があるので、’Reset outside window’ のチェックを無効にしました。

 # vi /etc/snort/threshold.conf

(参考)https://groups.google.com/forum/#!topic/security-onion/VTymExIcA70

# Suppress this event completely:
#
# suppress gen_id 1, sig_id 1852
#
# Suppress this event from this IP:
#
# suppress gen_id 1, sig_id 1852, track by_src, ip 10.1.1.54
#
# Suppress this event to this CIDR block:
#
# suppress gen_id 1, sig_id 1852, track by_dst, ip 10.1.1.0/24
#
suppress gen_id 129, sig_id 15  // ★この1行を追記

なお、gen_uid と sig_id の組み合わせは、マップファイルに記載されています。

 # view /etc/snort/gen-msg.map

129 || 15 || stream5: Reset outside window

2. ‘TCP small segments exceeding threshold’ のアラート

ユーザーがHTTPでファイルをアップロードすると途中で止まってしまう、という現象がありました。
Snortでは、下記のように ‘TCP small segments exceeding threshold’ を検知していました。

01/17-13:41:54.830210  [Drop] [**] [129:12:1] 
Consecutive TCP small segments exceeding threshold 
[**] [Classification: Potentially Bad Traffic] [Priority: 2] 
{TCP} <アクセス元IPアドレス>:61779 -> 192.168.0.11:80 (seen 1 times)

stream5_tcpの設定で、小さいデータパケット通信時のチェックを無効としました。

(参考)

 # vi /etd/snort/snort.conf

small_segments の値を 3 から 0 に変更。

-- 変更前
preprocessor stream5_tcp: policy windows, detect_anomalies, require_3whs 180, \
   overlap_limit 10, small_segments 3 bytes 150, timeout 180, \
--

-- 変更後
preprocessor stream5_tcp: policy windows, detect_anomalies, require_3whs 180, \
   overlap_limit 10, small_segments 0 bytes 150, timeout 180, \
--

3. その他、ルールファイルのルールにマッチする場合

以下は実際には誤検知は発生していませんが、運用を続けるうちに、正常なアクセスなのにルールファイルにマッチして不正アクセスとみなされてしまう場合の、対処例を記載します。

まず、アラートログのメッセージを確認します。

 # less /var/log/snort/alert

例えば、’EXPLOIT WinHTTP SSL/TLS impersonation attempt’ というメッセージが出力されていたとします。

Snortルールファイルをviエディタで開いて、該当のメッセージが記載されている行を検索します。

 # vi /etc/snort/rules/snort.rules

以下の行が見つかりました。(以下のルールは実際には1行で記載されています。)

drop tcp $EXTERNAL_NET 443 -> $HOME_NET any 
(msg:"EXPLOIT WinHTTP SSL/TLS impersonation attempt"; sid:15456; gid:3; 
rev:2; classtype:misc-attack; reference:cve,2009-0089; reference:url,
technet.microsoft.com/en-us/security/bulletin/MS09-013; metadata: 
engine shared, soid 3|15456, service ssl, policy balanced-ips drop, 
policy security-ips drop;)

この行をコメントアウトします。

#drop tcp $EXTERNAL_NET 443 -> $HOME_NET any 
(msg:"EXPLOIT WinHTTP SSL/TLS impersonation attempt"; sid:15456; gid:3; 
rev:2; classtype:misc-attack; reference:cve,2009-0089; reference:url,
technet.microsoft.com/en-us/security/bulletin/MS09-013; metadata: 
engine shared, soid 3|15456, service ssl, policy balanced-ips drop, 
policy security-ips drop;)

ただし、このままでは、次回のルールファイル自動更新の際にコメントアウトが外れて元に戻ってしまうので、ルールファイル自動更新スクリプトで、該当行をコメントアウトするsedコマンドを追記します。

 # vi /root/bin/update_snort_rule.sh

ルールファイルの該当ルール行に、’sid:15456; gid:3; rev:2;’ のように、sid, gid, rev のIDが記載されているので、これらのIDをsedコマンドのパターンマッチングの条件とします。
自動更新スクリプトで、マッチした行の drop を #drop に書き換えるsedコマンドを追記します。
追記する箇所は、alert を drop に置換した後、Snortプロセスを再起動する前です。

-- 変更前
${PULLEDPORKAPP} -c ${PULLEDPORKCONF} >> ${LOGFILE}

chown snort.snort ${SNORT_RULEDIR}/*.rules
chmod 700 ${SNORT_RULEDIR}/*.rules

/bin/sed -i 's/^alert /drop /' ${SNORT_RULEFILE} >> ${LOGFILE}

/etc/init.d/snort restart >> ${LOGFILE}
--

-- 変更後
${PULLEDPORKAPP} -c ${PULLEDPORKCONF} >> ${LOGFILE}

chown snort.snort ${SNORT_RULEDIR}/*.rules
chmod 700 ${SNORT_RULEDIR}/*.rules

/bin/sed -i 's/^alert /drop /' ${SNORT_RULEFILE} >> ${LOGFILE}

### ★特定のルールを無効にする★
/bin/sed -i '/sid:15456; gid:3; rev;2/s/^drop /#drop /' \
${SNORT_RULEFILE} >> ${LOGFILE}

/etc/init.d/snort restart >> ${LOGFILE}
--

ルール定義の変更を反映させるため、Snortを再起動します。

 # /etc/init.d/snort restart

アプリケーションの動作確認を行います。

4. 一時的にIPS機能を無効とする

運用において、誤検知を調査して適切な設定変更を行う前に、緊急対処として、一時的にIPS機能を無効にしてしまいたいケースもあるでしょう。

Snortによるパケット検査を行わずに、HTTPやHTTPSアクセスをすべてそのままWebサーバーに転送するには、iptablesのルールを変更します。
これは、上記で定義したiptablesルールでいうと、FOWARDチェーンのターゲットを、NFQUEUE, queue number=2 から ACCEPTに変更すればよいです。

以下のようなルールを、、、

 # iptables -A FORWARD -p tcp --dport 80 -j NFQUEUE --queue-num 2
 # iptables -A FORWARD -p tcp --dport 443 -j NFQUEUE --queue-num 2

次のようなルールに変更します。

 # iptables -A FORWARD -p tcp --dport 80 -j ACCESS
 # iptables -A FORWARD -p tcp --dport 443 -j ACCESS

iptablesのルール変更はいろいろな方法があると思いますが、ここでは簡単にCentOSにおけるiptablesの設定ファイル /etc/sysconfig/iptables を直接編集します。

 # vi /etc/sysconfig/iptables

-- 変更前
-A FORWARD -p tcp -m tcp --dport 80 -j NFQUEUE --queue-num 2
-A FORWARD -p tcp -m tcp --dport 443 -j NFQUEUE --queue-num 2
--

-- 変更後
-A FORWARD -p tcp -m tcp --dport 80 -j ACCEPT
-A FORWARD -p tcp -m tcp --dport 443 -j ACCEPT
--

iptablesサービスを再起動して、設定変更を反映させます。

 # /etc/init.d/iptables restart

isd補足

・SSL通信のパケット検査について

HTTPSなど、SSLの通信の場合にSnortがどのような検査を行うかについては、ソースセットに含まれるREADME.sslに記載があります。
また、同じファイルが以下で参照できます。
http://svn.dd-wrt.com/browser/src/router/snort/doc/README.ssl?rev=17490

'By enabling the SSLPP to inspect port 443, only the SSL handshake of 
 each connection will be inspected.  Once the traffic is determined 
 to be encrypted, no further inspection of the data 
 on the connection is made.'

「SSLのハンドシェイクのみ検査し、暗号化通信が確立されたあとのデータは検査しない」ということだそうです。

なお、SSL通信の検査を行うポートは snort.conf の下記で定義されているので、特殊なポートを使用する場合は、ここでポート番号を追記する必要があります。

 # vi /etc/snort/snort.conf

# SSL anomaly detection and traffic bypass.  For more information, see README.ssl
preprocessor ssl: ports { 443 465 563 636 989 992 993 994 995 7801 7802 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 7916 7917 7918 7919 7920 }, trustservers, noinspect_encrypted

isdまとめ

Snortを利用したIPSの構築手順をまとめました。
IPSやUTMのハードウェア装置は高価でしょうし、最新のルール定義を入手するためのランニングコストもかかるでしょう。
コスト削減のため、オープンソースで実現するのもひとつの選択肢としてアリだと思います。

ただし、本番環境で運用する場合は、事前に十分なWebアプリケーションの動作確認と、稼働中も継続的にSnortルールのチューニングが必要だと思いますので、その分のコストも検討されるとよいでしょう。

※正直に言いまして、僕はIPSやUTMのハードウェア装置やSnort以外のIDS/IPSソフトウェアに触れたことがないのでほかのIPSとの比較はできません。すみません。

isd参考

構築にあたり、参考にした文書、サイトをまとめます。

とても長くなりましたが、以上です。

(関連記事)
・さくらのクラウドでSiteGuard Lite(WAF)を試してみて気づいたこと
https://inaba-serverdesign.jp/blog/20161004/waf_siteguard-lite_sakura.html