ARPパケットに対する挙動からネットワーク上の盗聴者を特定する

この記事は, 旧ブログから移植された記事です. よって, その内容として, 旧ブログに依存した文脈が含まれている可能性があります. 予めご了承下さい.

通信が暗号化されていればまだ良いが, 自分の送受信しているパケットを同一ネットワーク上の信用できない者/物に無断で見られるのはやはり気持ちの良いものではない. 本エントリではそのような不届き者の存在を仮定して, その不届き者を比較的簡単に特定するといった試みを行う.

お断り

本エントリでの試行は当然ながら私個人のローカル LAN 上で行なっており, 同様の試行を公衆回線上などで行うと迷惑/法に抵触する可能性があるのでやめること. 本エントリに起因する直接的又は間接的な損害に関して, その理由及び原因を問わず著者(Roki) は一切の責任を負わない.

ネットワーク盗聴を検出する原理

ネットワークに接続するためには, 大抵コンピュータに NIC を装着して TCP/IP の設定を済まし, ハブに接続する. 同プロトコルにおける各コンピュータの通信では, IP アドレスと MAC アドレスによる論理情報と物理情報の組み合わせを利用して, 目的のコンピュータに対するパケットの送信を実現する. 通常, その過程で NIC は自分とは無関係であるパケットを破棄する. しかし NIC をプロミスキャスモードにすると, 自分とは無関係であるパケットを破棄せずに取り込むようになる. 盗聴者はこれを利用して, 同一ネットワーク上を流れる他人のパケットを取得できる.
IP アドレスと MAC アドレスを関連付けるためには ARP/RARP パケットを送信する. ARP パケットは, アドレス解決を必要とするノードによってブロードキャスト送信され, 要求に対応するノードが応答を行う. ブロードキャストは同一セグメント内にしか到達しないため, 他のセグメントと通信する際はルーターに依頼する. このとき, 要求に対応しない非プロミスキャスモードであるノードはその ARP 要求を破棄する. これに対して, プロミスキャスモードのノードは, 先に述べた通り全てのパケットを取り込む. しかし, プロミスキャスモードであっても, 通常この取り込まれた ARP 要求に対する応答を返すことはしない. なぜならば NIC のモードに関わらずソフトウェアによるフィルタが通常 OS 内部で働いている1からである.
ただ, このソフトウェアによるフィルタの実装は甘い場合がある. ブロードキャストを判定するときに, 正確には FF:FF:FF:FF:FF:FF の 6 バイトを比較しなければならないところを, 最初の 1 バイトだけで判定するなどの実装が有りうるのである. 例えば, そのような実装となっているソフトウェアを積んだコンピュータが, 同一 LAN 上にプロミスキャスモードで稼働しているとする. 宛先を FF:xx:xx:xx:xx:xx としてそのコンピュータの MAC アドレスを目的とする2 ARP パケットを送ると, NIC が Ethernet の仕様に忠実に従っていた場合, 送られてきた宛先は FF:FF:FF:FF:FF:FF ではないためブロードキャストではないと判断する. よってプロミスキャスモードで稼働していない場合, この ARP 要求は NIC によって破棄される. しかし, プロミスキャスモードである場合はこれを取り込む. 取り込んだ先に待っているのはソフトウェアによるフィルタであるが, これが甘い場合, それをブロードキャストアドレスとして認識し, ARP 応答を返すのである. ここで重要なのは, NIC とソフトウェアそれぞれでブロードキャストに対する判断が異なっているということである. これにより, プロミスキャスモードとなっているノードのみが, いま送信した ARP パケットに応答してしまうため, 特定ができるかもしれない.

実験

実験環境は次のとおり.

実験環境
/ 盗聴側 検出側
OS Ubuntu 16.04 LTS (4.13.0-37-generic) Ubuntu 17.04 (4.13.0-37-generic)
NIC プロミスキャスモード(enp4s0f2) 通常モード(enp19s0)
MAC アドレス 80:fa:5b:xx:xx:xx 24:b6:fd:xx:xx:xx
IP アドレス 192.168.12.4 192.168.12.3

盗聴側の MAC アドレスが検出側で得られれば良い. 実験には, 宛先を少し弄って ARP パケットを送信できるソフトウェアが必要である. nping や arping などを少し見て見たが, 宛先 MAC アドレスを弄れるような機能は特になかった (FF:FF:FF:FF:FF:FF でない ARP パケットを送る用途は殆どないだろうし, 当然だ) ので, 自分で作った. ついでに, 超簡易 ARP パケットキャプチャも作ってみた. これらは 上記の OS で動作を確認している. 動作テストには tcpdump を用いた3.

特に何も指定せずにmakeすると, 通常の ARP パケットを送信するバイナリが/dst/sendarpとしてビルドされ, -f Makefile_bogusを指定すると宛先 MAC アドレスを FF:FF:FF:FF:FF:FE とした ARP パケットを送信するバイナリが同様にしてビルドされる.
どちらの場合においても, /dst/dump_arpがビルドされ, これが超簡易 ARP パケットキャプチャだ. コマンドラインオプションに-pを指定すると, NIC をプロミスキャスモードにして動作する. これを上記で示した環境それぞれでビルドし, その挙動を見てみる. なお本実験では直接盗聴側の IP アドレスを指定しているが, 実際では総当たりすることになるだろう4. また検出側では自身の本当の IP アドレスと MAC アドレスを利用して試行しているが, 実際ではこれらを何かしら偽装することも考えられるだろう.

まずは, 通常通り ARP を送信してみる.

