메뉴
HN
Hacker News 8일 전

사람은 안 보이지만 AI는 보이는 MCP 서버의 보안 취약점

IMP
8/10
핵심 요약

터미널 제어용으로 쓰이는 ANSI 이스케이프 시퀀스를 악용해, 사용자 눈에는 보이지 않지만 AI 모델에게는 악성 지시문이 읽히도록 숨기는 새로운 프롬프트 인젝션 공격이 대두되었습니다. 특히 AI 에이전트와 연결되는 MCP 서버 환경에서 이 취약점이 치명적으로 작용할 수 있으며, DAST(동적 애플리케이션 보안 테스트) 스캐너를 통해 이를 탐지하고 방어하는 방안의 중요성이 강조됩니다.

번역된 본문

DAST로 MCP 서버의 ANSI 이스케이프 시퀀스 인젝션 탐지하기

ANSI 이스케이프 시퀀스는 터미널을 위해 설계되었지, 대형 언어 모델을 위해 만들어진 것이 아닙니다. 이는 터미널에 색상을 변경하거나, 텍스트를 숨기거나, 화면을 지우거나, 커서를 이동하도록 지시하는 보이지 않는 제어 코드입니다. 렌더링된 터미널 화면을 읽는 사람은 이 코드 자체를 절대 볼 수 없으며, 오직 그 결과만 보게 됩니다. 하지만 동일한 내용을 읽는 언어 모델은 모든 바이트를 그대로 읽어냅니다. 이 격차가 바로 이 공격의 핵심입니다. 공격자가 AI 에이전트가 소비하는 텍스트에 ANSI 이스케이프 시퀀스를 밀반입할 수 있다면, 출력물을 검토하는 인간의 눈에는 지시문을 숨기면서도 모델에는 완벽하게 읽히도록 만들 수 있습니다.

이는 실제 제품에서 발견됐던 출력 무효화 실패 취약점과 동일한 종류입니다. 과거 Kubernetes kubectl (CVE-2021-25743)은 터미널 출력을 위조할 수 있는 제어 시퀀스를 Event 문자열에서 허용했으며, Git (CVE-2024-52005)은 터미널 제어 코드를 제거하지 않은 채 원격 사이드밴드 메시지를 출력했습니다. 모델 컨텍스트 프로토콜(MCP) - 서버가 텍스트를 에이전트로 직접 스트리밍하는 도구, 리소스 및 프롬프트를 노출하는 환경 - 에서는 모델이 읽는 모든 필드가 잠재적인 인젝션 표면이 됩니다.

DAST(동적 애플리케이션 보안 테스트)는 이러한 종류의 결함을 잡는 자연스러운 방법입니다. Bright 스캐너는 외부에서 실행 중인 대상을 테스트하고, 조작된 입력을 보낸 뒤, 실제 응답을 검사하여 소스 코드 접근 없이도 취약점의 증거를 찾아냅니다. 페이로드가 살아남는지 여부는 실제 서버가 이를 어떻게 처리하고 전달하는지에 전적으로 달려 있기 때문에, 이러한 블랙박스 런타임 방식이야말로 ANSI 이스케이프 시퀀스 인젝션(AESI) 탐지에 꼭 필요합니다. 이 글에서는 두 가지 공격 변형인 직접 패치 AESI와 저장형 AESI, 이들이 초래하는 위험, 그리고 가장 중요한 DAST 스캐너가 이를 어떻게 자동으로 탐지하는지 다룹니다.

ANSI 이스케이프 시퀀스란 무엇인가? ANSI 이스케이프 시퀀스는 1970년대로 거슬러 올라갑니다. 당시 다양한 하드웨어 터미널 벤더들은 서로 호환되지 않는 고유한 제어 방식을 사용했습니다. ANSI X3.64 표준은 이를 통합하여, 프로그램이 커서 이동, 화면 지우기 또는 색상 설정을 위해 합의된 하나의 코드 세트를 내보내면 모든 호환 터미널이 이를 따르도록 만들었습니다. 텍스트 터미널을 휴대 가능하고 제어 가능하게 만들기 위해 발명되었으며, 오늘날의 공격은 바로 이 제어 기능을 악용하는 것입니다.

