엄격한 Content-Type 검사

엄격한 Content-Type 검사

기본적으로 FastAPI는 JSON 요청 본문에 대해 엄격한 Content-Type 검사를 해요. 즉 JSON 요청은 본문을 JSON으로 파싱하려면 유효한 Content-Type 헤더(예: application/json)를 반드시 포함해야 해요. 이 기본 동작은 특정 시나리오에서 CSRF(사이트 간 요청 위조) 공격에 대한 보호를 제공하는데요, 언제 필요한지 그리고 비활성화해야 할 때는 어떻게 하는지 살펴볼게요.

출처: FastAPI 공식 문서 - Strict Content-Type Checking

CSRF 위험

이 기본 동작이 막아주는 공격은 아주 특정한 상황에서 발생해요. 브라우저는 스크립트가 CORS preflight 검사 없이 요청을 보내는 걸 허용하는 경우가 있는데, 그 조건은 이 두 가지예요.

  • Content-Type 헤더가 없을 때 (예: fetch()Blob 본문을 쓴 경우)
  • 어떤 인증 자격 증명도 보내지 않을 때

이 유형의 공격은 주로 이런 상황에서 문제가 돼요.

  • 애플리케이션이 로컬(localhost 등) 또는 내부 네트워크에서 실행 중일 때
  • 애플리케이션에 인증이 없어서 같은 네트워크에서 온 요청은 신뢰해도 된다고 가정할 때

공격 예시

로컬 AI 에이전트를 실행하는 앱을 만든다고 상상해 봐요. API는 이 주소에 있고,

http://localhost:8000/v1/agents/multivac

프론트엔드도 같은 호스트인 http://localhost:8000에 있어요.

로컬에서 실행되니까 인터넷에 노출되지 않아서 인증을 걸지 않고, 로컬 네트워크 접근만 신뢰하기로 결정했어요. 그런데 사용자가 이걸 설치해서 로컬에서 실행한다면, https://evilhackers.example.com 같은 악성 웹사이트를 열 수도 있어요. 그 악성 사이트가 fetch()Blob 본문으로 로컬 API에 요청을 보내면, 악성 사이트와 로컬 앱의 호스트가 달라도 브라우저는 CORS preflight 요청을 발생시키지 않아요. 왜냐하면:

  • 인증 없이 실행 중이라 자격 증명을 보낼 필요가 없고
  • 브라우저는 Content-Type 헤더가 없으니 JSON을 보내는 게 아니라고 생각하거든요.

이렇게 해서 악성 웹사이트가 로컬 AI 에이전트로 하여금 사용자 대신 나쁜 일을 하게 만들 수 있어요.

공개 인터넷이라면

앱이 공개 인터넷에 있다면 네트워크를 신뢰하지 않고, 인증 없이 특권 요청을 아무에게나 보내게 두지 않을 거예요. 공격자는 브라우저 상호작용 없이 스크립트로 바로 요청을 보낼 수 있으니, 이미 특권 엔드포인트는 보호하고 있을 테고요. 그래서 이 위험과 공격은 일반적으로 해당되지 않아요. 이 위험은 앱이 로컬 네트워크에서 실행되고, 그 네트워크가 유일한 보호 수단일 때 주로 문제가 돼요.

Content-Type 없는 요청 허용하기

Content-Type 헤더를 보내지 않는 클라이언트를 지원해야 한다면, strict_content_type=False로 설정해서 엄격 검사를 끌 수 있어요.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(strict_content_type=False)


class Item(BaseModel):
    name: str
    price: float


@app.post("/items/")
async def create_item(item: Item):
    return item

이 설정에서 Content-Type 헤더가 없는 요청도 본문을 JSON으로 파싱해요. 이건 옛 버전 FastAPI의 동작과 같아요. 이 동작과 설정은 FastAPI 0.132.0에 추가되었어요.

더 알아보기 (Learn more)