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へ更新し、コンテナを再作成します。
- 修正済みイメージを取得し、composeファイルのタグを更新します。
seadocとsdoc-serverコンテナを再作成します。イメージを取得してもコンテナを再作成しなければ、古いプロセスが動き続けます。docker inspectで新しいタグを確認します。- 公開
.sdocリンクと公開Wikiを確認し、不要なものを削除します。 - 更新できない場合は、リバースプロキシでコンバーターのエンドポイントをブロックするか、すべての公開
.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ブロックが実行されず、配置されたファイルがディスクに残ります。
どのように悪用されますか?
- 攻撃者は公開
.sdoc共有リンクを開き、ページのソースからseadocAccessTokenの値を読み取ります。このトークンはその文書に対する有効な匿名資格情報です。 - 攻撃者は、
__init__.pyを含むZIPを返し、接続を受け入れて応答せず、要求されるとcharset=cp273でHTTP 500を返すサーバーを実行します。 - 攻撃者は
/sdoc-server/converter/api/v1/confluence-to-wiki/へ、/usr/lib/python3.12/encodings/cp273aaaaaaaaaのような絶対filename、ZIPを配信するdownload_url、停止するupload_urlを付けてPOSTします。コンバーターはZIPをPythonのencodingsパッケージへ展開します。 - 攻撃者は
/sdoc-server/converter/api/v1/file-convert/へ、500とcharset=cp273を返すupload_urlを付けてPOSTします。コンバーターは応答本文をログに記録し、Response.textが宣言されたcharsetでデコードします。 - CPythonは
encodings.cp273を遅延インポートします。FileFinderはモジュールcp273.pyより先にディレクトリパッケージcp273/__init__.pyを確認するため、配置されたファイルがコンバータープロセス内で実行されます。
研究者の公開分析には2つのリクエストと攻撃者サーバーが含まれます。最初のリクエストはファイルがディスクにある間ブロックされ、2番目のリクエストがインポートを起動します。分析のマーカーコマンドはコンバーターコンテナからuid=0 (root)を書き込みました。
この経路は再起動を必要としません。インポートは実行中プロセスでオンデマンドに行われるためです。唯一の前提条件は、匿名トークンを提供する公開.sdoc共有リンクまたは公開Wikiです。
攻撃の流れは?
匿名共有トークンが署名だけの検査を通り、絶対ファイル名がコンバーターのライブラリパスへPythonパッケージを書き込み、charsetエラーによってCPythonがそれをインポートして実行します。
匿名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-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日確認)