NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

まだ自力でインターネットをスキャンしているの?

はい、やってます^^

皆様どうもこんにちは、@strinsert1Na という人です。本ブログは NFLaboratories Advent Calendar 2024 *1 15日目の記事となります。筆者が執筆する内容は、先月 AVTOKYO2024 でプレゼンした『Going down the RAT hole』で語り切れなかったインターネット空間探索の話です。

speakerdeck.com

インターネット空間を如何に効率的に探索して脅威や脆弱性を検出するかというのは、多くのセキュリティベンダーやリサーチャーが真摯に取り組んでいるテーマかと思います。昨今は ASM (Attack Surface Management *2 ) という言葉も浸透し、公開情報をベースに自社のセキュリティ対策状況を評価・改善する取り組みをはじめた民間企業も少なくないでしょう。

このような背景下に置かれている公開情報収集者には、とある強力な味方たちがいます。それは、 ShodanCensys に代表される『インターネット空間デバイス探索サービス』*3 です。これらのサービスは 24/365 でインターネット空間に存在するデバイスをスキャンし続けており、結果は Webサービス上に無料で公開してくれています。これらのデータを参考にすることで、公開情報収集者はC2インフラストラクチャの網羅的な探索やアタックサーフェスの管理を簡単に行うことができます。もはや Shodan や Censys は、セキュリティリサーチャーにとっては必要不可欠な存在と言っても過言ではないでしょう。

しかし昨今、筆者は「(これらのサービスの結果が盲信されすぎているのではないか?)」と心の中で思ったケースに遭遇する事例が増えました。筆者は AVTOKYO に限らず JSAC などの別のカンファレンスで登壇したプレゼンでも「自分でスキャナを作ってインターネットをスキャンしてます😉」とお話しする機会があるのですが、そんな中でもポツポツと「Shodan, Censys でなぜ代用はできないのか?」という質問はいただきますし、「Shodan の API ですべて自動化して管理しています(!?!?!?)」という割と本気で心配になるような ASM をしている組織もあったりしました。サービスがどんな特性を持っているか不明瞭なのにもかかわらず、これらの結果を真として捉えるのは些か怖い話です。

engineers.ntt.com

なので本投稿では「インターネット空間デバイス探索サービスが充実している昨今でも、なぜ自力でインターネットを探索することに意味があるのか?」というテーマについて、筆者が余暇で行っているAPTの脅威ハンティングの実例からその必要性をお話ししたいと思います。

検証のための準備

自力でインターネットを探索することの有意性を説くためには、自身で建てたサーバに対して各種デバイス探索サービスに巡回してもらい、その結果をWebサービスから確認するのが一番でしょう。なので、まずは VPS を契約して簡単な TCP サーバを構築します。構築する TCP サーバは、筆者がよくリサーチの対象とする Post Exploitation Tool の Listener を模倣したものにします。違う言葉で表現すると、RAT の C2サーバ の模倣です。Post Exploitation Tool の Listener を模擬した C2 サーバをデバイス探索サービスが捉えることができれば、そのレスポンスの特徴をフィンガープリントとすることで同一のツールを使うインフラストラクチャを網羅的に探索することができるため非常に有用です。

今回検証するにあたり、TCP サーバとして2種類の Listener を想定したものを用意します。1つ目のケースは『 Listener にアクセスがあった場合乱数のデータを send し、その後 recv したデータに対して送った乱数を key として復号、 Listener が想定するデータ構造となっていたら処理を継続、そうでなければ切断する』というものです。RAT のインプラントを解析したアナリストが一度は遭遇したことのあるような処理かなと思います。これを簡単に実装したコードが以下です。本当は乱数作った後に埋め込んだ公開鍵を使って暗号化する方が一般的だと思いますが、簡略化のために省いてます。ご容赦ください。(ポート番号おかしくない? と思った方がいらっしゃるかもしれませんが、AVTOKYO のプレゼンを見た方にはわかるはず、あの Listener の模倣をしています)

import socket
import os
from Crypto.Cipher import ARC4


PASSPHRASE = b"asdf"

def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(('0.0.0.0', 4567))
    server_socket.listen(1)
    conn, addr = server_socket.accept()

    try:
        rc4_key = os.urandom(256)
        conn.sendall(rc4_key)
        first_byte = conn.recv(1)
        if not first_byte:
            print("Connection closed by client.")
            return

        data_size = first_byte[0]
        print(f"Expecting {data_size} bytes of data...")
        if data_size > 5:
            """
             1-4: PASSPHRASE
             5: white space
             6: main data
             
             e.g.) b"asdf aptMGWR"
            """
            received_data = conn.recv(data_size)
            print(f"Received data: {received_data}")
            cipher = ARC4.new(rc4_key)
            decrypted_data = cipher.decrypt(received_data)
            if len(decrypted_data) == data_size  and decrypted_data[:4]== PASSPHRASE:
                conn.send(b"Hello, " + decrypted_data[5:])
                print(f"[*] {decrypted_data[5:]} accessed")


    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        conn.close()
        server_socket.close()