検出側$ sudo ./dst/dump_arp enp19s0 &
検出側$ sudo ./dst/sendarp enp19s0 192.168.12.4
ether_header -------------------------
    dhost = 24:b6:fd:xx:xx:xx
    shost = 80:fa:5b:xx:xx:xx
    type = 806(ARP)
盗聴側$ sudo ./dst/dump_arp enp4s0f2 # 非プロミスキャスモード
    dhost = ff:ff:ff:ff:ff:ff
    shost = 24:b6:fd:xx:xx:xx
    type = 806(ARP)

正常にやりとりができているようだ. 続いて, 盗聴側は非プロミスキャスモードのまま, 検出側から FF:FF:FF:FF:FF:FE 宛てに ARP パケットを送信してみる.

検出側$ make -f Makefile_bogus
検出側$ sudo ./dst/dump_arp enp19s0 &
検出側$ sudo ./dst/sendarp enp19s0 192.168.12.4 # 宛先 FF:FF:FF:FF:FF:FE の ARP パケット
# ... 対応する出力なし
盗聴側$ sudo ./dst/dump_arp enp4s0f2 # 非プロミスキャスモード
# ... 対応する出力なし

NIC のフィルタリングによって, このパケットは省かれるため応答が返ってくることはない. つまり, 192.168.12.4 は盗聴していないということがわかる. 次に, 盗聴側をプロミスキャスモードにして FF:FF:FF:FF:FF:FE 宛てに ARP パケットを送信してみる.

検出側$ sudo ./dst/dump_arp enp19s0 &
検出側$ sudo ./dst/sendarp enp19s0 192.168.12.4 # 宛先 FF:FF:FF:FF:FF:FE の ARP パケット
    dhost = 24:b6:fd:xx:xx:xx
    shost = 80:fa:5b:xx:xx:xx
    type = 806(ARP)
盗聴側$ sudo ./dst/dump_arp enp4s0f2 -p # プロミスキャスモード
    dhost = ff:ff:ff:ff:ff:fe
    shost = 24:b6:fd:xx:xx:xx
    type = 806(ARP)

応答が返ってきた. つまり, 192.168.12.4 はプロミスキャスモードであり, 盗聴している可能性を特定できた. 当然ながら OUI を調べればメーカーがわかるので, 結果から盗聴者の目星をつけることができる.

$ curl -s -D - http://standards-oui.ieee.org/oui.txt | grep 80FA5B -A 4
80FA5B     (base 16)            CLEVO CO.
                                NO. 129, XINGDE ROAD
                                New TAIPEI CITY    241
                                TW

後日談

某大学授業のネットワークを専門とする講師にこの件について訪ねたところ, Linux におけるプロトコルスタックの実装に関して詳しい言及が頂けたため, それについてと, 新たにそこから考えられる知見等をここに追記する. 情報を提供/ご教示して下さいました先生に, この場を借りて改めて感謝いたします.

上記内容のネットワーク盗聴を検出する原理の中で, 取り込んだ先に待っているのはソフトウェアによるフィルタであるが, これが甘い場合, それをブロードキャストアドレスとして認識し, ARP 応答を返すと述べているが, これは厳密に, Linux カーネル v4.13 においては, マルチキャスト MAC アドレスと誤認し ARP 応答を返す. また, その処理として 6 byte 全てをチェックしていないというわけではない.
そもそもブロードキャストアドレスはマルチキャストアドレスの特殊形であるから, Linux の実装においても, それがまず広義のマルチキャストアドレスであるかどうかを判定する5. このとき宛先 MAC アドレス FF:FF:FF:FF:FF:FE は広義のマルチキャストアドレスとして認識し, 次にそれがブロードキャストアドレスであるかどうか6 byte 全てをみて判定する6. 結果的に, これをブロードキャストアドレスではないと判定し, 狭議のマルチキャストアドレスとして扱う.
なお, Linux では以下のように ARP そのものを無効化することができる. 同様の設定であるマシンに対しては, 本エントリの方法で検出することはできない.

$ sudo ip link set dev <interface name> arp off

また, ここまで述べてきたプロトコルスタックによる宛先 MAC アドレスのフィルタリング処理は, ネットワークインタフェース層で規定されるものであるから, Linux におけるプロトコルスタックの実装は, その実装定義の一種であるというように捉えることが最も妥当である. RFC 826 では, 同理由によりプロミスキャスモードによって発生しうるあらゆる振る舞いに関して定義しない7.

参考文献


  1. Linux kernel v4.13 (実験環境と同じ)の場合↩︎

  2. つまり盗聴ノードの IP アドレスを指定する. 実際の状況では IP アドレスも当然わかっていないので, 一定範囲内の IP アドレスに関連する ARP 要求を総当たりで送ることになるだろう.↩︎

  3. -vvv が非常に便利だ.↩︎

  4. つまり盗聴ノードの IP アドレスを指定する. 実際の状況では IP アドレスも当然わかっていないので, 一定範囲内の IP アドレスに関連する ARP 要求を総当たりで送ることになるだろう.↩︎

  5. net/ethernet/eth.c:168, include/linux/etherdevice.h:112~144 がこれに該当する. これを見ると, MACアドレスの先頭 1 オクテットのユニキャスト/マルチキャストフラグをチェックしていることがわかる.↩︎

  6. include/linux/etherdevice.h:309~361↩︎

  7. というより, プロトコルスイート各種の規定は, その各レイヤーごとの規定を定めるものであって, 各レイヤーごとがどのように連携するかについて規定することが本質ではない.↩︎