ANSI 이스케이프 시퀀스는 이스케이프 바이트(ESC, 16진수 0x1B)로 시작하여 터미널이 일반 텍스트가 아닌 명령으로 처리하는 제어 코드로 이어집니다. 여기서 중요한 두 가지 카테고리는 텍스트를 보이지 않게 만드는 '숨김 코드'와 디스플레이를 지우거나 커서를 이동시켜 정상적인 출력물을 지우거나 덮어쓰는 '화면 및 커서 코드'입니다. 핵심은 렌더링 과정에서 이 바이트들이 사람의 눈에 보이지 않게 숨겨지지만, 모델은 원시 텍스트를 그대로 처리한다는 점입니다. 사람에게는 단순한 빈 줄이나 정리된 보고서로 보이는 것이 에이전트에게는 완전한 지시문 세트로 읽힐 수 있습니다.

공격 1: 직접 패치 (Direct-fetch) AESI 많은 MCP 서버는 URL을 패치(Fetch)하여 모델에 내용을 반환하는 도구, 즉 '이 페이지 요약해 줘' 또는 '이 문서 읽어 줘'와 같은 기능을 노출합니다. 공격은 다음과 같이 작동합니다. 공격자는 자신이 제어하는 URL에 ANSI 이스케이프 시퀀스와 숨겨진 지시문이 섞인 콘텐츠를 호스팅합니다. 해당 URL이 패치 도구의 인자로 제공됩니다. 서버는 콘텐츠를 검색하여 에이전트가 실제로 섭취하는 응답 위치, 즉 '모델 소비 가능 필드(model-consumable field)'로 전달합니다. 모델은 이 숨겨진 지시문을 읽고 이에 따라 행동합니다.

여기서 짚고 넘어가야 할 핵심 개념은 바로 '모델 소비 가능 필드'입니다. MCP 응답의 모든 부분이 모델에 전달되는 것은 아닙니다. 중요한 것은 에이전트가 콘텐츠로 취급하는 특정 필드들입니다.

  • 도구 결과: result.content[].text 내부의 텍스트
  • 리소스 읽기: result.contents[].text 내부의 텍스트
  • 프롬프트 템플릿: result.messages[].content.text 내부의 텍스트

페이로드가 다른 곳에 나타난다면 악용할 수 없습니다. 이는 크로스 프롬프트 인젝션의 한 형태로, 악의적인 지시문이 에이전트가 신뢰하는 데이터에 숨어 들어가는 방식입니다.

