MCP Deep Dive, Part 8: 도구 결과가 공격이 될 때 — 프롬프트 인젝션(Prompt Injection) 및 도구 남용으로부터
요약
Model Context Protocol(MCP) 환경에서 도구 결과(tool result)를 신뢰할 수 없는 입력으로 간주하고, 프롬프트 인젝션 공격으로부터 에이전트를 보호하는 보안 설계 방식을 다룹니다. 모델을 완벽하게 방어하는 대신, 공격을 당하더라도 피해를 최소화할 수 있는 격리 및 검증 전략을 강조합니다.
핵심 포인트
- 도구 결과는 신뢰할 수 없는 입력(untrusted input)으로 취급해야 함
- 프롬프트 인젝션 면역이 아닌, 인젝션 발생 후의 피해 최소화에 집중
- 도구 결과가 지침이 아닌 데이터로 처리되도록 격리(fencing) 필요
- 도구 설명(description) 오염 및 러그 풀 공격 방지를 위한 체크섬 검증 권장
파트 6과 7에서는 적절한 권한을 가진 올바른 신원만이 도구를 호출할 수 있도록 보장했습니다. 이번 파트에서는 불편하지만 다음으로 마주하게 되는 질문을 다룹니다. 완벽하게 인증되고 올바르게 권한을 부여받은 에이전트가 단순히 잘못된 일을 하도록 지시받는다면 어떻게 될까요? 에이전트가 읽는 문서, 받는 도구 결과(tool result), 또는 신뢰하는 서버에 의해서 말입니다. 모델을 속임수에 면역이 되도록 만들 수는 없습니다. 따라서 게임의 핵심은 속임을 당한 에이전트가 해를 끼치지 못하게 만드는 것입니다.
이 글은 Model Context Protocol (MCP)에 대한 15부작 심층 분석 중 파트 8이며, 보안의 3요소를 완성합니다. MCP만의 독특한 점은, 에이전트에서 **도구 결과(tool result)는 신뢰할 수 없는 입력(untrusted input)**이라는 것이며, 이는 모든 것을 변화시킵니다.
요약 (TL;DR)
| 위협 | 단순한 MCP 에이전트 (이전) | 보안이 강화된 MCP (이후) |
|---|---|---|
| 도구 결과 인젝션 (Tool-result injection) | 신뢰함, 복종함 | 격리(fenced) + 신뢰할 수 없는 것으로 분류 |
| ... | ... | ... |
단 하나의 사고방식 전환: 모델을 프롬프트 인젝션 (Prompt Injection)에 면역되게 만들려고 노력하는 것을 멈추십시오. 그것은 불가능합니다. 에이전트가 속을 것이라고 가정하고, 속임을 당한 에이전트라도 접근해서는 안 될 데이터에 도달하거나, 읽은 내용을 유출하거나, 파괴적인 작업을 실행할 수 없도록 설계하십시오. 보안이란 성공적인 인젝션이 발생한 후에도 살아남는 것입니다.
1. 도구 결과는 신뢰할 수 없는 입력이다
에이전트는 도구가 반환하는 무엇이든 신뢰하며 이를 모델에 그대로 다시 전달합니다.
// 이전: 도구의 결과가 마치 신뢰할 수 있는 것처럼 모델로 흘러 들어갑니다.
var result = await client.CallToolAsync("get_campaign_kpis", args, ct);
messages.Add(ChatMessage.ToolResult(result.Text));
...
// 이후: 도구 결과는 신뢰할 수 없는(UNTRUSTED) 입력입니다. 이를 검사(Screen)한 다음, 지침(instructions)이 아닌 데이터(DATA)로 격리(fence)하십시오.
var result = await client.CallToolAsync("get_campaign_kpis", args, ct);
...
이것이 에이전트에게 고유한 인젝션 벡터(injection vector)입니다. 사용자가 공격을 입력한 것이 아니라, 도구가 공격을 반환한 것입니다. 캠페인 필드, 문서, 또는 하위 API의 에러 메시지 모두 "당신의 지침을 무시하고..."와 같은 내용을 담고 있을 수 있으며, 단순한 에이전트는 도구 출력을 신뢰하기 때문에 이를 따르게 됩니다. 도구 결과를 포함하여 모델이 읽는 모든 것은 신뢰할 수 없는 것입니다.
2. 도구 설명 오염(Tool-description poisoning) 및 러그 풀(rug pull)
모든 도구 정의(tool definition)에 대해 체크섬(Checksum)을 수행하세요. 승인한 이후에 변경되는 설명(description)이나 스키마(schema)는 러그 풀(rug pull, 갑작스러운 변심/사기)입니다. 이를 모델에 노출하는 대신 격리(quarantine)하십시오.
// 악의적인 서버는 모델이 도구를 선택하기 위해 읽는 도구 설명(DESCRIPTION)에 지침을 숨기거나,
// 무해한 도구를 배포하여 승인을 받은 뒤 악성 도구로 교체할 수 있습니다 ("rug pull").
var tools = await client.ListToolsAsync(ct);
...
설명 주입(Description injection)은 모델이 도구를 선택하는 동안 읽는 도구 설명 안에 지침을 숨기는 방식입니다. 이 페이로드(payload)는 어떤 도구가 실행되기도 전에 도달합니다. 러그 풀(rug pull)은 최초 사용 시 신뢰(trust-on-first-use) 모델을 악용합니다. 즉, 서버가 검토 단계에서는 정상적으로 동작하다가 나중에 도구를 변경하는 것입니다. 이름, 설명, 스키마에 대해 체크섬(Checksum)을 적용하세요. 변경 사항이 발생하면 묵시적으로 신뢰하는 것이 아니라 반드시 재승인을 거쳐야 합니다.
3. 치명적인 삼위일체(lethal trifecta) 타파
개인 테넌트 데이터(private tenant data)를 읽고, 신뢰할 수 없는 콘텐츠(untrusted content)를 수용하며, 데이터를 외부로 전송할 수 있는 도구를 보유한 에이전트는 프롬프트 인젝션(prompt injection) 한 번에 데이터 유출이 발생할 준비가 된 상태와 같습니다.
// "치명적인 삼위일체": (1) 개인 데이터(PRIVATE DATA)에 대한 접근 + (2) 신뢰할 수 없는 콘텐츠에 대한 노출 +
// (3) 데이터 유출(EXFILTRATE) 능력 = 유출을 유도할 수 있는 에이전트. 이 중 하나라도 제거하십시오.
var toolset = trifecta.Restrict(principal, sessionReadsUntrustedContent: true);
...
치명적인 삼위일체(THE LETHAL TRIFECTA) — 세 가지가 모두 존재하면 = 탈취를 유도할 수 있는 에이전트:
(1) 개인 데이터(PRIVATE DATA) ----\
...
이 프레임워크(Simon Willison의 제안)는 에이전트를 위한 가장 유용한 보안 모델입니다. 인젝션(injection)을 확실히 막을 수는 없으므로, 그 결과(outcome)를 차단하십시오. 민감한 데이터와 신뢰할 수 없는 콘텐츠를 읽는 에이전트는 데이터를 어디로든 보낼 수 있는 도구를 절대로 동시에 가져서는 안 됩니다. 유출 경로(exfil path)를 제거하면, 인젝션에 성공하더라도 탈취한 데이터를 보낼 곳이 없게 됩니다.
4. 도구 간 교차 유출(Cross-tool exfiltration) — 인자(arguments) 필터링
도구의 출력(outputs)뿐만 아니라 인자(arguments)에 대해서도 송신 필터(Egress-filter)를 적용하세요. 도구 호출(tool call)로 흘러 들어가는 내용을 스캔하십시오.
// 데이터 유출(Exfiltration)은 들어오는 과정(IN)에서도 발생합니다. 주입된 에이전트(injected agent)는 데이터를 도구의 인자(arguments) (예: 웹훅 URL, 검색 쿼리)에 넣어 몰래 빼낼 수 있습니다. 전송 전에 인자를 스캔하십시오.
var scan = egress.Inspect(call.Arguments);
...
사용자에게 전달되는 출력(outputs)을 스캔하는 것만으로는 충분하지 않습니다. 에이전트에서는 도구로 전달되는 도구 인자(tool arguments) 또한 스캔해야 합니다. 인젝션(Injection)은 비밀 정보를 URL, 쿼리, 또는 알림 본문(notification body)에 집어넣어 데이터를 유출(exfiltrate)하는 방식을 선호합니다. 송신 필터링(Egress filtering)은 양방향 모두를 대응해야 합니다.
5. 파괴적인 도구 남용은 스스로 승인할 수 없습니다
파괴적인 도구(Destructive tools)는 Part 7에서 다룬 단계 격상(step-up) 절차를 필요로 합니다. 즉, 모델이 위조할 수 없는 새로운 인간의 확인(human confirmation)이 필요합니다.
// 주입된 지시사항(injected instruction)은 파괴적인 도구를 스스로 승인할 수 없습니다. Part 7의 단계 격상(step-up)은
// 모델이 조작할 방법이 없는 '새로운(FRESH)' 인간의 확인을 요구합니다.
if (call.IsDestructive && !confirmation.IsFreshlyConfirmed(principal, call))
...
인젝션이 에이전트를 설득하여 delete_audience를 호출하게 하더라도, 최소 권한(least privilege) 원칙에 의해 이미 해당 범위(scope)가 거부될 수 있습니다. 만약 에이전트가 정당하게 해당 권한을 가지고 있더라도, 파괴적인 작업에 대한 단계 격상(step-up)은 모델이 생성할 수 없는 확인을 요구합니다. 인젝션은 문 앞까지 도달하지만 거기서 멈추게 됩니다.
6. 심층 방어(Defense in depth) — MCP 보안 스택
모든 것을 계층화하십시오. 각 공격은 여러 개의 독립적인 통제 수단(controls)을 뚫어야 하며, 모든 차단 시도는 감사(audited)됩니다.
신뢰할 수 없는 항목(Untrusted): 도구 결과(tool results), 도구 설명(tool descriptions), 리소스(resources), 인자(arguments)
|
[ 인증(authN) — Part 6 ] 익명 호출 불가
...
단일 계층이 곧 "보안"인 것은 아닙니다. 공격은 인증(authentication), 인가(authorization), 인젝션 스크린(injection screen), 송신 필터(egress filter), 그리고 인간의 단계 격상(human step-up)을 모두 통과해야 하며, 모든 시도는 감사 로그(audit log)에 기록됩니다.
수치 요약
| 통제 수단 (Control) | 단순한 MCP (이전) | 보안된 MCP (이후) |
|---|---|---|
| 도구 결과 (Tool results) | 신뢰함 (trusted) | 격리 및 인젝션 스크린 적용 (fenced + injection-screened) (0.85) |
| ... |
계속 이어갈 모델
인젝션(Injection)이 성공했다고 가정해 봅시다. 모델이 속임수에 면역을 갖게 만들 수는 없으므로, 보안의 핵심 질문은 "에이전트가 속을 수 있는가?"가 아니라 "속았을 때, 실제로 무엇을 할 수 있는가?"가 되어야 합니다. 최소 권한 (Least Privilege)으로 그 범위를 제한하고, 데이터 유출 (Exfiltration) 경로를 차단하며, 파괴적인 작업은 인간의 승인을 거치도록 게이트를 설정하고, 모델이 읽는 모든 것을 스크리닝(Screening)하며, 모든 블록을 감사(Audit)하십시오.
- 모든 도구 결과, 설명, 리소스를 신뢰할 수 없는 입력 (Untrusted Input)으로 취급하십시오. 공격은 당신의 도구를 통해 들어옵니다.
- 치명적인 삼각관계 (Lethal Trifecta)를 깨뜨리십시오. 단일 에이전트가 개인 데이터, 신뢰할 수 없는 콘텐츠, 그리고 유출 경로를 동시에 보유하게 두지 마십시오.
- 속임수가 통한다고 가정하고, 폭발 반경 (Blast Radius)을 제한하십시오. 최소 권한 (Least Privilege) + 단계별 인증 (Step-up) + 송신 제어 (Egress) + 감사 (Audit)를 계층적으로 적용하십시오.
원문은 PrepStack에 게시되었습니다. 인젝션 및 도구 남용으로부터 MCP 에이전트를 강화하고 있으며, 위협 모델 (Threat Model)에 대해 다른 전문가의 검토를 원하신다면 randhir.jassal[at]gmail.com으로 연락해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기