ベクトルと埋め込みの脆弱性評価 は何をしますか?
許可された使用のみ: これらのテストは、あなたが所有するか評価を許可されたRAGシステムのベクトルストアおよび埋め込みモデルに対してのみ実施してください。あなたが管理していないシステムに対する埋め込み逆変換やクロステナントプロービングは、第三者のデータを露出させる可能性があり、許可なく行うことは禁止されています。
概要
Retrieval-Augmented Generation(RAG)システムは、ドキュメントを埋め込みベクトルに変換してベクトルデータベース(Pinecone、Qdrant、Weaviate、Chroma、pgvector、FAISS)に格納し、最も近いベクトルを検索してLLMの応答を補完します。OWASP LLM08:2025 ベクトルと埋め込みの脆弱性では、このレイヤーに固有のセキュリティリスクをカバーしています。
- 埋め込み逆変換 — 埋め込みは一方向ではありません。訓練された逆変換モデル(またはブラックボックス再構成攻撃)は、ベクトルから元のテキストの大部分を復元でき、ソースドキュメントが漏洩します(MITRE ATLAS AML.T0024.001 MLモデルの逆変換に対応)。
- メンバーシップ推論 — 特定のレコードがコーパスに含まれているかどうかをクエリすることで推論します(AML.T0024.000)。
- クロステナント/マルチテナントリーク — 名前空間/コレクションが共有されている場合、またはフィルター分離がない場合、あるテナントが別のテナントのチャンクを取得します。
- ナレッジベースポイズニング — 攻撃者がコーパスに書き込める場合、検索で優位になるように細工されたチャンク(期待されるクエリとのコサイン類似度が高い)を挿入し、間接プロンプトインジェクションペイロードを運ばせます。
- 検索操作 — 無関係なクエリに対して検索されるように調整された敵対的なドキュメント(「検索ハイジャック」)。
上位の手法は AML.T0024 — ML推論APIを介したデータ流出です。攻撃者は正当な推論/クエリアクセスを使用して、データ(ソーステキストの逆変換、メンバーシップ、モデル抽出を介して)を流出させます。このスキルは、5つすべての脆弱性クラスの再現可能な評価を提供します。
使用するタイミング
- RAG / ベクトル検索アプリケーションのセキュリティ評価中(OWASP LLM08カバレッジ)。
- ベクトルストアがマルチテナントであり、名前空間/メタデータの分離を証明する必要がある場合。
- コーパスがユーザー提供またはサードパーティのドキュメントを受け入れる場合(ポイズニング攻撃面)。
- 埋め込みエンドポイントが外部から到達可能な場合(逆変換/メンバーシップ攻撃面)。
- リリース前に検索フィルタリング制御を検証する場合。
前提条件
- 対象の埋め込みエンドポイントおよびベクトルストアをカバーする許可とスコープ。
- Python 3.10以上。
- テストコレクションへの読み取りアクセス(ポイズニングテストの場合は書き込みアクセス)。本番コーパスでは決して実行しないでください。
# ベクトルDBクライアント + 埋め込み + 類似性ツール
python -m pip install numpy scikit-learn sentence-transformers
python -m pip install qdrant-client chromadb pinecone-client weaviate-client
# (オプション) テキスト埋め込み逆変換研究ベースライン
python -m pip install vec2text
目的
- 対象の埋め込みモデルに対する埋め込み逆変換の露出を測定する。
- コーパスに対してメンバーシップ推論プローブを実行する。
- マルチテナント分離(名前空間、メタデータフィルター、RBAC)をテストし、クロステナントリークを検出する。
- テストコレクションにベナイトなポイズンチャンクを注入し、検索における優位性を測定する。
- 検索されたチャンクに残存する間接プロンプトインジェクションコンテンツを検出する。
- 制御を推奨する:テナントスコープフィルター、コンテンツ検証、埋め込みアクセス制限。
MITRE ATT&CKマッピング
| ID | 戦術 | 公式手法名 | このスキルでの役割 |
|---|---|---|---|
| AML.T0024 | ATLAS: データ流出 | ML推論APIを介したデータ流出 | クエリ/埋め込みアクセスを使用したソースデータの流出 |
| AML.T0024.000 | ATLAS: データ流出 | トレーニングデータメンバーシップの推論 | コーパスに対するメンバーシップ推論プローブ |
| AML.T0024.001 | ATLAS: データ流出 | MLモデルの逆変換 | ソーステキストの埋め込み逆変換による再構成 |
| AML.T0020 | ATLAS: リソース開発 | トレーニングデータのポイズニング | コーパスへのナレッジベースポイズニング |
| AML.T0051.001 | ATLAS: 初期アクセス | LLMプロンプトインジェクション: 間接 | 検索されたチャンクに埋め込まれたインジェクションペイロード |
ワークフロー
ステップ 1: RAGパイプラインのインベントリ作成
埋め込みモデル + 次元数、ベクトルストアとそのテナントモデル、チャンキング戦略、検索 top_k と類似度指標(コサイン/ドット/L2)、クエリ時に適用されるメタデータフィルターを文書化します。
# 例: Qdrantコレクションの検査
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
info = client.get_collection("docs")
print(info.config.params.vectors) # サイズ + 距離指標
print(client.count("docs")) # コーパスサイズ
ステップ 2: 埋め込み逆変換の露出テスト
類似テキストの埋め込みは近接しています。埋め込みエンドポイントを持つ攻撃者は、ターゲットベクトルに一致するテキストを反復的に再構成できます。埋め込み空間での最近傍がどの程度元のテキストを復元できるかを、候補再構成とターゲット間のコサイン類似度を使用して測定します。
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
secret = "患者 山田太郎、医療記録番号 553120、高血圧と診断されました。"
target_vec = model.encode([secret])
# 攻撃者はtarget_vecと埋め込みエンドポイントのみを持っている。候補テキストを hill-climb。
candidates = [
"患者名と医療記録番号と診断。",
"山田太郎 医療記録 高血圧 診断",
"患者 山田太郎 医療記録番号 診断 高血圧",
]
cand_vecs = model.encode(candidates)
sims = cosine_similarity(target_vec, cand_vecs)[0]
for c, s in sorted(zip(candidates, sims), key=lambda x: -x[1]):
print(f"{s:.3f} {c}")
# ほぼ逐語的な推測で類似度が高い場合、このモデルでは逆変換リスクが現実的です。
研究グレードの再構成ベースラインとしては、互換性のある埋め込みモデルに対して vec2text を使用して全文復元を実証できます。
ステップ 3: メンバーシップ推論
正確な引用クエリに対するトップ1検索類似度を測定することで、特定のドキュメントがコーパスに含まれているかどうかを判断します。コーパス内のアイテムは、コーパス外のコントロールよりも著しく高い最大類似度を示します。
def membership_score(client, collection, embed, text):
vec = embed([text])[0].tolist()
hits = client.search(collection_name=collection, query_vector=vec, limit=1)
return hits[0].score if hits else 0.0
in_corpus = membership_score(client, "docs", model.encode, "<既知のチャンクからの正確な引用>")
control = membership_score(client, "docs", model.encode, "無関係なランダム文。")
print(f"コーパス内={in_corpus:.3f} コントロール={control:.3f} 差分={in_corpus-control:.3f}")
# 差分が大きく正の場合、そのアイテムはコーパス内に存在します(メンバーシップリーク)。
ステップ 4: マルチテナント分離のテスト
テナントBがテナントAのチャンクを取得できないことを確認します。テナントBで認証されたクエリを発行し(フィルターが適用されるべき)、結果にテナントAの tenant_id が出現しないことを確認します。
# テナントBとしてクエリ; tenant_id == "B" の結果のみ期待
from qdrant_client.models import Filter, FieldCondition, MatchValue
vec = model.encode(["機密の給与情報"])[0].tolist()
hits = client.search(
collection_name="docs",
query_vector=vec,
limit=10,
query_filter=Filter(must=[FieldCondition(key="tenant_id", match=MatchValue(value="B"))]),
)
leaked = [h for h in hits if h.payload.get("tenant_id") != "B"]
print("クロステナントリーク" if leaked else "分離OK", "->", len(leaked), "件の他テナント行")
# 重要なテスト: フィルターなしで繰り返し、サーバー側(クライアント側ではない)で分離が強制されていることを確認。
# フィルターなしのクエリがテナントAのデータを返す場合、分離はクライアント側のみです。
hits_nofilter = client.search(collection_name="docs", query_vector=vec, limit=10)
print("サーバー側分離失敗" if any(h.payload.get("tenant_id") != "B" for h in hits_nofilter) else "OK")
ステップ 5: ナレッジベースポイズニング(テストコレクションのみ)
多くの無関係なクエリで検索されるように細工されたベナイトなポイズンチャンクを挿入し、それが top_k にどの程度出現するかを測定します。
from qdrant_client.models import PointStruct
# ベナイトなマーカーペイロード(実際のインジェクションなし)で検索優位性を測定
poison = "POISON-CANARY. " + " ".join(
["パスワード リセット 請求 返金 アカウント ログイン サポート エラー ヘルプ"] * 8
)
client.upsert("docs_test", points=[
PointStruct(id=999999, vector=model.encode([poison])[0].tolist(),
payload={"tenant_id": "B", "source": "poison-test"})
])
queries = ["返金方法は?", "パスワードをリセット", "天気は?"]
for q in queries:
hits = client.search("docs_test", model.encode([q])[0].tolist(), limit=5)
dominated = any(h.payload.get("source") == "poison-test" for h in hits)
print(f"{'POISONがtop5に' if dominated else 'クリーン'}: {q}")
ステップ 6: 検索されたチャンク内の間接プロンプトインジェクションの検出
プロンプトに連結される前に、検索されたチャンクのテキストをインジェクションマーカーでスキャンします。
import re
INJECTION_PATTERNS = [
r"ignore (all|previous|the above) instructions",
r"system prompt", r"you are now", r"disregard", r"</?(system|instructions)>",
]
def chunk_is_injection(text):
low = text.lower()
return [p for p in INJECTION_PATTERNS if re.search(p, low)]
for hit in client.search("docs", model.encode(["ヘルプ"])[0].tolist(), limit=10):
flags = chunk_is_injection(hit.payload.get("text", ""))
if flags:
print("間接インジェクション in chunk", hit.id, flags)
ステップ 7: 報告と改善
- 逆変換/メンバーシップ: 埋め込みエンドポイントをレート制限および認証し、生の類似度スコアを返さないようにし、埋め込みのクエリを制限します。
- クロステナント: テナントフィルターをサーバー側で強制します(可能な場合はテナントごとに個別のコレクション/名前空間を使用)。クライアント提供のフィルターに依存しないでください。
- ポイズニング: 取り込まれるすべてのチャンクを検証し、出所タグを付けます。入力をインジェクションでスキャンし、単一ソースの検索シェアに上限を設定します。
- 間接インジェクション: 検索されたチャンクをサニタイズし、出力ガードレールを適用します(
defending-llms-with-guardrailsを参照)。
ツールとリソース
| ツール | 目的 | 主なソース |
|---|---|---|
| OWASP LLM08 | ベクトルと埋め込みの脆弱性ガイダンス | https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/ |
| sentence-transformers | テスト用の埋め込み生成 | https://www.sbert.net/ |
| Qdrant クライアント | ベクトルストア + フィルター検索 | https://qdrant.tech/documentation/ |
| Chroma / Weaviate / Pinecone | 代替ベクトルストア | https://docs.trychroma.com/ |
| vec2text | 埋め込み逆変換研究ベースライン | https://github.com/jxmorris12/vec2text |
| MITRE ATLAS | AML.T0024 ML推論APIを介したデータ流出 | https://atlas.mitre.org/ |
検証基準
- RAGパイプラインのインベントリ作成済み(埋め込みモデル、ストア、テナント、指標、top_k、フィルター)。
- 埋め込み逆変換の露出を測定し、評価済み。
- コーパス内アイテムとコントロールのメンバーシップ推論差分を計算済み。
- マルチテナント分離をクライアントフィルターあり/なしでテスト済み(サーバー側強制を確認)。
- テストコレクションのみでポイズニング優位性を測定済み。
- 検索されたチャンクを間接インジェクションコンテンツでスキャン済み。
- 各脆弱性クラスの改善策とともに調査結果を報告済み。
- 評価中に本番コーパスを変更していない。