본문 바로가기
C.W.K.
Stream
Lesson 02 of 05 · published

Origin 과 localhost 함정

~22 min · origin, localhost, dns-rebinding, cors

Level 0호기심 많은 독자
0 XP0/48 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

http://127.0.0.1:port 에 떠 있는 로컬 MCP server 는 무해해 보여. 안 무해해. 명시적인 방어가 없으면 아무 브라우저 탭의 웹페이지가 네 로컬 MCP server 와 대화해서 데이터를 빼갈 수 있어. DNS rebinding 을 쓰거나, 악의적인 origin header 를 들이밀거나, 허술한 CORS 를 파고들어서.

MCP spec 은 못박아놨어. localhost 에 붙은 server 는 모든 요청의 Origin header 를 반드시 검사하고, 허용 목록에 없으면 거절해야 한다고. 그리고 그 허용 목록의 기본값은 비어 있어야 해. 이게 없으면 https://evil.example.com 페이지가 fetch('http://127.0.0.1:1234/mcp', {…}) 를 던질 수 있고, server 는 user 가 설치해둔 tool 을 고분고분 다 내줘.

DNS rebinding 은 한 수 위의 공격이야. 공격자가 도메인을 하나 등록해두고, DNS 응답이 바깥 IP (첫 페이지를 띄울 때) 와 127.0.0.1 (짧은 TTL 이 지난 뒤) 사이를 오가게 만들어. 브라우저가 캐시한 origin 은 여전히 'evil.example.com' 인데, 실제 네트워크 호출은 user 의 로컬 server 에 꽂히는 거지. Host header 까지 허용 목록으로 (예를 들어 localhost127.0.0.1 만) 검사하면 막히지만, Origin 만 보고 있으면 못 막아.

로컬 server 는 127.0.0.1 에 명시적으로 붙이고, 0.0.0.0 에는 절대 붙이지 마. 모든 요청에서 OriginHost 를 검사해. CORS preflight 가 아닌 경로에 Origin 이 빠져 있으면 거절하고. 대략 여섯 줄짜리 middleware 인데, 그중 뺄 수 있는 줄은 하나도 없어.

Code

엄격한 origin & host 검증 (FastAPI middleware)·python
from fastapi import Request, HTTPException

ALLOWED_ORIGINS = {"app://claude-desktop", "https://localhost:5173"}
ALLOWED_HOSTS = {"127.0.0.1", "localhost"}

@app.middleware("http")
async def origin_host_guard(request: Request, call_next):
    origin = request.headers.get("origin", "")
    host = request.url.hostname or ""
    if origin and origin not in ALLOWED_ORIGINS:
        raise HTTPException(403, "origin not allowed")
    if host not in ALLOWED_HOSTS:
        raise HTTPException(403, "host not allowed")
    return await call_next(request)
127.0.0.1 에 bind, 0.0.0.0에는 bind하지 마·python
# 맞음
uvicorn.run(app, host="127.0.0.1", port=1234)

# 잘못 — 로컬 네트워크와 모든 인터페이스에 server 노출
uvicorn.run(app, host="0.0.0.0", port=1234)

External links

Exercise

Origin/Host 검사 없이 작은 로컬 MCP server 를 0.0.0.0:port 에 띄워. 전혀 상관없는 사이트의 (file:// 이든 네가 제어하는 공개 사이트든) 별도 브라우저 탭에서 devtools 를 열고 fetch('http://127.0.0.1:port/mcp') 를 던져봐. 성공하는 걸 확인해. 그 다음 127.0.0.1 에 붙이고 middleware 를 넣어. 실패하는 걸 확인해. 이 실습이 곧 교훈이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.