Help us understand the problem. What is going on with this article?

ログインログの収集と不正アクセス検知

More than 5 years have passed since last update.

http://www.slideshare.net/kawasima/clojure-30414922

以前のtokyo.cljで話した内容ですが、Stormを使った不正アクセス検知の仕組みの実装をちゃんと作りなおしてみたので、ここに詳細書いておきます。

http://www.nikkei.com/article/DGXMZO77794750R01C14A0000000/

最近またリスト型攻撃が増えてきているようで、対策に頭を悩まされているエンジニアも少なくないのではないでしょうか。リスト型攻撃はファイアウォールやWAFで防ぐことが難しく、インテリジェントな判断が必要になります。

そこで、複数のサイトからログインのログ(Database)をリアルタイムで収集し、リスト型アクセスっぽいものを解析しアラートを、監視コンソールにあげるという仕組みをプロトタイプとして作ってみました。

mine-canary.png

ログインログの収集

ログインログは、ユーザのログイン試行時に、各Webアプリのデータベースのテーブルにその成否が書き込まれる想定です。

create table login_log (
  logined_at timestamp not null,
  account varchar2(100) not null,
  ip_address varchar2(15) not null,
  success char(1) not null);

これを既存のWebアプリに変更を加えることなく、取得するにはデータベースのEvent機能を使うとよいです。

Oracleの場合、DBMS_ALERTを使うと、Webアプリとは別コネクションで、テーブルへの更新がリアルタイムに取得できるようになります。

CREATE OR REPLACE TRIGGER login_log_after_insert
AFTER INSERT
   ON login_log
   FOR EACH ROW
BEGIN
   DBMS_ALERT.SIGNAL('NEW_LOGIN',
                     (cast(SYS_EXTRACT_UTC(:new.logined_at) as date)
                      - cast(from_tz(timestamp '1970-01-01 00:00:00', '00:00')
                      - as date)) * 24 * 60 * 60 || ' '
                     || :new.account || ' '
                     || :new.ip_address || ' '
                     || :new.success);
END;
/

変更はトリガを仕込んでおいて、DBMS_ALERT.SIGNALでアラートを発生させるようにします。

これを別のコネクションからキャッチします。

(reset! oracle-conn (j/get-connection oracle-db))
 (let [stmt (.prepareCall @oracle-conn "{call DBMS_ALERT.REGISTER(?)}")]
  (doto stmt
    (.setString 1 "NEW_LOGIN")
    (.executeUpdate)
    (.close)))
(let [stmt (.prepareCall @oracle-conn "{call DBMS_ALERT.WAITANY(?,?,?,?)}")]
  (doto stmt
    (.registerOutParameter 1 Types/VARCHAR)
    (.registerOutParameter 2 Types/VARCHAR)
    (.registerOutParameter 3 Types/INTEGER)
    (.setInt 4 300)))

これでLOGIN_LOGテーブルにINSERTがされた瞬間に、ALERTをキャッチできるようになります。これをログサーバに送信してあげます。

https://github.com/kawasima/mine-canary/tree/master/example/log-collector

ここではflumeを使います。これは冒頭のスライドに書いてあるように、flumeの設定やオリジナルのSourceやSinkをClojureで実装できるようにしてあります。

ログインログの送信

これも冒頭のスライドに書いていますが、flumeとstormを連携は間にメッセージキューを挟むのがよくある実装例っぽいのですが、別立てするの面倒なので、WebSocketを使った自前の組み込み型のメッセージ送信のライブラリulon-colonを使っています。

https://github.com/kawasima/ulon-colon/

そうすると、送信側

(defsink ulon-colon-sink
  :start  (fn [] (start-producer))
  :process (fn [event]
             (produce (String. (.getBody event)))))

受信側は

(consume-sync consumer
  (fn [msg]
    (emit-spout! collector [msg]))))

とたったこれだけのコードで、メッセージ送信できるようになります。

不正アクセスの検知

Stormを使って収集したログインログを解析します。これ用のSpoutとBoltのセットをmine-canaryとして作りました。

https://github.com/kawasima/mine-canary/

こんな感じで一定期間に5回以上同一ユーザでログイン失敗した人を検出するBoltを書きます。

;;; 一定時間内に同一ユーザの大量のログイン失敗を検出する
(defbolt failures-by-same-user ["account" "times"] {:prepare true}
  [conf context collector]
  (let [counts (atom {})]
    (bolt
     (execute [tuple]
       (let [[tm account ip-address success?] (.getValues tuple)
             tm (long (Double/parseDouble tm))
             success? (= success? "1")]
         (when-not success?
           (let [failures-tm (get @counts account [])]
             (reset! counts
                   (assoc @counts account (->> (conj failures-tm tm)
                                               (sort >)
                                               (take 5)
                                               vec))))
           (when (in-the-period? (@counts account))
             (emit-bolt! collector [account (@counts account)] :anchor tuple)))
         (ack! collector tuple))))))

監視コンソールへの通知

Webアプリとしてmine-canaryの監視コンソールを作っているので、不正アクセスの通知をリアルタイムに受けることができます。

https://github.com/kawasima/mine-canary-console/

監視コンソールはWebSocketでmine-canaryにコネクションを貼っていて、不正アクセスの発生イベントのPushを待ちます。ここの連携も前述のulon-colonを使っています。
ulon-colonにはClojureScriptのconsumerも付いているので、直接メッセージをブラウザに配信できるのです。

ちなみに、監視コンソールはomを使って書いていますが、このulon-colonとomを組み合わせると、サーバからpushされたメッセージを受け取り順次表示するみたいなコードも、簡潔に書けます。

(defcomponent main-app [app owner]
  (will-mount
   [_]
    (consume (:consumer app)
      (fn [msg]
        (om/transact! app :alerts #(conj % msg)))))

  (render
   [_]
   (html
     [:div.ui.list
      (for [{:keys [account times]} (:alerts app)]
       [:div.item
        [:i.icon.frown]
        [:div.content
         [:div.header "Suspicious attack"]
         [:div.description (format-times account times)]]])])))

(om/root main-app {:alerts [] :consumer (make-consumer "ws://localhost:56293")}
         {:target (.getElementById js/document "app")})

こうして、mine-canaryであげた不正アクセスの兆しが、リアルタイムに監視コンソールに表示されるようになります。
ログイン用のexampleアプリもfriendを使って作ってみましたので、そこからログインを何回か失敗してみます。

SnapCrab_NoName_2014-10-8_14-25-55_No-00.png

すると、監視コンソールにメッセージがリアルタイムであがってきます。

SnapCrab_NoName_2014-10-8_14-25-19_No-00.png

実際に使うには…

実際のリスト型攻撃は、より巧妙になってきているのでmine-canaryのBoltのルール調整が必要です。どのくらいのユーザ・期間を監視対象とするかにもよりますが、Stormはインメモリにデータを溜め込むのが基本なので、それなりのノード数が必要になると思われます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした