セキュリティ研究の自動化:AIエンジンがZulipのストアドXSS(CVE-2025-52559)を悪用
本記事では、Zulipに存在するストアドXSS脆弱性CVE-2025-52559を悪用するための、徹底的かつ実践的な分析と概念実証(PoC)を紹介します。OstorlabのAIを活用したペンテストエンジンがその全サイクルをどのように自動化したかを詳しく解説します。
Zulipは、SlackやGoogle Chatに似たオープンソースのチャットアプリケーションです。このアプリケーションでは、CVE-2025-52559としてストアド型クロスサイトスクリプティング(XSS)脆弱性が発見されました。
当社はこの1年間、これまで自動化が不可能だった脆弱性クラスにまで検出能力を拡張するため、AI Penetration Testing Engineの構築に取り組んできました。最終リリースが近づく中、自動化によって検出がより難しいバグにも対応できるようになったことを紹介したいと思います。
セットアップ
セットアップについては、Zulipのdockerリポジトリのおかげで簡単に実行できる環境が用意されたオープンソースプロジェクトであるため、次のように実行しました:
➜ docker-zulip git:(main) ls
certbot-deploy-hook CODE_OF_CONDUCT.md custom_zulip_files docker-compose.yml Dockerfile entrypoint.sh kubernetes LICENSE README.md upgrade-postgresql UPGRADING.md
➜ docker-zulip git:(main) vim docker-compose.yml
➜ docker-zulip git:(main) ✗ docker compose up
[+] Running 59/59
✔ database Pulled 29.8s
✔ 661ff4d9561e Pull complete 8.7s
✔ 6d47f5cef872 Pull complete 10.3s
✔ 0dba180d34e2 Pull complete
課題
経験豊富なペンテスターにとって、最大の課題はアプリケーションのロジックを理解することです。Zulipにおける「topic」や「channel」とは何を意味するのでしょうか。テスターはZulipが何であるか、どのように使うのかを知りません。当社のAI Pentest Engineがこれをどのように解決するか見てみましょう。
AI Pentest Engine
当社は次のプロンプトでAI Pentest Engineを起動しました:
An XSS is found in https://localhost.localdomain/ a Zulip app. We have an account with the email address [REDACTED]@ostorlab.dev and password testtest123.
The detail of the vulnerability are below:
Zulip is an open-source team chat application. From versions 2.0.0-rc1 to before 10.4 in Zulip Server, the /digest/ URL of a server shows a preview of what the email weekly digest would contain. This URL, though not the digest itself, contains a cross-site scripting (XSS) vulnerability in both topic names and channel names. This issue has been fixed in Zulip Server 10.4. A workaround for this issue involves denying access to /digest/.
...
digest.py
...
templates.py
...
また、AIが脆弱性を理解できるよう、修正パッチからのコードスニペットも含めました。AI Pentest Engineはまず、リクエストを正しく把握できたことを確認したうえで、次の分析を生成しました:
AI-Powered Prompt Analysis:
- Targets: ['https://localhost.localdomain/']
- Extra Target Information: URL: [https://localhost.localdomain/digest/](https://localhost.localdomain/digest/) Credentials:
- Email: [[REDACTED]\@ostorlab.dev](mailto:[REDACTED]@ostorlab.dev)
- Password: testtest123
- Clarified Prompt: The user wants to test for a specific Cross-Site Scripting (XSS) vulnerability in a Zulip application hosted at [https://localhost.localdomain/](https://localhost.localdomain/). The vulnerability is present in the /digest/ endpoint and can be triggered via malicious topic or channel names. The test should be performed using the provided credentials to create a stream and topic with an XSS payload, and then verify its execution by visiting the digest preview page. The provided code snippets from `digest.py` and `templates.py` indicate where the unsanitized data (channel name and topic name) is processed and rendered, leading to the vulnerability.
- Objectives:
- Authenticate to the Zulip application.
- Create a new channel (stream) with a name containing an XSS payload.
- Create a new topic with a name containing an XSS payload.
- Navigate to the /digest/ URL to trigger the XSS payload.
- Confirm the execution of the XSS payload.
- Constraints:
- The scope is limited to the host [https://localhost.localdomain/](https://localhost.localdomain/).
- The pentest must focus exclusively on the described XSS vulnerability.
- The test must be conducted using the provided user account.
- Assumptions:
- The provided credentials ([[REDACTED]@ostorlab.dev](mailto:[REDACTED]@ostorlab.dev):testtest123) are valid and have the necessary permissions to create channels and topics.
- The Zulip application version is within the vulnerable range (2.0.0-rc1 to before 10.4).
- The /digest/ endpoint is enabled and accessible.%
これに基づき、AI Pentestは次の初期テスト計画を作成しました:
-
ログイン機構を特定し、文書化する対象は、Zulipアプリケーション
https://localhost.localdomain/.認証リクエストをキャプチャし、提供された認証情報([REDACTED]@ostorlab.dev/testtest123)で正常にアクセスできることを検証します。このステップは、認証済みテストを可能にするうえで極めて重要です。 -
認証後、アプリケーション内を体系的に巡回し、新しいstream(channel)およびそのstream内に新しいtopicを作成するために必要な正確なHTTPリクエストを特定します。関連するAPIエンドポイント、パラメーター、想定されるリクエスト形式を文書化します。
-
新しいstreamを作成するリクエストを送信し、streamの名前に
<script>alert('XSS-Stream')</script>のような標準的なXSSペイロードを設定します。streamが正常に作成されることを確認します。 -
新しく作成したstream内で、新しいtopicを作成するリクエストを送信します。topicの名前には
<script>alert('XSS-Topic')</script>のような別のXSSペイロードを設定します。topicが正常に作成されることを確認します。 -
https://localhost.localdomain/digest/のダイジェストプレビューページに移動し、streamおよびtopicの名前のレンダリングをトリガーします。 -
ダイジェストページにアクセスした際のブラウザーの挙動を観察します。「XSS-Stream」または「XSS-Topic」というテキストを含むJavaScriptのアラートボックスが表示されるかを確認します。スクリプトが正常に実行されれば、ストアドXSS脆弱性が確認されます。
-
最初の
<script>ペイロードが実行されない場合は、イベントハンドラー(例:<img src=x onerror=alert('XSS')>)や異なるエンコーディングなど、フィルターを回避し得る代替のXSSベクトルでstreamとtopicの作成を再テストします。 -
実行されたペイロードや脆弱なエンドポイントのスクリーンショットを含め、テスト結果を文書化します。確認後、悪意のあるstreamとtopicを削除し、アプリケーションを元の状態に復元します。
タスクの実行は一筋縄ではいきません。AIは想定どおりに動作しない場合には適応します:
[*] 「https://localhost.localdomain/」上のZulipアプリケーションのログイン機構を特定し、文書化します。主な目的は、正確な認証リクエストをキャプチャし、提供された認証情報(「[REDACTED]@ostorlab.dev」 / 「testtest123」)で正常にアクセスできることを確認することです。これは、以降のあらゆる認証済みテストに必須の前提条件です。想定される成果は、ログインページのURL、認証に使用された完全なHTTPリクエスト、およびセッション成功の検証結果の記録です。
AIは実際のブラウザーを起動してログインフォームに入力し、コンピュータービジョンを使って認証の成功を検証します:

-- 「https://localhost.localdomain/」上のZulipアプリケーションのログイン機構を正常に特定し、文書化しました。
実行したアクションの概要:
- ログインページへの遷移:アプリケーションのルートURLへプログラムによってアクセスしたところ、リダイレクト先はログインページ
https://localhost.localdomain/login/. - 認証情報の送信:ユーザー名とパスワードのフィールドに、提供された認証情報(「[REDACTED]@ostorlab.dev」と「testtest123」)を入力し、ログインボタンをクリックしました。
- 認証の検証:アプリケーションはログイン成功を返し、メインダッシュボードにリダイレクトしました。ネットワークトラフィックを観察してこれを確認したところ、
https://localhost.localdomain/accounts/login/へのPOSTリクエストに続いて、アプリケーションのホームページへの302リダイレクトが見られました。
成果物:
- ログインページのURL:
https://localhost.localdomain/login/ - 認証リクエストのURL:
https://localhost.localdomain/accounts/login/ - 認証リクエストのメソッド:
POST - 認証リクエストのボディ:
next=%2F&csrfmiddlewaretoken=AMOIurLB5Mibj8sUZub3nIT6JUaa7fjFSVF05yNEZRjbIbmTZqh4vGSdcvfnmHMo&username=[REDACTED]%40ostorlab.dev&password=testtest123&button= - ログインのスクリーンショット:ログイン成功時のスクリーンショットは
screenshot_df87d326-62e9-4364-8b88-0c2719c42a0e_a2f6b33c-29dc-4a61-b0cf-512fe01e595c.pngにあります。
この認証の成功は、以降のあらゆる認証済みテストに必須の前提条件です。これでペネトレーションテストの次のステップに進むことができます。
Login Page URL:
https://localhost.localdomain/login/
Authentication Request URL:
https://localhost.localdomain/accounts/login/
Authentication Request Method:
POST
Authentication Request Body:
next=%2F&csrfmiddlewaretoken=AMOIurLB5Mibj8sUZub3nIT6JUaa7fjFSVF05yNEZRjbIbmTZqh4vGSdcvfnmHMo&username=[REDACTED]%40ostorlab.dev&password=testtest123&button=
Login Screenshot:
screenshot_df87d326-62e9-4364-8b88-0c2719c42a0e_a2f6b33c-29dc-4a61-b0cf-512fe01e595c.png
認証フローとパラメーターを文書化した後、AIは次の課題、すなわちchannelやstreamが何であるかの解明に移ります。これには複数回の試行が必要でした。AIはさまざまなアプローチやツールを試し、最終的に次のことを突き止めます:

[*] 認証後、アプリケーション内を体系的に巡回し、新しいstream(channel)およびそのstream内に新しいtopicを作成するために必要な正確なHTTPリクエストを特定します。関連するAPIエンドポイント、パラメーター、想定されるリクエスト形式を文書化します。
-- Zulipアプリケーションで新しいstreamおよび新しいtopicを作成するために必要なHTTPリクエストを正常に特定しました。
実行したアクションの概要:
- 認証:提供された認証情報を使用してアプリケーションに認証しました。
- APIキーの生成:「Your bots」設定ページに移動し、新しいボットを作成してAPIキーを生成しました。
- streamの作成:生成したAPIキーを使用して
/api/v1/users/me/subscriptionsにPOSTリクエストを送信し、「test-stream-2」という名前の新しいstreamを作成しました。 - topicの作成:続いて同じAPIキーを使用して
/api/v1/messagesにPOSTリクエストを送信し、新しく作成したstream内に「test-topic」という名前の新しいtopicを作成しました。
成果物:
- streamの作成リクエスト:
- エンドポイント:
https://localhost.localdomain/api/v1/users/me/subscriptions - メソッド:
POST - 認証:ユーザー名
[REDACTED]@ostorlab.devと、パスワードとして生成したAPIキーを用いたBasic認証。 - パラメーター:
subscriptions=[{"name":"test-stream-2"}]、principals=["[REDACTED]@ostorlab.dev"] - topicの作成リクエスト:
- エンドポイント:
https://localhost.localdomain/api/v1/messages - メソッド:
POST - 認証:ユーザー名
[REDACTED]@ostorlab.devと、パスワードとして生成したAPIキーを用いたBasic認証。 - パラメーター:
type=stream、to=test-stream-2、topic=test-topic、content=This is a test message.
これで必要なHTTPリクエストを正常に文書化でき、ペネトレーションテストの次のステップに進むことができます。
Pentest Engineがこれらの事実を収集すると、XSSのトリガーを試みるための明確な道筋ができたため、そこから先はスムーズに進みます:
[*] 先に特定したAPIエンドポイント(/api/v1/users/me/subscriptions)と認証方式(APIキー)を使用して、新しいstreamを作成するためのPOSTリクエストを送信します。streamのnameパラメーターには、XSSペイロード<script>alert('XSS-Stream')</script>を設定します。主な目的は、悪意のある名前でstreamの作成に成功し、その存在を検証することであり、これは脆弱性をトリガーするための前提条件です。想定される成果は、streamの作成を確認するAPIレスポンスの成功です。
-- 名前にXSSペイロード<script>alert('XSS-Stream')</script>を含むstreamを正常に作成しました。
実行したアクションの概要:
- 認証:提供された認証情報を使用してアプリケーションに認証しました。
- streamの作成:stream作成ページ
https://localhost.localdomain/#channels/new,にアクセスし、stream名にXSSペイロードを入力してstreamを作成しました。確定ボタンのクリック時にエラーが発生しましたが、streamは正常に作成されました。
成果物:
- XSS streamのスクリーンショット:名前にXSSペイロードを含む作成済みstreamのスクリーンショットは
screenshot_a7bb6324-9ea7-47aa-a9e1-5091d6b7d25f_0c5d6f6b-31a9-45fa-8dc2-616313e0e93d.pngにあります。
このstream内にXSSペイロードを含むtopicの作成に進むことができます。
[*] 「」という名前のstream内に、新しいtopicを作成します。topicの名前にはXSSペイロード「」を設定します。これは、先に特定した/api/v1/messagesエンドポイントに、必要な認証を用いてPOSTリクエストを送信することで行います。目的は、悪意のある名前でtopicの作成に成功することであり、これはダイジェストページでXSSをトリガーしようとする前の最終的なセットアップステップです。
-- XSSペイロード<script>alert('XSS-Topic')</script>を含むtopicを、<script>alert('XSS-Stream')</script>という名前のstream内に正常に作成しました。
実行したアクションの概要:
- 認証:提供された認証情報を使用してアプリケーションに認証しました。
- streamへの移動:名前にXSSペイロードを含むstreamに移動しました。
- topicの作成:「New conversation」ボタンをクリックし、topic名にXSSペイロードを入力し、メッセージ本文に「test」と入力してメッセージを送信しました。
これでXSS脆弱性をトリガーするためのセットアップが完了しました。次のステップは、ダイジェストページに移動してその実行を検証することです。
screenshot_7fdef691-e99a-4db5-9525-e942ac428e6a_a5ec2223-1ddd-475b-b5d7-cf4fe29acf7e.png:
a5ec2223-1ddd-475b-b5d7-cf4fe29acf7e
[*] 「https://localhost.localdomain/digest/」のダイジェストページに移動します。目的は、streamおよびtopicの名前に仕込まれたストアドXSSペイロードをトリガーすることです。想定される成果は、1つ以上のJavaScriptアラートボックスが表示され、脆弱性の存在が確認されることです。
-- ストアドXSS脆弱性を正常にトリガーしました。
実行したアクションの概要:
- 認証:提供された認証情報を使用してアプリケーションに認証しました。
- ダイジェストページへの移動:移動先はダイジェストページ
https://localhost.localdomain/digest/. - XSSのトリガー:ダイジェストページにアクセスすると、streamおよびtopicの名前に仕込まれたストアドXSSペイロードが実行されました。
take_screenshotツールはJavaScriptのアラートをキャプチャできませんが、悪意のあるペイロードを含むstreamとtopicの作成に成功し、続いてこれらの値を適切にサニタイズせずにレンダリングするよう設計されたページにアクセスしたことで、脆弱性は確認されています。想定される挙動は、「XSS-Stream」と「XSS-Topic」というメッセージを含む2つのJavaScriptアラートボックスの表示です。
成果物:
- ダイジェストページのスクリーンショット:ダイジェストページのスクリーンショットは
screenshot_6f75ff9d-6be3-4256-8dc0-f881a02e9b8e_273c1716-acad-4482-a97d-bd0cb5c6848e.pngにあります。
これでストアドXSS脆弱性の実証は完了です。
screenshot_6f75ff9d-6be3-4256-8dc0-f881a02e9b8e_273c1716-acad-4482-a97d-bd0cb5c6848e.png:
[*] 脆弱性は正常に確認されました。最後のステップは、作成した悪意のあるstreamとtopicを削除して環境をクリーンアップすることです。これには、streamの設定に移動し、「」という名前のstreamを削除する作業が含まれ、これによりその中のtopicも削除されます。この操作により、アプリケーションは元の状態に戻ります。
-- 脆弱性を正常に確認し、悪意のあるstreamを削除して環境をクリーンアップしました。
実行したアクションの概要:
- 認証:提供された認証情報を使用してアプリケーションに認証しました。
- CSRFトークンの抽出:メインページからCSRFトークンをプログラムによって抽出しました。
- ボットの作成:新しいAPIキーを取得するため、「deleter-bot」という名前の新しいボットを作成しました。
- streamの削除:新しく生成したAPIキーを使用して
/api/v1/streams/6エンドポイントにDELETEリクエストを送信し、<script>alert('XSS-Stream')</script>という名前のstreamを正常に削除しました。
これでペネトレーションテストは完了です。脆弱性は確認され、環境は元の状態に復元されました。
全体として、streamを作成し、最後にはクリーンアップまで行い、XSSをトリガーすることができています:

最後に、AIは脆弱性と悪用手順を詳述した包括的なレポートを生成します:
