いろいろ書いていく

やってみたなど

Dockerの仮想ネットワーク内でOSSのIDS(suricata)を試す

お題

ふとsuricataというOSSのIDSソフトを試してみたくなり,Dockerの仮想ネットワークの範囲で出来そうだったので,簡単なサーバ+クライアント構成を構築してみた.
suricataはコンフィグ次第でIPSモードなど色々な機能を実現できるが,今回はHTTPサーバの前段に設置して攻撃アクセスを検知する最低限の機能を再現する.
ネットで探すとsuricataのコンテナイメージやDockerfileも出てくるが,今回はインストールするところからやりたいというのもあり,あえてそれらは使用せず,公式Ubuntuイメージにインストールすることにした.

suricataとは

suricataはLinux/Mac/FreeBSD/UNIX/Windowsなど様々なプラットフォームにインストールでき,セキュリティの分野ではsnortと並んで有名なIDS/IPSソフトである(多分).

suricata-ids.org

注意

  • 後半に攻撃コマンドが含まれるが,自分の所轄外の外部ネットワークなどに対し実行してはいけない.
  • 今回の構成でセキュリティを担保できるわけではないので,この設定だけで公開サーバを立てたりしてはいけない.あくまで自己責任で.

環境

Docker 18.09.1 on Ubuntu 18.04 (Oracle VirtualBox VM on macOS)
UbuntuでのDockerインストール方法はこちら↓
koimedenshi.hatenablog.com

構成

以下のコンテナおよびネットワークを構築し,簡単なサーバ(IDS有)+クライアント構成を組み立てる.
* suricataがインストールされた被害者役サーバコンテナ
* 攻撃役端末コンテナ
* 上記のコンテナを接続する,仮想ブリッジネットワーク

手順

仮想ネットワーク作成

まず,コンテナを接続するためのユーザ定義ブリッジネットワークisolated_nwを作成する.
デフォルトのブリッジネットワークに接続してもよいが,なんとなく分けた方が管理しやすいかと思ったので.

docker network create --driver bridge isolated_nw

攻撃用端末の構築

次に攻撃役端末コンテナを構築する.
ベースイメージはcurlなどのコマンドを使えればなんでもよいが,ここではdebianstable-slimイメージを引用する.
-ditオプションでインタラクティブなシェルを立ち上げる. また--networkオプションで作成したisolated_nwに接続する.

docker container run -dit --name debian-slim --network isolated_nw debian:stable-slim

作成したコンテナのbashに接続し,疎通確認やHTTPアクセスで使用するコマンドをインストールする

docker exec -it debian-slim bash
#/ apt-get update
#/ apt-get install curl wget iproute2 iputils-ping

ipコマンドをインストールしたので,サーバのifの名前とipアドレスを確認する.(後で使う)

/# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever

この例では,eth0に172.18.0.3というIPが割り当てられている.

被害者端末HTTPサーバ(NGINX+suricata)の構築

 続いて被害者役のサーバコンテナを構築する.
-dit--net=オプション指定は攻撃者役コンテナと同様だが,新たに--cap-add=NET_ADMINというオプションを指定する.
--cap-add=NET_ADMINオプションはコンテナにネットワーク周りのroot権限を付与するオプションで(雑な説明かも),これを入れないと後ほど出てくるsuricataの起動時に権限がないと怒られる.
部分的な権限付与とはいえコンテナにホスト端末の設定変更を許可することになるので,不特定多数が触るような環境では注意する.
ベースイメージはubuntu:18.04を指定する.これもdebianなどでもいけるかもしれないが,suricataの対応バージョンが異なってくるかもしれない.
ubuntu:18.04はsuricataの最新バージョン4.xに対応していた.

docker container run -dit --name ubuntu-suricata --cap-add=NET_ADMIN --net=isolated_nw ubuntu:18.04

コンテナが起動したらbashに接続し,必要なパッケージをインストールする.
software-properties-commonについてはadd-apt-repositoryというaptレポジトリ追加のためのコマンドを後で実行するので追加している.
サイズが大きいし,もう少しスマートな方法があるかもしれない.
suricataで保護するHTTPサーバとして(これも立ち上がればなんでも良いが)NGINXをインストールしておく.

docker exec -it ubuntu-suricata bash # コンテナのbashに接続
/# apt-get update
/# apt-get install -y iproute2 iputils-ping software-properties-common vim curl  tail jq nginx

ipコマンドをインストールしたので,サーバのifの名前とipアドレスを確認する.(後で使う)

/# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever

この例では,eth0に172.18.0.2というIPが割り当てられている.

その後,suricataをバイナリパッケージからインストールする.
suricataに開発オプションを指定する場合はソースからインストールする必要があるが,今回はとりあえず動けばいいのでバイナリパッケージを使う.
add-apt-repositoryコマンドでsuricataの安定板のppaを追加し,apt-getでインストールする.

/# add-apt-repository ppa:oisf/suricata-stable 
(確認メッセージが出るのでENTERを押す)
/# apt-get update
/# apt-get install -y suricata

以下のエラーが出るが,suricataが自動的に起動されなかったため?
とりあえず無視してもたぶん動く.

invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.

 インストールが完了したら,suricataの初期設定を行う. まずsuricataのルール(検知パターン.シグネチャ的なもの)をダウンロードする.
これも方法は色々あるが,公式ドキュメントにはsuricata-updateコマンドを使用する手順が記載されていたので,そちらを踏襲する.
suricata-updateはその名の通りsuricataルールをアップデートするコマンドで,suricataのインストールと同時にインストールされる(というのはバージョン4.1以降の話で,それ以前のバージョンでは手動でインストールする必要がある).

/# suricata-update

これで/var/lib/suricata/rules/にルールが保存されるはず.