if __name__ == "__main__":
    print("[*] Waiting for a connection on port 4567...")
    while(1):
        main()


続いて2つ目のケースは『Listener にアクセスがあった場合データのフォーマットを検証、 Listener が想定するデータ構造となっていたら処理を継続、そうでなければ何も行わずに切断する』 というケース1をよりシンプルにしたものです。データ構造は最初の 1バイトが後続のファイルサイズ、次に検証用のパスワード、最後にメインデータという完全に某 Post Exploitation Tool の実装パクリです。これを簡単に実装したコードが以下となります。

import socket


PASSPHRASE = b"asdf"

def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(('0.0.0.0', 5678))
    server_socket.listen(1)
    conn, addr = server_socket.accept()

    try:
        first_byte = conn.recv(1)
        if not first_byte:
            print("Connection closed by client.")
            return

        data_size = first_byte[0]
        print(f"Expecting {data_size} bytes of data...")
        if data_size > 5:
            """
             1-4: PASSPHRASE
             5: white space
             6: main data
             
             e.g.) b"asdf aptMGWR"
            """
            received_data = conn.recv(data_size)
            print(f"Received data: {received_data}")
            if len(received_data) == data_size  and received_data[:4]== PASSPHRASE:
                conn.send(b"Hello, " + received_data[5:])
                print(f"[*] {received_data[5:]} accessed")


    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        conn.close()
        server_socket.close()

if __name__ == "__main__":
    print("[*] Waiting for a connection on port 5678...")
    while(1):
        main()

これらケース1, ケース2の TCP サーバをそれぞれ port 4567, 5678 で動作させ、2週間放置したあとにデバイス探索サービスの結果を確認します。検証および比較対象として使うサービスは、筆者の主観において業界で最も利用されている以下の4つのサービスです。

各サービスの詳細については他のブログでも紹介されているので省略します。いずれも無料で活用できる有用なデバイス探索サービスですが、本投稿では検証結果の確認というスコープに留めて利用していきましょう。

CASE-1: 乱数を返す TCP サーバのスキャン結果

さて、2週間放置しました。さすがにすべてのサービスが一度は巡回して、結果が Web サイト上から確認できるようになっているはずです。まずは手元で Port discovery scan を行って、2週間前に構築した TCP サーバが立っているかを確認してみます。確認には高速な RustScan を利用しました。

