Langflow: `X-Forwarded-For` を悪用したIPスプーフィングによる、リモートからの設定書き込みの脆弱性
**説明**:
### 概要
Model Context Protocol (MCP) の設定インストールエンドポイント (`POST /api/v1/mcp/project/{project_id}/install`) におけるIPスプーフィングの脆弱性により、認証されたリモート攻撃者が、"ローカルのみ" のアクセス制限を回避することが可能でした。 攻撃者は、偽の `X-Forwarded-For: 127.0.0.1` ヘッダーを送信することで、サーバーがリクエストをローカルホストからのものとして扱うようにさせ、サーバーのファイルシステム上の MCP クライアント設定ファイルを書き込み/上書きすることができました。
### 詳細
この脆弱性は、`install_mcp_config` のローカル接続のみを許可する制限を適用するために使用される `get_client_ip` ヘルパー関数に存在していました。 この関数は、`X-Forwarded-For` の最も左側のエントリ(完全にクライアントによって制御される)を、信頼できるプロキシを経由したかどうかを確認することなく、無条件に信頼していました。
**脆弱なコード (commit `d3d06be8e5` で導入され、v1.5.0 で初めてリリース):** `src/backend/base/langflow/api/v1/mcp_projects.py`
```python
def get_client_ip(request: Request) -> str:
# X-Forwarded-For ヘッダーの確認 (プロキシの背後にある場合に一般的)
forwarded_for = request.headers.get("X-Forwarded-For")
if forwarded_for:
# クライアントのIPアドレスは、リストの最初のもの
return forwarded_for.split(",")[0].strip()
if request.client:
return request.client.host
return "255.255.255.255"
@router.post("/{project_id}/install")
async def install_mcp_config(
project_id: UUID,
body: MCPInstallRequest, # {client: str, transport: "sse" | "streamablehttp" | None}
request: Request,
current_user: CurrentActiveMCPUser,
):
client_ip = get_client_ip(request)
if not is_local_ip(client_ip):
raise HTTPException(status_code=500, detail="MCPの設定は、ローカル接続からのみインストールできます")
...
```
**修正点と元のレポートとの比較:** このエンドポイントが受け入れるリクエストボディは、`MCPInstallRequest {client: str, transport: str | None}` です ( `src/backend/base/langflow/api/v1/schemas/__init__.py` )。 `mcp_path` フィールドは存在せず、宛先パスは攻撃者によって指定されることはありません。 `install_mcp_config` は、`get_config_path(body.client)` を介して、書き込み先を解決します。これは、サーバープロセスのホームディレクトリ内の、あらかじめ定義された、限られた数の、OSごとの開発ツール設定パスのいずれかになります。具体的には、`~/.cursor/mcp.json` (Cursor)、`~/.codeium/windsurf/mcp_config.json` (Windsurf)、またはClaude Desktopの設定ファイル (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS、`%APPDATA%\Claude\claude_desktop_config.json` on Windows/WSL) です。したがって、影響は「攻撃者によって操作された内容が、これらの固定されたファイルのうちのいずれかに書き込まれる」というものであり、任意のパスへの書き込みではありません。
### 実証
1. 有効なアクセス トークンを取得するために認証を行います。
2. 攻撃者がアクセスできる `project_id` を特定します。
3. 以下のコマンドを実行します。
```bash
curl -X POST "http://<server-ip>:7860/api/v1/mcp/project/<project_id>/install" \
-H "Authorization: Bearer <token>" \
-H "X-Forwarded-For: 127.0.0.1" \
-H "Content-Type: application/json" \
-d '{"client": "cursor"}'
```
4. サーバーが `200 OK` を返し、攻撃者によって操作されたMCPサーバーのエントリを、ホスト上の `~/.cursor/mcp.json` に書き込み/上書きします。これは、リクエストがリモートのローカルでないアドレスから送信されたにもかかわらず発生します。
### 影響
認証されたリモートからの設定書き込みにより、ホスト上のIDE/MCPクライアント設定ファイルのいずれかに書き込みが行われます。これにより、以下のようなことが可能になります。
- 攻撃者が制御するMCPサーバーに、ローカルの開発者が後でそのIDEを開いた際に接続するように、Cursor/Windsurf/Claude Desktopの設定ファイルに悪意のあるMCPサーバー定義を注入する。
- これらのツールの既存のMCP構成を破壊または破損させる。
- 意図されたネットワーク境界制御(「ローカルのみ」)を回避する。
### 状態:すでに修正済み
この特定の脆弱性(単一行でカンマ区切りの`X-Forwarded-For`を偽装する手法、デフォルト設定)は、以下のバージョンで修正されました。
- **修正PR:** langflow-ai/langflow#13915 — "fix(security): MCPインストール時のロカリティチェックにおいて、`X-Forwarded-For`を信用しないようにする"。これは、langflow-ai/langflow#13530におけるセキュリティ強化の取り組みの一部として実装されました。
- **修正点:** `get_client_ip`関数は、デフォルトで実際のTCPピア(`request.client.host`)を使用し、`X-Forwarded-For`を完全に無視します。ただし、オペレーターが`rate_limit_trust_proxy`設定で明示的に有効にした場合を除きます(デフォルトは`False`)。有効にした場合、最も右側のエントリを使用し、`langflow.services.rate_limit.service.get_client_ip`の動作と一致します。
- **リリースバージョン:** v1.11.0、およびv1.10.3に遡って適用(langflow-ai/langflow#14071)。
- **関連する修正:** より限定的な、関連する脆弱性(オペレーターが`rate_limit_trust_proxy=true`を設定し、かつ`X-Forwarded-For`を単一のカンマ区切りではなく、複数のヘッダー行で送信するプロキシ(例:HAProxyの`option forwardfor`)を使用している場合にのみ到達可能)は、langflow-ai/langflow#14425で個別に修正され、v1.11.3でリリースされました。これは、デフォルトの設定(`rate_limit_trust_proxy`はデフォルトで`False`)には影響しません。
- このレポートは、GHSA-qvvj-g573-9638とほぼ同一の内容であり、同じ根本原因に対する修正が含まれています。
### 影響を受けるバージョン
- 脆弱なエンドポイント/ヘルパーは、**v1.5.0**で導入されました(langflow-ai/langflow#8271、"特定のクライアントに対して、ワンクリックインストールをMCPサーバーに追加する"、2025-07-08)。v1.5.0より前のバージョンには、このエンドポイントが含まれておらず、この問題の影響を受けません。
- **脆弱性あり:** >= 1.5.0、< 1.10.3(およびメインラインの< 1.11.0)。
- **修正済み:** v1.10.3(遡って適用)およびv1.11.0以降。
影響を受けるパッケージ:
- pip (Python) langflow: 影響を受けるバージョン >= 1.5.0、< 1.10.3、修正は1.10.3で。