Strands Agents: シンボリックリンク攻撃によるローカル権限昇格の脆弱性(デフォルトのセッションファイル保存場所)

## 概要

`FileSessionManager`は、デフォルトでセッションデータを`/tmp/strands/sessions/`(誰でも書き込み可能なディレクトリ)に、予測可能なファイルパスと、シンボリックリンクに従う書き込みパターン(`open(path + ".tmp", "w")` → `os.replace()`)を使用して保存します。ローカルの攻撃者は、この脆弱性を悪用して、任意のファイルを上書きしたり、永続的なプロンプトインジェクションのために悪意のあるセッションデータを注入したりすることができます。

## 脆弱性の詳細

**コンポーネント:** `strands-py/src/strands/session/file_session_manager.py`

**根本原因:**
1. デフォルトの保存ディレクトリは`tempfile.gettempdir() + "/strands/sessions/"`(Linux/macOSでは`/tmp/strands/sessions/`に解決)です(58行目)。
2. `_write_file()`メソッド(116-123行目)は、シンボリックリンクの保護なしで、予測可能な`.tmp`パスに書き込みます。
```python
tmp = f"{path}.tmp"
with open(tmp, "w", encoding="utf-8", newline="\n") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
os.replace(tmp, path)
```
3. `open()`はシンボリックリンクに従います。`.tmp`パスに事前に配置されたシンボリックリンクがあると、書き込みが任意のターゲットファイルにリダイレクトされます。
4. セッションファイルに対する整合性検証(HMAC/署名)が行われていません。改ざんされたデータはそのまま読み込まれます。

## 攻撃手法

### 1. 任意のファイルの上書き(ローカル権限昇格)
1. 攻撃者は、`/tmp/strands/sessions/session_<id>/`を事前に作成します(または、inotifyを使用して新しいディレクトリを監視します)。
2. 攻撃者は、シンボリックリンクを作成します:`session.json.tmp` → `/home/victim/.bashrc`(または、任意の書き込み可能なファイル)。
3. 攻撃対象のアプリケーションがセッションデータを書き込む → シンボリックリンクに従う → ターゲットファイルをJSONコンテンツで上書きします。
4. 攻撃者は、次のシェルログイン時にコードを実行します(または、`.ssh/authorized_keys`、cronファイルなどをターゲットにします)。

### 2. セッションの改ざんによる持続的なプロンプトインジェクション
1. 攻撃者が、細工を施したメッセージファイルをセッションディレクトリに書き込みます。
2. 脆弱なアプリケーションがセッションを復元すると、攻撃者が制御する会話履歴が読み込まれます。
3. インジェクトされたメッセージは、エージェントに、今後のすべてのやり取りで攻撃者の指示を実行させるように指示します。
4. 整合性チェックがないため、この攻撃を防ぐことはできません。SDKは、すべてのセッションファイルを表面的な内容で信頼します。

## 影響

- **任意のファイルの書き換え**:ローカルの攻撃者は、被害プロセスが書き込み可能な任意のファイルに、攻撃者が制御する内容を書き込むことができます。
- **ローカル権限昇格**:シェル設定、SSHキー、cronジョブ、またはsystemdユーザーサービスを書き換えることで、ローカル権限を昇格させることができます。
- **エージェントの持続的な侵害**:セッションデータへのインジェクションにより、エージェントは攻撃者の指示を無限に実行し続けます。

## 再現方法

```python
import os, json, tempfile, shutil

# セットアップ:脆弱な書き込み動作をシミュレート
session_dir = "/tmp/strands/sessions/session_test/"
os.makedirs(session_dir, exist_ok=True)

# 攻撃者:.tmpの書き込みパスにシンボリックリンクを作成
target_file = "/tmp/symlink_attack_proof.txt"
with open(target_file, "w") as f:
f.write("ORIGINAL - should not be overwritten\n")

symlink_path = os.path.join(session_dir, "session.json.tmp")
if os.path.exists(symlink_path):
os.unlink(symlink_path)
os.symlink(target_file, symlink_path)

# 被害者:FileSessionManager._write_file()と同等の処理
with open(symlink_path, "w", encoding="utf-8", newline="\n") as f:
json.dump({"session_id": "test", "injected": True}, f, indent=2)

# 検証:ターゲットファイルが書き換えられたか
with open(target_file) as f:
print(f"ターゲットファイルのコンテンツ: {f.read()}")
# 出力: {"session_id": "test", "injected": true}

# クリーンアップ
os.unlink(symlink_path)
os.unlink(target_file)
shutil.rmtree(session_dir)
```

## 推奨される対策

1. **ユーザー固有のデフォルトディレクトリを使用する** — `/tmp/` ではなく、`~/.strands/sessions/` または `$XDG_DATA_HOME/strands/sessions/` を使用する。
2. **`os.open()` を `O_NOFOLLOW | O_CREAT | O_WRONLY` オプション付きで使用する** — シンボリックリンクを追跡しないようにする。
3. **予測可能な `.tmp` 拡張子ではなく、`tempfile.mkstemp()` を使用して、アトミックな書き込みを行う。
4. **書き込み前に、ディレクトリの所有権が現在のユーザーと一致することを確認する。
5. **オプションで、セッションファイルの整合性チェック(HMAC)を追加し、改ざんを検出する。

影響を受けるパッケージ:
- pip (Python) strands-agents: 1.48.0 未満が影響を受ける。1.48.0 で修正済み。