원문 보기
원문 보기 (영어)
Detecting ANSI Escape Sequence Injection in MCP Servers with DAST ANSI escape sequences were designed for terminals, not for language models. They are invisible control codes that tell a terminal to change colors, hide text, clear the screen, or move the cursor. A human reading a rendered terminal never sees the codes themselves — only their effect. A language model reading the same content sees every byte. That gap is the whole attack. If an attacker can smuggle ANSI escape sequences into text that an AI agent consumes, they can hide instructions from the human reviewing the output while leaving those instructions perfectly legible to the model. This is the same class of output-neutralization failure seen in real products: Kubernetes `kubectl` ([CVE-2021-25743]( https://nvd.nist.gov/vuln/detail/CVE-2021-25743 )) accepted control sequences in Event strings that could spoof terminal output, and Git ([CVE-2024-52005]( https://github.com/git/git/security/advisories/GHSA-7jjc-gg6m-3329 )) printed remote sideband messages without neutralizing terminal controls. In the Model Context Protocol (MCP) — where servers expose tools, resources, and prompts that stream text straight into an agent — every field the model reads is a potential injection surface. DAST is the natural way to catch this class of flaw. The Bright scanner exercises a running target from the outside, sends crafted inputs, and inspects real responses for evidence of a vulnerability — no source access required. That black-box, runtime posture is exactly what ANSI Escape Sequence Injection (AESI) demands, because whether a payload survives depends entirely on how the live server processes and relays it. This article covers two variants of the attack, direct-fetch AESI and stored AESI , what they put at risk, and, most importantly, how a DAST scanner detects them automatically. What ANSI escape sequences are ANSI escape sequences date back to the 1970s, when hardware terminals from different vendors each spoke their own incompatible control dialect. The ANSI X3.64 standard unified them: a program could emit one agreed-upon set of codes to move the cursor, clear the screen, or set colors, and any conforming terminal would obey. They were invented to make text terminals controllable in a portable way — and that same control capability is what the attack abuses today. An ANSI escape sequence starts with the escape byte (`ESC`, hex `0x1B`) followed by a control code the terminal treats as a command rather than printable text. Two categories matter here: concealment codes that make text invisible, and screen and cursor codes that clear the display, move the cursor, or erase a line to scrub or overwrite legitimate-looking output. The key insight: rendering hides these bytes from people, but a model processes the raw text. What looks like an empty line or a tidy report to a human can carry a full set of instructions to the agent. Attack 1: Direct-fetch AESI Many MCP servers expose a tool that fetches a URL and returns its contents to the model — a "summarize this page" or "read this document" capability. The attack works like this: The attacker hosts content laced with ANSI escape sequences and hidden instructions at a URL they control. That URL is supplied to a fetch-style tool as an argument. The server retrieves the content and relays it into a model-consumable field — a response location the agent actually ingests. The model reads the concealed instructions and acts on them. The concept to hold onto here is the model-consumable field . Not every part of an MCP response reaches the model. What matters are the specific fields an agent treats as content: tool results — the text inside result.content[].text resource reads — the text inside result.contents[].text prompt templates — the text inside result.messages[].content.text A payload that surfaces anywhere else is not exploitable. This is a form of cross-prompt injection: the malicious instructions ride in on data the agent was asked to process, not on the user's own prompt. Attack 2: Stored AESI Stored AESI is the same payload with a more dangerous delivery mechanism. Instead of the server fetching a live URL, the attacker writes the payload into storage through one entrypoint — a note, a comment, a record — and it detonates later when a different entrypoint reads that data back into a model field. Three properties make the stored variant worse than direct-fetch: Persistence. The payload sits in storage and fires again on future sessions, potentially against other users. Decoupling. The write and the read are different entrypoints, and they can even use different protocols. A payload written through an ordinary HTTP endpoint can resurface when the agent reads the same record back through an MCP tool or resource. Delayed exposure. Nothing looks wrong at write time. The injection only becomes active when some later, unrelated read pulls the stored text into the model's context. Crucially, you cannot infer these write-then-read paths from parameter names, tool descriptions, or protocol type. The only reliable way to know where written data resurfaces is to probe for it empirically — which is exactly what the automated detection does. Security impact Unauthorized agent actions. Concealed instructions can cause the model to invoke tools, disclose data, alter analysis, or produce attacker-controlled output within the agent's existing privileges. Human-in-the-loop bypass. ANSI concealment can hide attacker instructions from the user supervising or approving the agent. The visible response appears benign, while the model still receives the raw payload and may follow it. Log and audit trail manipulation. Screen-clear, cursor-movement, and line-erasure sequences can spoof terminal or log-viewer output, obscuring activity during review and incident response. Persistent injection risk. In stored AESI, the payload remains in application data and can reappear through later MCP tools, resources, or prompts. A single write can affect future sessions, other users, and cross-protocol read paths. In practice, one poisoned record in a shared knowledge base can influence every later agent workflow that reads it. The risk is not just a misleading summary; it is attacker-controlled data reaching an agent with access to tools, credentials, and downstream systems. Automating detection with DAST The heart of this work is turning the attack into a repeatable, automated test. The detection is built on a few design principles, then applied through two pipelines — one for direct-fetch, one for stored. Design principles Inspect raw bytes. The ANSI escape bytes are the signal, so any rendering, sanitizing, or normalizing before inspection destroys it. Detection reads the raw HTTP/MCP response bytes. Only model-consumable fields count — the sink. The classifier walks the JSON paths a model actually ingests (tool content, resource contents, prompt message text) and ignores everything else. Unique trigger markers. Each payload carries its own improbable marker, so a match ties an effect back to exactly one injection — the mechanism black-box confirmation depends on. Three-signal confirmation. A field is vulnerable only when an ANSI escape byte, a fixed instruction phrase, and the payload's trigger all survive together in the same model-consumable field. Requiring all three is what suppresses false positives. Recurse into encoded values. The classifier descends into stringified JSON so a payload wrapped inside a JSON-encoded field is still caught. Detection is therefore binary — the three-signal rule holds or it doesn't — which is what makes the check low-noise enough to automate. The payload corpus Injections are driven from a small corpus of hosted payload files, each exercising a different ANSI technique (concealment, background-matched foreground, clear-screen, cursor-erase, and an HTML-wrapped variant) with its own trigger. Variety matters because a server that strips one tec