向量資料庫存取控制
向量資料庫的存取控制弱點:API 金鑰管理、租戶隔離失誤、命名空間安全,以及 metadata 過濾繞過技術。
向量資料庫的存取控制是一個產品能力經常落後於部署需求的快速演進領域。許多向量資料庫最初是為搜尋用途而設計,並非作為安全敏感資料儲存,其存取控制模型也反映此起源。本頁涵蓋最常見的存取控制弱點,以及在安全評估中加以利用的方法。
API 金鑰管理
API 金鑰是多數向量資料庫的主要驗證機制。整個向量資料庫的安全性,往往取決於單一 API 金鑰的安全。
常見的 API 金鑰弱點
每個專案只有一把金鑰。 Pinecone 及許多託管型向量資料庫為每個專案發放單一 API 金鑰。此金鑰對所有資料具完整讀寫權限。無法發放唯讀金鑰、限於特定命名空間的金鑰,或查詢能力受限的金鑰。
# 單一 Pinecone API 金鑰即可存取一切
import pinecone
pinecone.init(api_key="compromised-key")
index = pinecone.Index("production-index")
# 完整讀取權限
results = index.query(vector=[0.1] * 1536, top_k=10000)
# 完整寫入權限
index.upsert(vectors=[("malicious-id", [0.1] * 1536, {"injected": True})])
# 完整刪除權限
index.delete(ids=["any-id"])金鑰出現在用戶端程式碼中。 當 AI 應用將向量資料庫查詢嵌入用戶端程式碼(瀏覽器應用、行動 App)時,API 金鑰會對終端使用者暴露。即便金鑰意圖受限,向量資料庫未必強制執行精細權限。
金鑰出現在組態檔中。 向量資料庫 API 金鑰經常出現在:
- 提交至版控的環境變數檔
- Docker Compose 檔案
- Kubernetes ConfigMap(而非 Secret)
- CI/CD 管線組態
- Terraform 狀態檔
金鑰輪替挑戰
向量資料庫 API 金鑰很少被輪替,原因包括:
- 許多資料庫不支援多把同時啟用的金鑰,輪替需要停機
- 金鑰嵌入多個服務中,需要協調性更新
- 沒有可比擬 AWS Secrets Manager 的自動化輪替整合
租戶隔離
多租戶向量資料庫部署需要租戶間隔離以防資料外洩。隔離機制在不同資料庫與部署模型間差異極大。
邏輯隔離 vs. 實體隔離
| 隔離類型 | 機制 | 繞過風險 |
|---|---|---|
| 命名空間 | 索引內的邏輯分區 | 高——單一 API 金鑰可存取所有命名空間 |
| 集合(Collection) | 具獨立組態的獨立索引 | 中——取決於存取控制模型 |
| 資料庫 | 獨立資料庫實例 | 低——需獨立憑證 |
| 基礎設施 | 獨立運算與儲存 | 最低——實體隔離 |
多數生產部署為節省成本會採用命名空間或集合層級的隔離。相較於獨立資料庫實例,這會形成較弱的邊界。
命名空間隔離繞過
在 Pinecone 及類似資料庫中,命名空間是索引內的邏輯分區。指定命名空間的查詢僅回傳該命名空間內的結果。然而持有 API 金鑰的攻擊者可查詢任何命名空間:
# 預期:應用只查詢自己的命名空間
results = index.query(
vector=query_embedding,
top_k=10,
namespace="tenant-a"
)
# 攻擊:查詢其他租戶的命名空間
results = index.query(
vector=query_embedding,
top_k=10,
namespace="tenant-b" # 取得 tenant-b 的資料
)
# 攻擊:列舉命名空間
# 多數向量資料庫並不限制命名空間列舉
stats = index.describe_index_stats()
# 回傳命名空間名稱與向量數跨租戶查詢洩漏
即使具備正確的命名空間隔離,某些操作仍可能跨租戶洩漏資訊:
- 索引統計 會揭露其他租戶資料的存在與大小
- 相似度分數 在共用索引中可能反映與其他租戶向量的鄰近性
- Metadata 過濾 未必於儲存層強制執行,允許過濾繞過
命名空間安全
命名空間提供邏輯區隔,但在多數向量資料庫中並非安全邊界。理解其侷限對於準確風險評估相當關鍵。
命名空間列舉
多數向量資料庫可透過統計端點列出所有命名空間:
# Pinecone 命名空間列舉
stats = index.describe_index_stats()
for namespace, info in stats.namespaces.items():
print(f"Namespace: {namespace}, Vectors: {info.vector_count}")命名空間名稱常揭露組織結構、專案名稱或租戶識別碼。這些資訊有助進一步的針對性攻擊。
命名空間刪除
具備寫入權限的攻擊者可刪除整個命名空間,造成資料遺失:
# 刪除命名空間內所有向量
index.delete(delete_all=True, namespace="target-namespace")多數向量資料庫對讀、寫、刪操作並無分離權限。能查詢的金鑰也能刪除。
Metadata 過濾繞過
向量資料庫會將 metadata 與嵌入一併儲存,以支援過濾查詢。許多 RAG 系統透過 metadata 過濾來實作存取控制——於嵌入上附加存取標籤,並以過濾限縮查詢僅回傳使用者被授權的嵌入。
用戶端過濾
最常見也最危險的模式,是透過用戶端 metadata 過濾來實作存取控制:
# 應用程式以 metadata 過濾實作存取控制
def search(user, query_embedding):
results = index.query(
vector=query_embedding,
top_k=10,
filter={"department": user.department} # 「存取控制」
)
return results能修改查詢的攻擊者(透過 API 操弄、用戶端程式碼修改或注入)可移除或更改過濾:
# 攻擊:移除過濾以存取所有資料
results = index.query(
vector=query_embedding,
top_k=10
# 無過濾——回傳所有部門的結果
)
# 攻擊:查詢其他部門
results = index.query(
vector=query_embedding,
top_k=10,
filter={"department": "executive"} # 存取高階主管資料
)過濾注入
部分應用以使用者輸入建構 metadata 過濾,製造過濾注入的機會:
# 不安全:過濾由使用者輸入建構
def search(request):
filters = {"department": request.user.department}
# 使用者提供的附加過濾
if request.params.get("category"):
filters["category"] = request.params["category"]
# 攻擊者可注入:category={"$exists": true}
# 或其他依資料庫而異的過濾運算子
results = index.query(
vector=query_embedding,
top_k=10,
filter=filters
)Metadata 列舉
即便過濾有被強制執行,metadata 值仍可透過針對性查詢加以列舉:
# 以各可能值查詢以列舉 metadata 值
departments = ["engineering", "sales", "executive", "hr", "finance"]
for dept in departments:
results = index.query(
vector=random_embedding,
top_k=1,
filter={"department": dept}
)
if results.matches:
print(f"Department exists: {dept}")紅隊評估檢核表
評估向量資料庫存取控制時:
- 取得 API 金鑰 —— 檢查用戶端程式碼、組態檔、環境變數、CI/CD 管線
- 測試命名空間隔離 —— 跨命名空間查詢,列舉命名空間名稱
- 測試 metadata 過濾繞過 —— 移除過濾、修改過濾值、注入過濾運算子
- 測試寫入權限 —— 嘗試 upsert、更新、刪除向量
- 測試管理操作 —— 索引建立、刪除、組態變更
- 測試金鑰範疇 —— 判定金鑰允許的操作,以及不同金鑰是否具不同權限
- 檢查金鑰輪替 —— 判定金鑰上次輪替時間,以及輪替是否自動化