벡터 및 임베딩 취약점 평가은(는) 무엇을 하나요?
승인된 사용만 허용: 이러한 테스트는 귀하가 소유하거나 평가 권한이 있는 RAG 시스템의 벡터 스토어 및 임베딩 모델과 상호 작용합니다. 귀하가 제어하지 않는 시스템에 대한 임베딩 역전 및 교차 테넌트 프로빙은 제3자 데이터를 노출시킬 수 있으며 승인 없이 금지됩니다.
개요
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를 통한 유출입니다: 공격자는 합법적인 추론/쿼리 액세스를 사용하여 데이터(역전을 통한 원본 텍스트, 멤버십 또는 모델 추출)를 유출합니다. 이 스킬은 다섯 가지 취약점 클래스 모두에 대한 반복 가능한 평가를 제공합니다.
사용 시기
- 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과 임베딩 엔드포인트만 가지고 있습니다. 후보 텍스트를 언덕 오르기합니다.
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단계: 멤버십 추론
정확한 인용 쿼리에 대한 top-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 "격리 양호", "->", 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 in 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(["help"])[0].tolist(), limit=10):
flags = chunk_is_injection(hit.payload.get("text", ""))
if flags:
print("청크에서 간접 인젝션 발견", 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, 필터).
- 임베딩 역전 노출 측정 및 등급 지정.
- 말뭉치 내 vs 제어 항목에 대한 멤버십 추론 차이 계산.
- 다중 테넌트 격리 테스트(클라이언트 필터 있음 및 없음, 서버 측 시행 확인).
- 테스트 컬렉션에서만 중독 지배력 측정.
- 검색된 청크에서 간접 인젝션 콘텐츠 스캔.
- 각 취약점 클래스에 대한 수정 사항과 함께 결과 보고.
- 평가 중 프로덕션 말뭉치 수정 없음.