79%対51%:独立系ネオバンクはアプリセキュリティで後れ。 レポートを読む

ディープダイブ

ディープダイブ

Seafile SeaDoc:公開共有リンク経由のRCE(CVE-2026-129167)

SeaDocの匿名共有トークンがコンバーターの全エンドポイントに到達し、絶対パスによってPythonパッケージが配置されて実行されます。2.0.11と3.0.8で修正されました。

要点

  • SeaDocのConfluenceインポートの絶対パスにより、公開.sdoc共有リンクを持つ匿名の攻撃者が、コンバーターのライブラリパスへPythonパッケージを書き込んで実行できます。Haiwenは2026年11月2日にサーバーを修正しました。
  • SeaDoc 2.0.4から2.0.10、および3.0.0から3.0.7が影響を受けます。インスタンスに公開.sdoc共有リンクまたは公開Wikiが必要です。
  • CVEレコードはCVSS v4.0 9.2 Criticalと評価しています。悪用は報告されておらず、2026年11月14日時点でCISAのKnown Exploited Vulnerabilities(KEV)カタログにもありません。
  • SeaDoc 2.0.11または3.0.8以降へ更新してください。コンバーターのコミットと回避策は「修正方法」を参照してください。

影響を受けますか?

次の条件がすべて当てはまる場合に影響を受けます。

  • デプロイがSeaDoc 2.0.4から2.0.10、または3.0.0から3.0.7を実行している。
  • インスタンスに公開.sdoc共有リンクまたは公開Wikiがある。共有ページが埋め込む匿名トークンが、攻撃で使う資格情報です。
  • SeaDocコンバーターがSeafileのフロントエンド経由で到達できる。公式Docker Composeデプロイは/sdoc-server/converter/の下に公開します。
  • デプロイが2026年11月2日より前のものである。Haiwenはこの日にクローズドソースのsdoc-serverコンポーネントを修正したと述べています。

公開.sdoc共有リンクも公開Wikiもないデプロイは、ここで説明する匿名トークンの経路を露出しません。SeaDocトークンを取得できるサインイン済みユーザーは、依然としてコンバーターに到達できます。コンバーターは有効なSeaDocトークンをすべて受け入れるため、1つの文書に発行されたトークンがコンバーターの全エンドポイントに到達します。前提条件は攻撃経路の条件として扱い、安全な既定状態とはみなしません。

ブランチ 影響を受けるバージョン 修正バージョン
SeaDoc 2.0 2.0.4から2.0.10 2.0.11
SeaDoc 3.0 3.0.0から3.0.7 3.0.8

執筆時点の最新イメージは2.0.13と3.0.9です。どちらも修正を含みます。

実行中のイメージと公開共有を確認します。

# Image tag of the running SeaDoc container
docker inspect --format '{{.Config.Image}}' <seadoc-container>

# Images present on the host
docker image ls | grep sdoc-server

# Public links, in the Seafile web interface
# Open each library, then Sharing, and review the links that are public

公式デプロイではイメージタグが信頼できるバージョン確認です。composeファイルが2.0-latestや3.0-latestなどのタグを固定している場合、実行中コンテナのバージョンが変わらなくてもタグが移動することがあります。

修正方法

SeaDocを2.0.11または3.0.8、あるいは現在の2.0.13または3.0.9へ更新し、コンテナを再作成します。

  1. 修正済みイメージを取得し、composeファイルのタグを更新します。
  2. seadocとsdoc-serverコンテナを再作成します。イメージを取得してもコンテナを再作成しなければ、古いプロセスが動き続けます。
  3. docker inspectで新しいタグを確認します。
  4. 公開.sdocリンクと公開Wikiを確認し、不要なものを削除します。
  5. 更新できない場合は、リバースプロキシでコンバーターのエンドポイントをブロックするか、すべての公開.sdocリンクを削除してすべてのWikiを非公開にします。回避策は匿名トークンの経路を除くだけで、絶対パスの問題は修正しません。

オープンソースのseadoc-converterの修正は、2026年10月29日のコミットcb43162eです。文字列でないfilename、.html.zipで終わらない値、/、\、NULバイトを含む値を拒否します。空のspace_key、.、..も拒否します。

sdoc-serverコンポーネントはクローズドソースです。Haiwenは2026年11月2日のメールで、SeaDoc 2.0と3.0を修正したと研究者に伝えました。その半分の修正を説明する公開コミットや勧告はありません。

根本原因は何ですか?

