
웹 페이지 한 장으로 localhost를 신뢰하는 AI 에이전트를 탈취하는 AutoJack
요약
Microsoft 보안 팀이 공개한 AutoJack은 AI 에이전트가 웹 브라우징 중 로컬 서비스(localhost)의 신뢰 경계를 무너뜨려 시스템을 탈취할 수 있는 취약점을 다룹니다. AutoGen Studio의 설정 오류를 이용해 악성 코드가 로컬 프로세스 권한을 획득하는 과정을 설명합니다.
핵심 포인트
- AI 에이전트가 웹을 브라우징할 때 localhost는 더 이상 안전한 신뢰 경계가 아님
- Origin 체크 미흡과 인증 미들웨어 누락이 결합되어 공격 가능
- 에이전트가 실행하는 JavaScript가 로컬 서비스에 대한 권한을 위장하여 탈취 가능
- MCP(Model Context Protocol) 서버 및 WebSocket 설정 시 보안 주의 필요
개발자의 데스크톱에서, 아무런 조작도 하지 않았는데 calc.exe가 실행된다. 계기는 내가 구동 중인 AI 에이전트에게 "이 페이지를 봐줘"라고 부탁한 것뿐이었다. 게다가 계산기를 실행한 것은 브라우저가 아니라, 에이전트의 호스트 프로세스 본체였다.
Microsoft 보안 팀이 6월 18일에 공개한 "AutoJack"은, 이 일견 사소해 보이는 데모에 AI 에이전트 시대의 급소가 응축되어 있다. 타겟이 된 것은 AutoGen Studio로, Microsoft Research의 멀티 에이전트 프레임워크인 AutoGen을 GUI를 통해 다루기 위한 프로토타이핑 도구다. 에이전트를 조립하고, MCP (Model Context Protocol) 서버 등의 도구를 연결하여 수중에 두고 실험한다. 연구자의 개인적인 실험을 위한 환경이며, 본 프로덕션 운영을 상정한 것이 아니다. 그 점이 이번 이야기의 입구가 된다.
로컬에서 동작하는 서비스는 전통적으로 127.0.0.1로부터의 액세스만을 신뢰해 왔다. 외부에서 직접 호출할 수 없기 때문에, 인증을 느슨하게 만들어도 실질적인 피해가 발생하기 어렵다. 많은 개발 도구가 이 전제에 의존하고 있다.
하지만, 웹을 브라우징할 수 있는 에이전트를 동일한 머신에서 실행하면 이야기가 달라진다. Microsoft의 표현이 정확하다.
에이전트가 개방된 웹을 브라우징하면서 특권을 가진 로컬 서비스와도 통신할 수 있게 된 시점에서, localhost는 신뢰 경계(Trust Boundary)가 아니게 된다.
에이전트가 공격자의 페이지를 열면, 그 안의 JavaScript는 "localhost 상의 프로세스에서 나온 요청"으로서 행동할 수 있다. 외부의 악의적인 코드가 내부의 신뢰된 경로로 위장하여 침입한다. 이것이 AutoJack의 본질이며, 특정 버그 하나라기보다는 설계 사상의 괴리를 찌르고 있다.
AutoJack는 단독으로는 무해한 3가지 결함을 사슬처럼 연결한다. 타겟은 모두 AutoGen Studio의 MCP WebSocket 주변이다.
| 연쇄 | 약점 | 발생하는 현상 |
|---|---|---|
| ① 출처 체크 | localhost를 무조건 허용 (CWE-1385) | 에이전트의 헤드리스 브라우저가 localhost를 자처하며 통과 |
| ... |
먼저 ①. WebSocket은 다음과 같이 설정되어 있었다.
allowed_origins = ["http://127.0.0.1", "http://localhost"]
통상적인 브라우저로부터의 외부 액세스를 차단하려는 설정이지만, 에이전트가 조종하는 브라우저는 동일한 머신 위에서 동작한다. Origin 헤더는 당연히 localhost가 되어 허가 리스트를 충족해 버린다.
②는 더욱 단순한 실수다. 인증 미들웨어(Authentication Middleware)가 MCP 계열의 경로를 명시적으로 스킵하고 있었다.
# 이 경로들은 인증을 제외 (각 핸들러 측에서 검증할 예정이었음)
if request.url.path.startswith("/api/ws") or request.url.path.startswith("/api/mcp"):
return await call_next(request)
"핸들러 측에서 체크할 것"이라는 가정하에 제외했으나, 정작 WebSocket 핸들러는 해당 체크를 구현하지 않았다. 인증 모드를 none / github / msal / firebase 중 무엇으로 설정하더라도, 이 입구만은 열려 있게 된다. 책임 전가가 낳은 전형적인 구멍이며, 코드 리뷰에서 가장 놓치기 쉬운 종류의 것이다.
③가 결정타다. 엔드포인트는 server_params라는 쿼리 인자를 base64 디코딩하여, 그 내용을 그대로 StdioServerParams로서 기동한다.
encoded = websocket.query_params.get("server_params")
decoded = base64.b64decode(encoded)
params = StdioServerParams(**json.loads(decoded))
...
실행 파일의 허가 리스트(Allowlist)가 없다. 즉, 공격자는 실행하고 싶은 명령어를 JSON으로 전달하기만 하면 된다.
{
"type": "StdioServerParams",
"command": "calc.exe",
...
}
공격의 흐름은 다음과 같다. 악의적인 페이지의 JavaScript가 ws://localhost:8081/api/mcp/ws/?server_params=<base64페이로드>로 WebSocket 연결을 시도한다. 개발자가 에이전트에게 해당 페이지를 보도록 지시하면, 에이전트의 브라우저가 JS를 실행하여 ①단계의 출처 확인(Origin Check)을 통과하고, ②단계의 인증을 통과하며, ③단계에서 페이로드의 명령어가 개발자의 권한으로 실행된다. 사용자의 조작은 단지 URL을 전달하는 것뿐이라는 점이 이 공격의 무서운 점이다. 데모에서 calc.exe가 사용된 이유는 무해하기 때문이며, 이를 powershell.exe나 bash로 교체하면 파일 탈취나 멀웨어 상주도 동일한 방식으로 수행할 수 있다.
이 부분은 정확히 짚고 넘어가야 한다. 자극적인 기사가 되기 쉬운 내용이지만, 영향 범위는 제한적이다. 취약한 MCP WebSocket 구현(autogenstudio/web/routes/mcp.py)은 PyPI를 통해 배포된 버전에는 단 한 번도 포함된 적이 없다. pip install autogenstudio로 도입한 사용자는 이 체인의 영향을 받지 않는다. 위험한 경우는 GitHub의 main 브랜치를 해당 기능이 포함된 시점부터 수정 전까지의 기간에 빌드하여 사용했던 케이스로 한정된다.
뒤집어 생각하면, "연구용 프로토타입을 최신 소스에서 따라가며 실행하는" 개발자일수록 빠지기 쉬운 구조이기도 하다. 최첨단을 다루는 사람일수록 리스크에 가깝다는 사실은 에이전트 개발 전반에 통용되는 아이러니다.
수정 커밋 b047730은 근본적인 데이터 흐름(Data Flow)을 재설계했다. 파라미터를 URL에 싣는 방식을 중단하고, 서버 측에 보유하는 방식으로 변경했다. POST /ws/connect를 통해 파라미터를 서버 내부의 pending_session_params에 등록하며, 반환되는 것은 session_id뿐이다. WebSocket이 연결되는 순간 그 자리에서 파라미터를 pop(꺼내어 삭제)한다. 커밋 메시지는 그 의도를 명확히 설명하고 있다.
이를 통해 공격자가 WebSocket의 쿼리 문자열(Query String)을 통해 임의의 server_params를 주입하는 것을 방지한다.
알 수 없는 세션 ID로 들어온 연결은 클로즈 코드(Close Code) 4004로 거부된다. 이와 함께 인증 제외 목록도 축소하여, /api/mcp는 더 이상 스킵되지 않는다.
흥미로운 점은 동일한 커밋이 또 다른 RCE(원격 코드 실행) 경로도 차단했다는 것이다. FunctionTool의 _from_config()가 사용자 제공 소스 코드를 exec()로 실행하고 있었기 때문에, 설정값을 주입하는 것만으로 코드가 실행되는 구멍이 되어 있었다. 수정 후에는 인스턴스화를 수행하지 않고 스키마 검증(Schema Validation)만으로 처리한다. AutoJack 조사가 근처에 있던 또 다른 지뢰를 찾아낸 격이다. 현재까지 CVE 번호는 부여되지 않았다.
이 버그 자체는 이미 수정되었다. 하지만 Microsoft가 강조한 것은 훨씬 더 긴 안목의 이야기다.
이 패턴(머신 위의 에이전트가 localhost 서비스에 도달하는 것)은 이 하나의 버그보다 더 넓은 문제다.
브라우징과 실행이 모두 가능한 에이전트를 로컬에서 실행한다면, AutoGen Studio에 국한되지 않고 동일한 함정이 기다리고 있다. 사용자의 머신에는 인증이 허술한 로컬 서비스가 여러 개 돌아가고 있을 것이다. 개발 서버, 디버그 포트, 각종 MCP 서버 등 말이다. 그것들은 "외부에서 오지 않는다"는 전제하에 보호되어 왔지만, 이제 스스로 그 전제를 내부에서 무너뜨리는 존재를 불러들이고 있다.
실무에서 할 수 있는 대책은 그리 파격적이지 않다. 에이전트의 브라우징용 ID와 개발자 본인의 ID를 분리하는 것(별도의 OS 사용자, 컨테이너, VM에 격리)이다. 신뢰할 수 없는 콘텐츠를 처리하는 에이전트를 권한이 높은 서비스와 동일한 호스트에서 실행하지 않는 것이다. 로컬 서비스는 루프백(Loopback)에만 바인딩하고, 방화벽으로 비루프백 접속을 차단해야 한다. MCP 실행 파일에는 허용 목록(Allowlist)을 설정해야 한다. 요컨대, 에이전트를 "나와 같은 권한을 가진 동거인"이 아니라 "신뢰 범위가 다른 외부 프로세스"로 다시 취급하겠다는 발상의 전환이 필요하다.
AI 에이전트의 보안 논의는 프롬프트 인젝션(Prompt Injection)에 치우치기 쉽지만, AutoJack이 보여준 것은 주입된 프롬프트 너머의 실행 환경 자체가 허술하면 결국 호스트 전체를 탈취당할 수 있다는 사실이다. 에이전트에 능력을 추가할 때마다, 자신의 신뢰 경계(Trust Boundary)가 어디로 이동했는지 다시 그려보는 습관을 가져야 한다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기