PraisonAI: MCP HTTPストリームのデフォルト設定では認証が行われないため、ツールの列挙や、検証されていないツール呼び出しの脆弱性が存在する。
## 概要
PraisonAIのMCP HTTPストリームサーバーは、APIキーが設定されている場合にのみリクエストを認証します。CLIのデフォルト設定では、`--api-key`が`None`に設定されているため、`praisonai mcp serve --transport http-stream`を実行すると、MCPの全機能が認証なしで公開されます。`Authorization`ヘッダー(および`Origin`ヘッダー)のないリクエストでも、`initialize`や`tools/list`(約50種類のツール)を実行でき、ディスパッチャーは、広告されている`inputSchema`に対する検証なしに、ツール呼び出しの引数をハンドラに転送します。実行環境での確認により、認証なしの`initialize`/`tools/list`と、ディスパッチャーのスキーマバイパスが確認されました。これは、バージョン4.6.63において、リモートコード実行(RCE)やファイル読み込みを直接引き起こすものではありません。`workflow.run`/`workflow.run_file`は、実行環境で問題がないことが確認されました(アダプターの互換性問題)。深刻度は中~高。
## 詳細
### 影響を受けるコンポーネント
- パッケージ: `praisonai` バージョン 4.6.63。ファイル: `src/praisonai/praisonai/mcp_server/transports/http_stream.py`、`mcp_server/cli.py`、`mcp_server/server.py`(ディスパッチャー)。
### 脆弱なコード / 根本原因
パス:
`src/praisonai/praisonai/mcp_server/transports/http_stream.py`
関数:
`mcp_post` / `_validate_origin`
コードスニペット:
```python
if self.api_key: # api_keyが設定されている場合にのみ認証が適用される
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer ") or auth_header[7:] != self.api_key:
return JSONResponse({"error": "Unauthorized"}, status_code=401)
# _validate_origin: Originヘッダーが存在しない場合にTrueを返す
```
問題点: `api_key=None`の場合、認証チェックは実行されず、`Origin`ヘッダーがない場合でも許可されるため、ブラウザ以外のクライアント(curl/Burpなど)がブロックされません。
パス:
`src/praisonai/praisonai/mcp_server/cli.py`
関数:
`cmd_serve` (argparse)
コードスニペット:
```python
parser.add_argument("--api-key", default=None) # デフォルトでは認証なし
```
パス:
`src/praisonai/praisonai/mcp_server/server.py`
関数:
`_handle_tools_call`
以下はコードスニペットです。
```python
result = await tool.handler(**arguments) # 引数はinputSchemaの検証なしでhandlerに渡される
```
問題点:攻撃者が制御する`arguments`がそのままhandlerに渡されます。ディスパッチャは、ツールの定義された`inputSchema`に対して、これらの引数を検証しません。未定義のキーが拒否されるのは、handler自身のPythonのシグネチャによるものです。
### 攻撃の流れ
1. ユーザーが`praisonai mcp serve --transport http-stream`(`--api-key`なし)を実行します。
2. 攻撃者(認証なし、Originなし)が`initialize`リクエストを送信し、セッションを取得します。次に、`tools/list`リクエストを送信し、約50のツールを列挙します。最後に、`tools/call`リクエストを送信し、引数が検証なしで渡されます。
### 既存の保護が回避される理由
認証はオプションです(APIキーが設定された場合にのみ有効になります)。`Origin`ヘッダーがない場合でも許可されます。また、ディスパッチャは`inputSchema`を強制しません。
### セキュリティ境界
認証されていないアクセスが、MCPツールのインターフェースに可能です。デフォルトのバインド先は`127.0.0.1`です(ローカルプロセスまたはマルチユーザーホスト。リモートアクセスは`--host 0.0.0.0`オプションを使用した場合のみ)。
### 影響範囲(過大評価しない)
- `praisonai.workflow.run` / `workflow.run_file`は、**4.6.63で実行時に拒否されます**。アダプタが必須の`config_list`引数を省略した`AgentsGenerator(...)`を呼び出すため、実行またはファイルオープン前にエラーが発生します。他のツールアダプタでも、実行時にエラーが発生する場合があります。これらのツールを介した認証されていないリモートコード実行や任意のファイルオープンは、現在のバージョンでは困難です。
- MCPの`knowledge.add`によるファイル読み込み機能は、現在正常に動作しません(詳細は`FT-01_Knowledge_FileRead_Negative_Report.md`を参照)。
## 実証
### 環境
ローカルの実行環境(`127.0.0.1:18090`)で動作する、認証なしのMCP HTTPストリームサーバー(`api_key=None`)。実行可能なアセットは`PraisonAI-Runtime-Repro\runtime-files\`(`docker-compose.mcp.yml`)。MCPリクエストは`Accept: application/json`とヘッダー`Mcp-Session-Id`を使用します。
### 再現手順
1. `MCP-Initialize`: `POST /mcp`リクエストを送信して初期化します(Authorizationヘッダーなし)。レスポンスは`200`と`mcp-session-id`です。
2. `MCP-Tools-List-NoAuth`: 上記で取得したセッションIDを使用して、`POST /mcp`リクエストで`tools/list`を呼び出します。レスポンスは`200`と約50のツールです。
3. `MCP-Schema-Bypass`: 未定義の追加引数(`__undeclared_evil_param__`)を指定して、`tools/call`リクエストを送信します。
### 期待される結果
通信には認証が必要であり、ディスパッチャは `inputSchema` に対して引数を検証します。
### 実際の状況
- `initialize` および `tools/list` は、認証なしで、かつ Origin ヘッダーなしで成功します。
- 未定義の引数がハンドラに到達します (`got an unexpected keyword argument '__undeclared_evil_param__'`)。これは、ディスパッチャでスキーマ検証が行われていないことを示しています。
### スクリーンショット
<img width="1544" height="798" alt="03-MCP-Schema-Bypass" src="https://github.com/user-attachments/assets/5a4cb764-9428-487d-b4e0-2854cbda7fb7" />
<img width="1538" height="793" alt="02-MCP-Tools-List-NoAuth" src="https://github.com/user-attachments/assets/6356af71-867f-4fbc-a994-c7ca338fd2aa" />
### スクリーンショット
**認証なしの MCP initialize**
`/mcp` への `initialize` メソッドを使用した POST リクエストは、`Authorization` ヘッダーなしで成功します。サーバーは HTTP 200 OK を返し、MCP の機能を提供し、認証されていないクライアントに `mcp-session-id` を発行します。
<img width="1546" height="804" alt="01-MCP-Initialize-NoAuth" src="https://github.com/user-attachments/assets/2a62ee6b-99d3-4a38-a752-bfe6165c8c04" />
**認証なしの MCP tools/list**
初期化後、同じ認証されていない MCP セッションは、発行された `Mcp-Session-Id` のみを使用して `tools/list` を呼び出すことができます。サーバーは HTTP 200 OK を返し、ツールの名前、スキーマ、および注釈を提供します。
<img width="1538" height="793" alt="02-MCP-Tools-List-NoAuth" src="https://github.com/user-attachments/assets/f55189ff-13aa-4c36-a617-3d2ee4a52a84" />
**MCP ツール呼び出しにおけるスキーマ回避**
認証されていない MCP クライアントは、ツールのスキーマで宣言されていない追加の引数を使用して `tools/call` を呼び出します。ディスパッチャ層でスキーマ違反の入力を拒否する代わりに、予期しないパラメータが Python ハンドラに到達し、`unexpected keyword argument` エラーが発生します。これにより、ツールの呼び出しに対する入力スキーマの適用が不完全であることが確認されます。
<img width="1544" height="798" alt="03-MCP-Schema-Bypass" src="https://github.com/user-attachments/assets/d3f36e50-2363-4eb2-8b3c-985ff0e27f6e" />
## 影響
認証なしでのツール列挙とツール呼び出し機能の存在。LLMのキー/コストの不正利用や、動作するツールを介したデータへのアクセス(現状、いくつかの不具合のあるアダプターとデフォルトのループバック設定により影響は限定的)。バージョン4.6.63では、認証なしでのリモートコード実行やファイル読み込みは確認されていません。
影響を受けるパッケージ:
- pip (Python) praisonai: 影響を受けるバージョンは4.6.77以下、4.6.78で修正済み。