첫 번째 악성 MCP 서버, 그리고 그것이 신뢰에 대해 우리에게 가르쳐준 것
요약
악성 MCP 서버인 'postmark-mcp' 사례를 통해 AI 에이전트 생태계에서의 의존성 보안 문제를 분석합니다. 한 번의 검증으로 끝나는 신뢰가 아닌, 지속적인 스키마 및 도구 변화를 감시해야 하는 필요성을 강조합니다.
핵심 포인트
- 악성 MCP 서버가 이메일 BCC를 통해 정보를 탈취한 사례 발생
- 초기 버전의 정직함이 이후 버전의 악성 코드를 위한 신뢰 구축 수단으로 악용됨
- 의존성(Dependency)은 한 번의 검증이 아닌 지속적인 재확인이 필요한 대상임
- 도구의 스키마와 파라미터 등 구조적 기술을 지속적으로 비교·감시해야 함
2025년 9월, Koi의 보안 팀은 지나고 보니 피할 수 없었던 어떤 사실을 발견했습니다. Postmark를 통해 AI 어시스턴트가 이메일을 보낼 수 있게 해주는 Model Context Protocol (MCP) 서버인 postmark-mcp라는 npm 패키지가 조용히 무기로 변해 있었습니다. 이 이야기는 거의 지루할 정도로 단순하며, 바로 그 점이 연구할 가치가 있게 만듭니다.
한 엔지니어가 Postmark의 정당한 오픈 소스 MCP 서버를 거의 한 줄씩 그대로 복사하여 자신의 이름으로 게시했습니다. 처음 15개의 버전은 깨끗했습니다. 그것들은 주장하는 바를 정확히 수행했습니다. 사람들은 그것들을 설치하고, 자신의 에이전트(agent)에 연결한 뒤 다음 작업으로 넘어갔습니다. 그러다 버전 1.0.16이 출시되었습니다. 이 버전은 단 한 줄, 즉 모든 발신 메시지에 phan@giftshop[.]club으로 숨은 참조(BCC)를 추가하는 231번 라인을 제외하고는 이전 버전과 동일했습니다. 그 시점부터 AI 어시스턴트가 이 도구를 통해 보낸 모든 이메일—비밀번호 재설정, 송장, 자격 증명, 계약서 등—은 낯선 이에게 조용히 전달되었습니다. 누군가 알아차리기 전까지 이 패키지는 1,643번 다운로드되었습니다.
우리가 다루는 문제에 대해 추론할 때 이 사건을 계속 다시 살펴보는 이유는, 이 사건이 부수적인 모든 것을 벗겨내고 핵심 문제를 노출시키기 때문입니다. 그러니 우리가 실제로 추론했던 방식대로 그 과정을 따라가 봅시다.
신뢰는 단 한 번 부여되었고, 다시는 확인되지 않았다
명백한 질문부터 시작해 봅시다. postmark-mcp를 사용하는 사람들이 침해당한 순간은 언제인가요? "1.0.16 버전을 설치했을 때"라고 말하고 싶겠지만, 그것은 정확하지 않습니다. 그들은 설치 시점에 이 도구가 안전하다고 결정하고, 그 결정을 다시는 재검토하지 않기로 한 순간 침해당한 것입니다.
이것은 우리가 사고가 발생할 때마다 반복해서 목격해 온 패턴입니다. 신뢰는 특정 시점의 행위입니다. 당신은 스키마 (schema)를 검사하고, 도구 (tool)를 검토하며, 권한 (permission)을 승인하고 판단을 내립니다: 이것이 내가 의존하고 있는 대상의 형태이다. 그리고 나서 당신은 그 판단이 영구적인 것처럼 그 위에 무언가를 구축합니다. 하지만 당신이 의존했던 대상은 영구적이지 않습니다. 그것은 타인이 제어하는 살아있는 산물 (artifact)이며, 당신에게 알릴 의무 없이 언제든, 어떤 이유로든, 당신의 발밑에서 변할 수 있습니다.
처음 15개의 버전이 깨끗했다는 사실은 안심할 만한 일이 아니었습니다. 그것은 바로 공격이었습니다. 15개의 정직한 버전은 정확히 16번째 버전이 써버릴 신뢰를 얻는 과정일 뿐입니다. 전체 익스플로잇 (exploit)은 “나는 이것을 한 번 평가했다”와 “이것은 여전히 내가 평가했던 바로 그것이다” 사이의 간극에 존재합니다.
따라서 우리가 도달한 첫 번째 아이디어는 당혹스러울 정도로 단순합니다: 의존성 (dependency)의 형태는 한 번 확립하면 끝나는 사실이 아니다. 그것은 계속해서 측정해야 하는 값입니다. 만약 계약 (contract)이 변경될 수 있는 순간 신뢰가 붕괴된다면, 유일하게 정직한 방법은 계약을 승인하고 잊어버리는 것이 아니라, 지속적으로 재확인해야 하는 대상으로 취급하는 것입니다.
변화를 감시하기 위해서는, 먼저 계약을 당신이 보유할 수 있는 형태로 만들어야 합니다.
다음 질문은 자연스럽게 뒤따라옵니다. 만약 우리가 도구나 스키마가 변경되는 것을 알아차리고 싶다면, 정확히 무엇을 비교해야 할까요?
당신은 '느낌'을 디프 (diff)할 수는 없습니다. “이 도구는 신뢰할 수 있는 느낌이었다”라는 것은 시간에 따라 비교할 수 있는 대상이 아닙니다. 비교 가능한 것은 대상의 구체적이고 구조적인 기술 (description)입니다: 페이로드 (payload) 내의 필드와 그 타입들, MCP 서버가 광고하는 도구들, 각 도구가 받아들이는 파라미터 (parameter)들, 그리고 모델이 도구 사용법을 결정하기 위해 읽는 설명 텍스트들입니다. 이것들이 바로 약속이며, 결정적으로, 이것들은 당신이 저장하고, 버전을 관리하며, 나란히 놓아 비교할 수 있는 명시적인 산물 스냅샷 (artifact snapshots)으로 캡처될 수 있습니다.
이것은 두 번째 아이디어로 이어집니다: 계약을 보호하기 전에, 먼저 그것을 검사 가능하고 안정적인 무언가로 구현해야 합니다. API나 도구 카탈로그(tool catalog)의 실시간으로 변화하는 표면을 캡처된 형태(captured shape)로 전환하십시오. 일단 그렇게 되면, "내가 해킹당했는가?"라는 불가능해 보이는 질문은 기계적인 질문이 됩니다: "오늘의 형태가 내가 승인한 형태와 동일한가?" 이런 관점에서 볼 때, postmark-mcp 공격은 전혀 교묘하지 않습니다. 버전 1.0.15와 버전 1.0.16은 동일한 도구들을 설명하지만, 그 동작은 갈라졌습니다. 버전, 해시(hashes), 도구 목록과 같은 눈에 보이는 메타데이터조차 변경되었습니다. 어제의 스냅샷을 오늘의 스냅샷과 나란히 보유하고 있는 시스템이라면, 지적할 수 있는 구체적인 근거를 가졌을 것입니다.
모든 변화가 공격은 아니다 — 따라서 비교에는 판단이 포함되어야 한다
여기서 우리는 이것을 단순한 체크섬(checksum)이 아닌 실제 엔지니어링 문제로 만드는 복잡한 지점에 도달합니다. 계약은 변해야 합니다. API는 필드(fields)를 추가합니다. 도구들은 설명을 개선합니다. 만약 모든 차이점이 경고를 울린다면, 당신은 모두가 무시하는 법을 배우게 되는 알람을 만든 것이며, 무시되는 알람은 아예 없는 것보다 나쁩니다.
따라서 세 번째 아이디어입니다: 유용한 비교는 약속을 깨뜨리는 변화와 단순히 확장하는 변화를 구분해야 합니다. 선택적 필드(optional field)를 추가하는 것은 필수 필드(required field)를 제거하는 것과 같지 않습니다. 새로운 도구가 나타나는 것은, 기존 도구가 조용히 새로운 파라미터(parameter)를 추가하거나, 에이전트(agent)를 의도치 않은 새로운 동작으로 유도할 수 있는 방식으로 설명이 변이하는 것과는 다른 위험입니다. 비교는 안전한 것 대 파괴적인 것, 추가적인 것 대 파괴적인 것을 분류해야 하며, 그래야만 인간의 주의력이 실제로 약속이 깨진 곳에만 집중될 수 있습니다. 이것이 바로 가공되지 않은 차이점 비교(raw diffing)를 팀원들을 지치게 하지 않으면서도 팀 앞에 내놓을 수 있는 무언가로 바꾸는 과정입니다.
드리프트(drift)를 포착하는 가장 저렴한 곳은 배포 전이지만, 그곳이 유일한 장소는 아니다
그렇다면, 이 검사를 어디서 실행해야 할까요? 논리적으로 추론해 보면 두 개의 뚜렷한 시점이 있으며, 당신은 둘 다 필요합니다.
첫 번째는 코드 병합(merge) 전, 당신의 변경 사항이 경계를 넘는 시점입니다. 당신의 통합(integration) 과정이 특정 응답 형태(response shape)를 가정하고 있다면, 그 가정이 틀렸음을 발견해야 하는 시점은 운영 환경(production)에서 새벽 2시에 깨어날 때가 아니라 풀 리퀘스트(pull request) 시점이어야 합니다. 여기서 드리프트(drift)를 잡아내는 것은 비용이 저렴합니다. 이는 변화의 경로에 설치한 게이트(gate)이며, 잘못된 가정이 사용자에게 도달하기 전에 빌드(build)를 실패하게 만듭니다. 이것은 당신이 유발한 드리프트이며, 상류(upstream)에서 차단할 수 있습니다.
하지만 postmark-mcp는 완전히 다른 종류입니다. 피해 측의 그 누구도 아무것도 변경하지 않았습니다. 그들의 코드는 안정적이었습니다. 드리프트는 외부에서, 즉 다른 누군가의 릴리스 일정에 따라, 그들의 마지막 병합 이후 한참 뒤에 발생했습니다. 세상 그 어떤 병합 전 게이트(pre-merge gate)도 이를 잡아낼 수 없었을 것입니다. 왜냐하면 병합 자체가 없었기 때문입니다. 이것이 네 번째 아이디어이며, 대부분의 도구가 놓치는 부분입니다. 가장 위험한 드리프트 중 일부는 당신이 의존하고 있지만 통제할 수 없는 것들에서 발생하며, 당신이 소유하지 않은 시계에 맞춰 지속적으로 발생합니다. 이를 잡아내려면 라이브 표면(live surface)을 계속 폴링(polling)하고, 도구 카탈로그(tool catalog)를 다시 가져오고, 광고된 형태(advertised shapes)를 다시 읽으며, 자체적인 일정에 따라 영원히 수행하다가, 당신이 승인했던 형태가 더 이상 제공되는 형태와 달라지는 순간 즉시 신호를 보내는 무언가가 필요합니다.
그리고 변경되었을 때, 누군가에게 증거와 함께 알려야 합니다
마지막 조각은 거의 허무하게 느껴질 수도 있지만, 대부분의 선의가 죽어가는 지점이기도 합니다. 변경을 감지하고 실행되는 체크(check)라 할지라도, 그 감지 결과가 아무도 읽지 않는 로그(log)에 남겨진다면 무용지물입니다. 가치는 오직 사람이 구체적이고 읽기 쉬운 주장과 함께 방해를 받는 순간에만 실현됩니다: "이 도구의 계약(contract)이 이 시점에, 이러한 방식으로 변경되었습니다. 여기 변경 전과 후의 내용이 있습니다."
다섯 번째 아이디어는 이것입니다: 탐지(detection)는 절반에 불과합니다. 나머지 절반은 전달(delivery)과 이력(history)입니다. 사람에게 도달하는 알림이 필요하며, 계약의 형태가 몇 주에 걸쳐 어떻게 변해왔는지 보여주는 내구성이 있는 기록, 즉 타임라인이 필요합니다. 그래야 무언가 잘못되었을 때, postmark-mcp 피해자들이 며칠 동안 대답하지 못했던 질문에 답할 수 있습니다: "이 변경은 언제 발생했으며, 그 사이에 우리는 무엇에 노출되었는가?"
결론으로 (So, to the landing)
이 아이디어들을 한 줄로 세우면 하나의 단일한 규율(discipline)이 됩니다. 계약을 명시적이고 비교 가능한 형태로 캡처하십시오. 파괴적 변경(breaking change)과 무해한 성장(benign growth)을 구분할 수 있을 만큼 충분한 판단력을 가지고 차이점(diff)을 분석하십시오. 변경 사항이 병합(merge)되기 전에 스스로 게이트(gate)를 설정하십시오. 당신이 제어할 수는 없지만 의존하고 있는 표면(surfaces)들을 지속적으로 폴링(poll)하십시오. 왜냐하면 그들의 드리프트(drift)는 당신의 일정에 맞춰 움직이지 않기 때문입니다. 그리고 약속이 깨졌을 때는, 모든 영수증(receipt, 증거)과 함께 사람에게 알리십시오.
우리는 이 아이디어들을 테스트하고자 DriftGuard에 구현했습니다. 파괴적 변경을 분류하고 CI에 통합되는 로컬 디프(local diff)는 저렴하고 상류(upstream)에서 잡아내는 수단입니다. 라이브 API와 MCP 도구 카탈로그를 폴링하는 지속적인 감시(continuous watches)는 당신이 제어할 수 없는 드리프트(drift)를 위한 것입니다. 즉, 어떤 병합 전 테스트(pre-merge test)로도 볼 수 없는 postmark-mcp-shaped 드리프트 말입니다. 알림(alerting)과 이력(history)은 탐지된 변경 사항을 누군가가 실제로 행동에 옮길 수 있는 결정으로 바꾸어 주는 요소입니다. 이 중 어느 것도 단독으로는 영리하지 않습니다. 이것들이 중요한 이유는 위협이 이제 지속적이고 외부적이라는 점이며, 따라서 방어(guard) 또한 지속적이어야 하기 때문입니다.
첫 번째 악성 MCP 서버가 주는 교훈은 MCP가 위험하다는 것이 아닙니다. 우리가 신뢰를 '한 번 설정하면 끝나는 것'으로 취급해 왔다는 사실입니다. 우리가 신뢰하는 것들이 타인의 조건에 따라 매일 변할 수 있는 세상에서 말이죠. 그 간극을 메우는 것, 즉 약속을 한 번 승인하는 대신 지속적으로 측정하는 것이 우리가 해야 할 일입니다. postmark-mcp는 왜 이 일을 미룰 수 없는지에 대해 우리가 발견한 가장 명확한 근거일 뿐입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기