PraisonAI: Crawl4AI/Chromium バックエンドも、`web_crawl` の SSRF 検証回避の影響を受けています。
## 概要
PRAI-05 で報告されている DNS リバインディング/リダイレクトによる SSRF 検証回避は、**httpx/urllib バックエンドに限定されません**。`crawl4ai` (Playwright を使用したヘッドレス Chromium) がインストールされている場合、`web_crawl` は自動的に `provider=crawl4ai` を選択し、ヘッドレスブラウザが DNS を再解決し、リダイレクトを独自に処理します。この際、接続ごとの SSRF 対策は行われません。実行環境で確認されたのは、DNS リバインディングとリダイレクトの両方を使用して、内部のカナリア情報を読み取ることができるという点です (`provider: "crawl4ai"`)。状況: PRAI-05 への補足として、実行環境での確認が完了しました。
## 詳細
### 影響を受けるコンポーネント
- パッケージ: `praisonaiagents` バージョン 4.6.63。`crawl4ai` + Playwright がインストールされている場合に選択されるバックエンド。
- ファイル: `src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py` (`_crawl_with_crawl4ai`) および `src/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py` (スタンドアロンのクローリング機能)。
### 脆弱なコード / 根本原因
パス:
`src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py`
関数:
`_crawl_with_crawl4ai`
コードスニペット:
```python
async with AsyncWebCrawler() as crawler:
for url in urls:
result = await crawler.arun(url=url) # ヘッドレスブラウザ: DNS を再解決し、リダイレクトに従う
```
パス:
`src/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py`
関数:
`Crawl4AITools.crawl` / `crawl4ai`
コードスニペット:
```python
result = await crawler.arun(url=url, config=config) # `_is_safe_crawl_url` は使用されず、接続ごとの検証も行われない
```
問題点: SSRF チェックは、`web_crawl()` 内で初期の URL 文字列に対して行われる `_is_safe_crawl_url()` という単一の事前フェッチのみです (PRAI-05 を参照)。その後、ヘッドレスブラウザが独立して DNS を解決し、接続し、ブラウザ内でリダイレクトに従います。解決された IP アドレスの固定や、ホップごとの/接続ごとの検証は行われません。入力 (`urls`) は攻撃者/エージェントによって制御され、シンクは `crawler.arun(url=...)` です。この対策は、DNS リバインディング (TOCTOU) やリダイレクト (ブラウザ内で追跡される) によって回避されます。
### 攻撃の流れ / バイパスの理由 / セキュリティ境界
PRAI-05と同様です。ガードの処理とブラウザの接続の間にTOCTOU(Time-of-Check to Time-of-Use)の脆弱性があり、リダイレクトがブラウザ内で処理され、内部からのレスポンスがクローリングの`content`として返されます。詳細はPRAI-05を参照してください。
## 概念実証
### 環境
`crawl4ai`とPlaywright Chromiumが、専用のランタイムコンテナ(`127.0.0.1:18081`)にインストールされています。同じように制御された内部のカナリア環境を使用し、DNSの再バインディングを行っています。実行可能なファイルは`PraisonAI-Runtime-Repro\runtime-files\`にあります(`docker-compose.crawl.yml`)。
### 再現手順
1. `SSRF-04-01-Crawl4AI-DNS-Rebind-Trigger` → `127.0.0.1:18081`:
```http
POST /tool/web_crawl HTTP/1.1
Host: 127.0.0.1:18081
Content-Type: application/json
{"url":"http://rebind.lab:8081/secret"}
```
2. リダイレクトのバリエーション `SSRF-04-03-Crawl4AI-Redirect-Readback`: `{"url":"http://redirector:8082/redirect-to-internal"}`。
### 期待される結果
クローリングのバックエンドは、DNSタイミングやリダイレクトに関わらず、内部の宛先を拒否するはずです。
### 実際の状況
HTTP 200が返され、`"provider":"crawl4ai"`、レスポンスの`content`には、DNSの再バインディングとリダイレクトのペイロードの両方で、`PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91`と`FAKE_INTERNAL_TOKEN_DO_NOT_USE_7f3a91`が含まれています。
### スクリーンショット
**Crawl4AI DNS再バインディングによる情報漏洩**
攻撃者が制御する`rebind.lab`のURLが、Crawl4AIのバックエンドにある`web_crawl`エンドポイントによって受け入れられます。PraisonAIは、内部のカナリアレスポンスボディを返し、その中には`PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91`が含まれています。
<img width="1542" height="763" alt="01-Crawl4AI-Burp-Readback" src="https://github.com/user-attachments/assets/a03b3a1c-e382-4f67-8ea6-b766dceb4a69" />
**Crawl4AI DNS再バインディングのランタイムの証拠**
ランタイムのログには、Crawl4AI/Chromiumのバックエンドが、フェッチの段階で`rebind.lab`を内部のDocker IPに解決していることが示されています。内部のカナリアは、ヘッドレスChromeのユーザーエージェントから`GET /secret`リクエストを受け取っています。
<img width="1659" height="946" alt="02-Crawl4AI-DNS-Log-And-Internal-Hit" src="https://github.com/user-attachments/assets/d93db090-3e39-43d0-af3b-5d1d8f614db8" />
**Crawl4AIのリダイレクト確認**
攻撃者によって制御されるリダイレクトURLが、Crawl4AIによってサポートされるエンドポイントによって受け入れられます。PraisonAIはリダイレクトに従い、内部のカナリア応答の本文を返します。
[画像:幅1544ピクセル、高さ771ピクセル、キャプション "03-Crawl4AI-Redirect-Readback"]
**Crawl4AIのリダイレクト実行時の証拠**
制御されたリダイレクト先が `302 -> http://internal-canary:8081/secret` を返し、内部のカナリアはCrawl4AI/Chromiumのバックエンドから `GET /secret` リクエストを受け取ります。
[画像:幅1590ピクセル、高さ920ピクセル、キャプション "04-Crawl4AI-Redirect-Internal-Hit-Log"]
## 影響
PRAI-05と同じクラスの脆弱性であり、内部/メタデータサービスに対するリダイレクトによるSSRF攻撃です。今回はCrawl4AIのバックエンドで発生しています。影響を受ける範囲が広がっています(脆弱性は共有の「検証を伴わないピン留め」の設計にあり、特定のバックエンドの問題ではありません)。
## 推奨される対策
一度解決するだけで済むように、IPアドレスの固定と、接続ごとの検証をCrawl4AIのバックエンドにも適用する必要があります(ヘッドレスブラウザを検証済みのIPアドレスに制限するか、許可リストに登録されたアクセス先のみを許可する)。
影響を受けるパッケージ:
- pip (Python) praisonaiagents: 影響を受けるバージョンは1.6.77以下、1.6.78で修正済み。