RustScan による Port discovery scan(CASE-1 の 4567 と CASE-2 の 5678 のポートが両方 Open なことを確認

IPアドレスはマスクしてありますが、問題なく動作していましたね。検証で構築した TCP サーバはとある Post Exploitation Tool の Listener を模倣する都合 Windows Server 上に構築されており、Windows Server 標準で open される SMB や WinRM のサービスも確認できますね。それでは、各サービスでの検索結果を確認していきます。

Shodan

まずは王道の Shodan から確認していきます。Shodan の場合、以下の形式で URL にアクセスすることによって該当するIPアドレス上で露出しているサービス一覧を取得できます。*4

https://www.shodan.io/host/192.0.2.1

それでは、筆者が構築したサーバのIPアドレスを入力して結果を見てみます。

Shodan でのIPアドレス検索結果

......見てはいけないものを見た気がしました。検証のために構築した TCP サーバが2つとも確認できませんでした。以下の Web ページを参考にすると Shodan は一週間に一度はインターネット空間全体をスキャンするようですし、筆者自身もこの方式に沿って On-Demand scan の要求もしましたが、構築したサービス port との相性が悪かったのかもしれません。しかしながら、今回の検証では何もフィルタをしていないにもかかわらず DCERPC や RDP のサービスも見えていないので、結果が少々懐疑的にも感じます。
help.shodan.io

少なくとも検出された port 一覧に 4567, 5678 の数字はなかったので、Shodan だとマイナーポートまたは Post Exploitation Tool の Listener を模した TCP サーバは検出しない可能性があるというのは検証できました。

Censys

次に Censys です。おそらくですが、2024年12月現在 Shodan と同じか(筆者の個人的評価だと)それ以上に利用される機会が多いデバイス探索サービスなのではないかと思います。Shodan と異なり過去のスキャン履歴を確認できたり、スキャンが1日1回程度の頻度では行われるため更新頻度が非常に高いというのがその理由です。サービスの利用方法は Shodan とほぼ同じで、 Web サイトからクエリを入力するだけでその端末のスキャン結果を確認できます。試しに、IPアドレスに加えてCASE-1 で利用した port 番号に絞って検索し結果を表示させてみましょう。

192.0.2.1 and services.port: 4567
Censys でのサービス確認結果

結果が表示されました。Shodan と違いホスト上の RDP のサービスも検出しているため網羅性も十分ですね。また、port 4567 のスキャン結果の詳細を確認してみると、検証用に実装した乱数データが返されていることも確認できます。

Censys でのサービス確認結果詳細

Post Exploitation Tool の Listener 探索や ASM など、様々な局面で利用できる非常に強力なサービスであることが確認できましたね。

FOFA

続いて FOFA です。FOFA も Shodan, Censys とほぼ同じ感覚で Web サイトからスキャン結果を確認できるのですが、Web GUI が中国語であることと基本的にホスト(IPアドレス)単位ではなくサービス(port)単位で結果を表示するので見方に一癖あります。今回のケースでは、以下のようにIPアドレスとサービスを直接クエリで指定して結果を見るのが一番手っ取り早いでしょう。

ip="192.0.2.1" && port="4567"

検索結果は以下の画像になります。Censys と同じく、Banner に乱数データが表示されており想定通りですね。

FOFA でのサービス確認結果

また巡回速度も速く、体感一日前後で 4567 のサービスをスキャン完了していました。FOFA は Post Exploitation Tool の探索用ツールとして有効に働く見込みがありそうです。

ZoomEye

最後に ZoomEye の結果を確認していきましょう。ZoomEye も他のサービスと同様 Web サイトから簡単に結果の確認ができるのですが、ログインしないと期間検索ができないのでログインした状態で以下のようなクエリを実行します。

ip="192.0.2.1" && after="YYYY-MM-DD" && before="YYYY-MM-DD"

期間を絞る理由としては、どうやら ZoomEye は port のサービスをスキャン後に更新があったデータだけ上書きするという方針をとっているようで、日付を指定しない場合に過去の VPS 契約者のスキャン結果と混じりノイズが生まれてしまうためでした。それでは、筆者が VPS を契約した後に更新されたスキャン結果を見てみましょう。

ZoomEye でのIPアドレス検索結果

......どういうことでしょう、WinRM の port しか見えませんでした。ひょっとすると、その他のインターネット空間デバイス探索サービスとは異なり、特定の脆弱性や port に絞ったスキャンしか行っていないのかもしれません。なので現状では、Post Exploitation Tool の Listener 探索の用途では少々不向きではないかという考察が得られました。

CASE-1 段階のまとめ

CASE-1 で用意した4つのサービスの検出状況をまとめると、下表のようになります。

サービス名 Shodan Censys FOFA ZoomEye
CASE-1 のサービス検出状況 × ×

特に Censys に関しては port discovery の網羅性もよく、Post Exploitation Tool の Listener 探索用途としては非常に向いてそうです。FOFA と併用して利用すれば、これだけで ASM もできそうな気がしてきました。あれ、じゃあもう自力でインターネットをスキャンする意味ってなくないっすか??

CASE-2: 想定したフォーマットでない場合応答しない TCP サーバのスキャン結果

それでは続いて、CASE-2 の結果がどう検出されているかを確認していきましょう。Shodan と ZoomEye は CASE-1 の時点ですでに検出できていないことがわかっているので、ここでは Censys と FOFA に絞って見ていきます。

Censys

まずは Censys からです。CASE-2 で使用した port は 5678 なので、Webサービスから以下のクエリで検索した結果を確認します。

192.0.2.1 and services.port: 5678
Censys でのスキャン結果(CASE-2)

なんということでしょう。CASE-1 とは異なり CASE-2 のサービスのスキャン結果は Censys からでは確認できませんでした。Censys の結果は一番信頼していたので、筆者の主観でも少々意外でした。

FOFA

続いて FOFA です。FOFA でも Webサービスから以下のクエリで検索して結果を確認します。

ip="192.0.2.1" && port="5678"
FOFA でのスキャン結果(CASE-2)

......残念ながら、FOFA でも CASE-2 のサービスを検出することができませんでした。実質、4つのデバイス探索サービスすべてで CASE-2 の検出に失敗したことになります。

一旦 CASE-1 と CASE-2 をまとめてみる

これまでの検証結果を下表にまとめてみました。

サービス名 Shodan Censys FOFA ZoomEye
CASE-1 のサービス検出状況 × ×
CASE-2 のサービス検出状況 × × × ×

Shodan, ZoomEye は Post Exploitation Tool の Listener 探索としてそもそも不向きなのかもしれませんが、CASE-2 のサービスが Censys を含むすべてのデバイス探索サービスから見えなくなるというのは少々驚きです。CASE-1 と CASE-2 のサービスにはデータを send するタイミングと port 番号程度の違いしかないはずですが、この差によって Webサービス側には一体何が起こっているのでしょうか? 効果的な脅威ハンティングをするためにも、もう少し深堀して調査を行う必要性がありそうです。

CASE-3: Censys を活用したより詳細な検証

この謎を明らかにするために、探索サービスを Censys に絞って差分を深堀してみます。 Censys に絞った理由は、巡回する頻度が非常に多いことと、内部のスキャンロジックを一部公開してくれていることの2点です。実際に Censys が巡回するポートの一覧は下記の『Censys Internet Scanning Introduction』のページにまとめられており、CASE-1, CASE-2 で使用した port 4567 と 5678 はどちらもリストに入っていることがわかります。
support.censys.io

よって、サービスを検出できるかどうかの違いはデータを send するタイミングの違いが起因していそうだと予想は付くのですが、CASE-3 の検証ではより差分をなくすために、CASE-2 のサービスを port 4567 に再構築して結果を比較してみることにします。さらに、サービスにアクセスがあった場合に接続元IPアドレスを表示するようスクリプトを変更して、Censys からのスキャンが確実にあったことを確認できるようにしました。実行して数日放置すると、下図のように Censys から大量のサービススキャンがあったことが確認できました。

TCPサーバを巡回したIPアドレスの一例 (162.142.125[.]44, 167.94.138[.]54, 206.168.34[.]57, 206.168.34[.]194 はいずれも Censys スキャナーのIPアドレス)

それでは、Censys が CASE-2 のサービスを port 4567 で動作させた場合にどう評価したか Web サイトから確認してみましょう。結果は下図のようになりました。

port 4567 に CASE-2 の TCP サーバを移動させた結果

なんとステータスが PENDING REMOVAL に移行し、サービスの検出に失敗しています。どうやらスキャン対象のサービスから何もデータを recv できなかった場合、Censys 用語ではこれをサービスとは見做さないようです。おそらくですが、FOFA も同じような処理をしているものと思われます。これは Post Exploitation Tool の Listener 探索という観点では致命的な見落としです。特定のデバイス探索サービスからのIPアドレスをブロックするようなフィルタを用意しなくても、サービス側が許容するデータ構造でデータが送られてこなかった場合コネクションを切断するという RAT に見られる動作をするだけで Shodan や Censys からの探索はできなくなります。そうなると、特定の RAT を利用する攻撃者グループのインフラを網羅的にハンティングしようとした場合、自身で解析した結果をもとにオリジナルのスキャナーを開発してインターネット空間を自力でスキャンせざるを得ないですね。ぴえん。

CASE-0: epilogue

ここまで長々と書いてきましたが、筆者の伝えたいことの趣旨は「インターネット空間デバイス探索サービスは使えねーな!!」というものでは決してありません。筆者が伝えたいことは「使っているサービスの特性や限界を知らないと何か大切なものを見落とすかもよ」ということです。実際 Shodan や Censys は非常に便利なサービスであり簡単に情報収集をすることができますが、その一方で以下のような特性・限界があることをここまで読んでくださった方々には理解していただけたと思います。

  • デバイス探索サービスを複数組み合わせても、 ASM や脅威ハンティングの側面で捉えられない脆弱性・脅威が存在する
  • デバイス探索サービスが port discovery scan の代替にはなりえない

なので世の中のレッドチームの多くはインターネットに露出しているサーバでも自力で port discovery scan をしますし、筆者自身はこれからも自力でインターネット空間をスキャンして攻撃者のインフラを検出する営みを続けることでしょう。それをしないと見えないものというのがいっぱいありますからね。

最後に、本稿に書かれた内容の一部は、筆者が尊敬するセキュリティリサーチャーの1人である春山さんの数々の資料にたいへんわかりやすく書かれています。興味を持った方は Speaker Deck にアップロードされている資料を読んでみると勉強になるでしょう。

speakerdeck.com

本稿が AVTOKYO2024 で筆者の発表を聞いてくれたにもかかわらず駆け足すぎてよくわからなかった方や、普段 Shodan, Censys の結果をネットの真実だと信じている人の参考になれば幸いでございます。

......

...

さて、NFLaboratories Advent Calendar 2024 も明日で最終日です。早いものですね。

adventar.org

最終日はNFLabs.で開発している新しいトレーニングプラットフォームの記事で締めたいと思います。それでは👋

*1:https://adventar.org/calendars/10492

*2:https://gmo-cybersecurity.com/column/security-measures/asm/

*3:これは筆者が勝手に名前付けしたカテゴリであり、実際には違う呼称方法がありそうです。しかしながら筆者には一言できれいに表現できる名称が思いつきませんでしたので、本投稿ではこの名称で呼ばせてください

*4: 例示用IPアドレスの部分を、各自が調査したいIPアドレスに変更してください。