コンバーターはSeaDoc JWTの署名だけを確認してすべて受け入れ、その後、攻撃者が制御するファイル名をZIP展開の絶対パスとして使い、アップロードが停止している間も展開したファイルを保持していました。

SeaDocは3つのプロセスで構成されます。seahubは認証とページ描画、sdoc-serverは共同編集、seadoc-converterは文書変換を担当します。3つはHMACキーJWT_PRIVATE_KEYを共有します。訪問者が公開.sdoc共有リンクを開くと、seahubは匿名プレビュー用のJWTをページに埋め込みます。

コンバーターのcheck_auth_token()はconfig.SEADOC_PRIVATE_KEYで署名を確認してtrueを返します。トークンが発行された文書や、トークンが持つ権限は確認しません。

def check_auth_token(req):
    auth = req.headers.get('Authorization', '').split()
    if not auth or auth[0].lower() != 'token' or len(auth) != 2:
        return False

    token = auth[1]
    if not token:
        return False

    private_key = config.SEADOC_PRIVATE_KEY
    try:
        jwt.decode(token, private_key, algorithms=['HS256'])
    except (jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
        logger.exception(e)
        return False

    return True

匿名トークンは読み取り専用ですが、コンバーターはpermissionフィールドを無視します。そのためトークンは/api/v1/confluence-to-wiki/に到達し、このエンドポイントがfilenameを受け取ってos.path.joinでパスを構築します。

    underscore_index = filename.rfind('_')
    if underscore_index != -1:
        space_key = filename[underscore_index + 1:-len('.html.zip')]
    elif 'Confluence-space-export-' in filename:
        wiki_name = filename[:-len('.html.zip')]
        space_key = filename[len('Confluence-space-export-'):-len('.html.zip')]
    else:
        wiki_name = filename[:-len('.html.zip')]
        space_key = wiki_name
    try:
        extract_dir = '/tmp/wiki'
        space_dir = os.path.join(extract_dir, space_key)
        zip_file_path = os.path.join(extract_dir, filename)
        if not os.path.exists(extract_dir):
            os.mkdir(extract_dir)
        # Deploy on two machines
        is_same_machine = True
        if not os.path.exists(space_dir):
            os.mkdir(space_dir)
            is_same_machine = False
            response = requests.get(download_url)
            with ZipFile(BytesIO(response.content), 'r') as zip_ref:
                zip_ref.extractall(space_dir)

os.path.joinの2番目の引数がスラッシュで始まると、Pythonは1番目の引数を破棄します。/usr/lib/python3.12/encodings/cp273のようなspace_keyは、space_dirとzip_file_pathを絶対パスにします。コンバーターはディレクトリを作成し、攻撃者のZIPをそこへ展開します。

クリーンアップはprocess_zip_file()が戻った後にのみfinallyブロックで実行されます。この関数は結果をタイムアウトなしでupload_urlへ送信します。接続を受け入れて応答しないアップロード先ではリクエストが停止したままになり、finallyブロックが実行されず、配置されたファイルがディスクに残ります。

どのように悪用されますか?

  1. 攻撃者は公開.sdoc共有リンクを開き、ページのソースからseadocAccessTokenの値を読み取ります。このトークンはその文書に対する有効な匿名資格情報です。
  2. 攻撃者は、__init__.pyを含むZIPを返し、接続を受け入れて応答せず、要求されるとcharset=cp273でHTTP 500を返すサーバーを実行します。
  3. 攻撃者は/sdoc-server/converter/api/v1/confluence-to-wiki/へ、/usr/lib/python3.12/encodings/cp273aaaaaaaaaのような絶対filename、ZIPを配信するdownload_url、停止するupload_urlを付けてPOSTします。コンバーターはZIPをPythonのencodingsパッケージへ展開します。
  4. 攻撃者は/sdoc-server/converter/api/v1/file-convert/へ、500とcharset=cp273を返すupload_urlを付けてPOSTします。コンバーターは応答本文をログに記録し、Response.textが宣言されたcharsetでデコードします。
  5. CPythonはencodings.cp273を遅延インポートします。FileFinderはモジュールcp273.pyより先にディレクトリパッケージcp273/__init__.pyを確認するため、配置されたファイルがコンバータープロセス内で実行されます。

研究者の公開分析には2つのリクエストと攻撃者サーバーが含まれます。最初のリクエストはファイルがディスクにある間ブロックされ、2番目のリクエストがインポートを起動します。分析のマーカーコマンドはコンバーターコンテナからuid=0 (root)を書き込みました。

この経路は再起動を必要としません。インポートは実行中プロセスでオンデマンドに行われるためです。唯一の前提条件は、匿名トークンを提供する公開.sdoc共有リンクまたは公開Wikiです。

攻撃の流れは?

匿名共有トークンが署名だけの検査を通り、絶対ファイル名がコンバーターのライブラリパスへPythonパッケージを書き込み、charsetエラーによってCPythonがそれをインポートして実行します。

flowchart LR A["公開.sdoc共有ページが
匿名JWTを埋め込む"] --> B["コンバーターは
JWT署名だけを確認"] B --> C["confluence-to-wikiが
全エンドポイントでトークンを受け入れる"] C --> D["絶対filenameが/tmp/wikiの外へ
os.path.joinで書き込む"] D --> E["ZIPがPythonの
encodingsパッケージへ展開される"] E --> F["停止したアップロードが
finallyのクリーンアップを防ぐ"] F --> G["charset=cp273でCPythonが
配置されたパッケージをインポート"] G --> H["コンバータープロセスで
コードが実行される"]

攻撃者に何ができますか?

公開共有トークンを持つ未認証の攻撃者が、SeaDocコンバータープロセスでコードを実行できます。公式コンテナはこのプロセスをrootで実行します。

影響には次が含まれます。

  • コンバーターが到達できる文書の読み取りと変更。設定内の共有HMACキーも含みます。
  • そのキーを使った、他の文書やサービス向けSeaDocトークンの偽造。
  • コンバーターコンテナがアクセスできるネットワークとマウント済みボリュームへの到達。
  • Seafileデプロイへの足がかりとしてのコンバーターの利用。

条件は影響の一部です。

  • インスタンスが公開.sdoc共有リンクまたは公開Wikiを露出している必要があります。
  • コンバーターが攻撃者のネットワークから到達できる必要があります。
  • デプロイが2026年11月2日より前に構築された影響バージョンのSeaDocである必要があります。
  • 攻撃者にアカウントもユーザー操作も不要です。

研究者は公式Docker Composeラボでuid=0 (root)を実証しました。コードはコンバーターコンテナで実行され、既定ではSeafileアプリケーションサーバー上ではありません。ホストに到達できるかはコンテナのマウントとネットワークによります。

修正が機能する理由

コンバーターの修正は/tmp/wikiの外へ出られるファイル名を拒否するため、os.path.joinが絶対的な展開パスを生成できなくなります。

修正コミットcb43162eはconfluence_to_wiki()の先頭の検証を変更し、space_keyの導出後に検査を追加します。

-    if not filename:
+    if (not isinstance(filename, str)
+            or not filename.endswith('.html.zip')
+            or any(char in filename for char in ('/', '\\', '\x00'))):
         return {'error_msg': 'path invalid.'}, 400
...
     else:
         wiki_name = filename[:-len('.html.zip')]
         space_key = wiki_name
+
+    if space_key in ('', '.', '..'):
+        return {'error_msg': 'path invalid.'}, 400

     try:
         extract_dir = '/tmp/wiki'
         space_dir = os.path.join(extract_dir, space_key)

この検査はfilename内のスラッシュ、バックスラッシュ、NULバイトを拒否するため、そこから導出されるspace_keyは単一のパス要素になります。space_keyの検査は、通常の子パスの外を指す3つの値を拒否します。展開は/tmp/wikiの下に留まります。

これはオープンソースのコンバーターにおける部分的な修正です。匿名の読み取り専用トークンがコンバーターの全エンドポイントへ到達するのを止めるクローズドソースのsdoc-serverの変更は公開されていません。Haiwenはコンポーネントを修正したと述べていますが、トークンのスコープ変更はレビューできません。

侵害されたかどうかを確認するには?

ベンダーの侵害指標は公開されていません。確認はコンバーターへの2つのリクエストと、Pythonライブラリパスに配置されたファイルを対象にします。

  • リバースプロキシとSeafileのアクセスログで、filenameが/で始まるか..を含む/sdoc-server/converter/api/v1/confluence-to-wiki/へのPOSTを検索します。
  • upload_urlが自社インフラの外を指す/sdoc-server/converter/api/v1/file-convert/へのPOSTを検索します。
  • コンバーターコンテナで、インストールしていないパッケージ、特にcp273のようなコーデック名のディレクトリをPythonのencodingsディレクトリで探します。
  • /tmp/wikiに残ったディレクトリやZIPファイルを確認します。
  • コンバーターコンテナの/root、/tmp、アプリケーションディレクトリに予期しないファイルがないか確認します。

研究者の概念実証は/root/pwnedを書き込みますが、攻撃者は別のものを書き込めます。展開前に失敗したリクエストは痕跡を残さない場合があり、コンバーターはアップロード応答の本文だけを記録し、リクエスト全体は記録しません。

年表

  • 2026-10-24:研究者が分析を公開する。
  • 2026-10-29:seadoc-converterの修正コミットcb43162eが作成される。
  • 2026-11-02:Haiwenがクローズドソースのsdoc-serverコンポーネントを修正したと研究者に伝える。
  • 2026-11-13:CVE-2026-129167が17:21 UTCに公開される。

分析からCVEまで20日
図1:分析は2026年10月24日に公開され、コンバーターの修正は10月29日に続きました。ベンダーは11月2日にクローズドコンポーネントを修正し、CVEは11月13日に公開されました。出典:研究者の分析、修正コミット、CVEレコード、2026年11月14日確認

よくある質問

CVE-2026-129167は実環境で悪用されていますか?

悪用は報告されていません。2026年11月14日時点でCISAのKEVになく、研究者の分析はキャンペーンではなくラボでの再現を説明しています。

悪用にアカウントは必要ですか?

いいえ。攻撃者には公開.sdoc共有リンクまたは公開Wikiが必要で、共有ページに埋め込まれた匿名トークンを読み取ります。Seafileアカウントは不要です。

匿名トークンは攻撃を1つの文書に限定しますか?

いいえ。コンバーターはトークンの署名だけを確認します。permissionと文書のフィールドは無視されるため、匿名トークンはコンバーターの全エンドポイントに到達します。

脆弱なのはSeafileサーバーですか、SeaDocだけですか?

欠陥はSeaDocコンバーターと、SeaDocサービスが共有するトークン検査にあります。SeaDocのないSeafileデプロイはこのCVEの影響を受けません。攻撃者のコードを実行するのはコンバーターコンテナです。

どのバージョンで修正されますか?

SeaDoc 2.0.11と3.0.8が最初の修正版です。現在の2.0.13と3.0.9も修正を含みます。クローズドソースのsdoc-serverコンポーネントは2026年11月2日に修正されました。

修正は完全ですか?

オープンソースのコンバーターの修正は絶対パスの経路を閉じます。クローズドソースのsdoc-serverにおけるトークンスコープの変更は公開されていないため、その完全性はコードから確認できません。

参考資料

  • CVE-2026-129167レコード、VulnCheck CNA、2026-11-13公開、CVSS v4.0 9.2:https://www.cve.org/CVERecord?id=CVE-2026-129167(2026年11月14日確認)
  • Wu Yulun「Seafile Vulnerability Report」、2026-10-24公開、根本原因、再現、2つのリクエスト:https://vul.yulun.ac.cn/20261024/seafile/(2026年11月14日確認)
  • seadoc-converterの修正コミットcb43162e、2026年10月29日、ファイル名とspace_keyの検証:https://github.com/haiwen/seadoc-converter/commit/cb43162e8bc2991a2900c8acf85090358a30e516(2026年11月14日確認)
  • 脆弱なseadoc-converterのソースbb6cf0f、seadoc_converter/server/apis.py、check_auth_token()とconfluence_to_wiki():https://github.com/haiwen/seadoc-converter/blob/bb6cf0fb9ebc6bb05c9161acbfd6289737ec2cd6/seadoc_converter/server/apis.py(2026年11月14日確認)
  • 脆弱なseadoc-converterのソースbb6cf0f、seadoc_converter/converter/utils.py、タイムアウトなしのアップロード:https://github.com/haiwen/seadoc-converter/blob/bb6cf0fb9ebc6bb05c9161acbfd6289737ec2cd6/seadoc_converter/converter/utils.py(2026年11月14日確認)
  • Docker Hub、seafileltd/sdoc-serverのタグ、2.0.11、3.0.8、2.0.13、3.0.9:https://hub.docker.com/r/seafileltd/sdoc-server/tags(2026年11月14日確認)
  • Docker Hub、seafileltd/seafile-mcのプル数、20,336,673:https://hub.docker.com/r/seafileltd/seafile-mc(2026年11月14日確認)
  • CISA Known Exploited Vulnerabilitiesカタログ、2026年11月14日にCVE-2026-129167を確認(未掲載):https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json(2026年11月14日確認)