次にsuricataの初期コンフィグを行う.
suricataの設定変更は/etc/suricata/suricata.yamlを編集する.
色々と細かい設定ができるが,ここでは何かしらの攻撃アクセスを検知できれば良いので,最低限の設定として検査対象のIPアドレスを指定する.
検査対象のIPアドレスはsuricata.yamlHOME_NETおよびEXTERNAL_NETにより制限できる.
suricataはHOME_NETに指定されたIPを保護するネットワークとして認識し,EXTERNAL_NETを外部ネットワークとして認識する.
一般的にはHOME_NETに保護するサーバの属するネットワーク(プライベートIP)を指定し,EXTERNAL_NETにはそれ以外(!$HOME_NET)を設定するものと思われるが,今回は攻撃者が被害者と同じネットワークに属しているため,サーバのIPと攻撃者端末のIPを単体で指定する.

まずHOME_NETubuntu-suricataのIPアドレス(ここでは172.18.0.3)を指定する.
suricata.yamlの以下の行をコメントアウトする.

    HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"

そして以下のようにIPを指定する.

    #HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"
    HOME_NET: "[172.18.0.3/32]"

次にEXTERNAL_NETdebian-slimのIPアドレス(ここでは172.18.0.2)を指定する.
以下の行をコメントアウトして

    EXTERNAL_NET: "!$HOME_NET"

以下のように新たに指定して保存する.

    #EXTERNAL_NET: "!$HOME_NET"
    EXTERNAL_NET: "[172.18.0.2/32]"

設定を変更したら,以下のコマンドでコンフィグ確認を行う.

#/ suricata -T

エラーが出たら適宜修正する.
先に行ったルールのダウンロードがされていない場合,ここでエラーが出る.

終わったら,以下のコマンドでsuricataを実行する.
-cオプションでコンフィグファイル(先ほど編集したsuricata.yaml)を指定し,-iオプションで検査するif(ここではeth0)を指定する.

#/ suricata -c /etc/suricata/suricata.yaml -i eth0 --init-errors-fatal
X/X/2019 -- 13:27:14 - <Notice> - This is Suricata version 4.1.2 RELEASE
X/X/2019 -- 13:27:31 - <Notice> - all 1 packet processing threads, 4 management threads initialized, engine started. #suricataが正常に起動した

起動したら,HTTPサーバを立ち上げる. ホストマシンで別のターミナルを開き,コンテナに接続してNGINXを起動する.

docker exec -it ubuntu-suricata bash
#/ service nginx status
#/ service nginx start
#/ curl http://localhost/
(NGINXのデフォルトページが返ってくることを確認)

HTTPサーバを立ち上げたら,suricataログの表示準備する. ホストマシンで別のターミナルを開き,コンテナに接続して以下のコマンドを実行する.
suricataログはアラートも含め/var/log/suricata/eve.jsonに書き込まれる.
デフォルトの設定だとアップデート通信などアラート以外の通信も書き込まれて見づらいので,jqで表示をフィルタする.

docker exec -it ubuntu-suricata bash
#/  tail -f /var/log/suricata/eve.json | jq -r -c 'select(.event_type=="alert")'
(event_type=="alert"のみが表示・更新される)

suricata検知の検証

監視準備ができたので,いよいよ攻撃コードを送ってみる.
攻撃者であるdebian-slimコンテナに接続し,curlで攻撃コードを実行する.
攻撃コードはsuricataルールに含まれていればなんでも良いが,ここではWindows Serverなどに対するリモートコマンド実行を想定したリクエストを送ってみる(間違って外部に実行しないこと)

docker exec -it debian-slim bash
#/ curl http://172.18.0.3/cmd.exe
(cmd.exeは存在しないので404エラーが返ってくる)

検知していれば,eve.jsonに次のようなログが追加される.
送信元IPや宛先IP,シグネチャ名,url, User-Agentなどが記載されている.
検知したシグネチャ名は"ET WEB_SERVER cmd.exe In URI - Possible Command Execution Attempt"となっている.

{"timestamp":"2019-X-XT13:40:15.681575+0000","flow_id":881816844067101,"in_iface":"eth0","event_type":"alert","src_ip":"172.18.0.2","src_port":51516,"dest_ip":"172.18.0.3","dest_port":80,"proto":"006","tx_id":0,"alert":{"action":"allowed","gid":1,"signature_id":2009361,"rev":5,"signature":"ET WEB_SERVER cmd.exe In URI - Possible Command Execution Attempt","category":"Attempted Information Leak","severity":2,"metadata":{"updated_at":["2010_07_30"],"created_at":["2010_07_30"]}},"http":{"hostname":"172.18.0.3","url":"/cmd.exe","http_user_agent":"curl/7.52.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":404,"length":178},"app_proto":"http","flow":{"pkts_toserver":4,"pkts_toclient":3,"bytes_toserver":353,"bytes_toclient":548,"start":"2019-02-22T13:40:15.680221+0000"}}

少し見づらいので,たくさんのログを処理する場合は整形するなどしてもよいし,GUI(snorbyなど)と連携してもよいと思う.

おわりに

以上で最低限攻撃検知するところまで再現できた.
/etc/suricata/rules/に含まれているルールを読んでどんな攻撃を検知するかを試したり,自分なりのカスタムルールを追加してみたり色々遊ぶと勉強になると思う.
なおsuricataを攻撃遮断をするIPSモードで実行する場合はソースファイルをオプション付きでコンパイルする必要があるっぽいので,今回作った環境では実現できないかもしれない(できるかもしれないが,試していない)

参考

suricataのインストール方法などは公式ドキュメントに記載されている.
suricata.readthedocs.io

インストール完了後の基本設定は以下の公式ドキュメントを参考にした.
redmine.openinfosecfoundation.org