본문으로 건너뛰기
피드

30살 된 FastCGI가 아직도 리버스 프록시 백엔드 프로토콜로 더 낫다는 주장

backend 약 8분
vote
0
댓글
북마크

HTTP를 리버스 프록시와 백엔드 사이 프로토콜로 쓰는 관행이 desync 공격과 신뢰 헤더 문제를 계속 만든다는 글이다. 저자는 FastCGI가 1996년 나온 오래된 프로토콜이지만 명시적 프레이밍과 신뢰 정보 분리 덕분에 이 구간에서는 HTTP보다 안전한 선택일 수 있다고 주장한다.

  • 1

    HTTP/1.1은 메시지 경계가 애매해 리버스 프록시와 백엔드가 서로 다르게 파싱할 수 있고, 이게 request smuggling의 뿌리다.

  • 2

    FastCGI는 요청 경계를 명확히 나누고, 클라이언트 헤더와 프록시가 넣은 신뢰 정보를 구조적으로 분리한다.

  • 3

    nginx는 첫 릴리스부터 FastCGI 백엔드를 지원했지만, HTTP/2 백엔드 지원은 2025년 말에야 들어갔다.

  • 4

    FastCGI는 WebSocket 미지원, 부족한 도구, 덜 최적화된 성능 같은 현실적인 단점도 있다.

  • 리버스 프록시와 백엔드 사이에 HTTP를 쓰는 게 너무 당연해졌지만, 이 글은 그 기본값이 꽤 위험하다고 찌름

    • 최근 Discord 미디어 프록시에서도 desync 취약점이 공개됐고, 이 취약점은 비공개 첨부파일을 엿볼 수 있는 수준이었다고 함
    • 저자는 이런 문제가 특이한 사고가 아니라 HTTP 리버스 프록시 구조에서 반복적으로 나오는 패턴이라고 봄
  • 핵심 원인은 HTTP/1.1의 메시지 경계가 애매하다는 점임

    • HTTP/1.1은 겉으로는 텍스트라 단순해 보이지만, 같은 메시지도 여러 방식으로 표현할 수 있고 파서마다 해석이 달라질 수 있음
    • 메시지가 어디서 끝나고 다음 메시지가 어디서 시작되는지 프록시와 백엔드가 다르게 보면 request smuggling, 즉 HTTP desync 공격이 가능해짐
    • 보안 연구자 James Kettle은 이런 류의 파서 차이를 계속 찾아냈고, 결국 “HTTP/1.1 must die”라고까지 말했다고 함

⚠️주의

> HTTP desync는 단순 파서 버그 하나 고치는 문제로 끝나지 않음. 프로토콜 자체가 애매한 경계를 허용하면 구현체 차이가 계속 공격면이 됨.

  • FastCGI는 1996년에 나온 30년 된 프로토콜이지만, 이 문제 하나만큼은 처음부터 더 깔끔하게 피하고 있었음

    • FastCGI는 프로세스 실행 모델이 아니라 wire protocol이라, 백엔드 데몬에 TCP나 유닉스 소켓으로 요청을 보내는 식으로 쓸 수 있음
    • Go에서는 net/http/fcgi를 import하고 http.Servefcgi.Serve로 바꾸면 기존 http.ResponseWriter, http.Request 핸들러를 그대로 쓸 수 있다고 함
    • Apache, Caddy, nginx, HAProxy 모두 FastCGI 백엔드를 지원하고, 설정도 HTTP 프록시 설정과 크게 다르지 않음
  • HTTP/2는 메시지 프레이밍을 명확히 해서 desync 문제를 줄이지만, 배포 현실은 그렇게 단순하지 않음

    • 프록시와 백엔드 사이에서 HTTP/2를 일관되게 써야 효과가 있는데, nginx는 첫 릴리스부터 FastCGI 백엔드를 지원한 반면 HTTP/2 백엔드 지원은 2025년 말에야 들어갔다고 함
    • Apache의 HTTP/2 백엔드 지원도 아직 “experimental” 상태라고 언급됨
    • 반대로 FastCGI는 1996년부터 명확한 요청 경계를 가진 더 단순한 프로토콜이었다는 게 저자의 포인트임
  • 두 번째 문제는 프록시가 넣은 신뢰 정보를 HTTP 헤더로 전달하는 방식임

    • 실제 클라이언트 IP, 인증된 사용자명, mTLS 클라이언트 인증서 정보 같은 값은 백엔드가 믿어야 하는 데이터임
    • 그런데 HTTP에서는 이 값들을 결국 X-Real-IP 같은 헤더에 넣게 되고, 클라이언트가 보낸 헤더와 구조적으로 섞임
    • 프록시가 X-Real-IP를 지운다 해도 True-Client-IP 같은 다른 헤더를 미들웨어가 먼저 신뢰하면 공격자가 조작한 값이 백엔드까지 들어갈 수 있음
  • FastCGI는 이 부분에서 클라이언트 헤더와 프록시 신뢰 정보를 구조적으로 분리함

    • 클라이언트가 보낸 HTTP 헤더는 HTTP_ 접두사가 붙은 파라미터로 전달됨
    • 반면 실제 클라이언트 IP는 REMOTE_ADDR 같은 표준 파라미터로 전달되기 때문에, 클라이언트가 보낸 헤더가 신뢰 정보처럼 위장하기 어려움
    • Go의 net/http/fcgiREMOTE_ADDR를 자동으로 http.Request.RemoteAddr에 채워줘서 별도 미들웨어 없이도 동작한다고 함

중요

> 저자가 보는 FastCGI의 진짜 장점은 속도가 아니라 보안 모델임. 요청 경계와 신뢰 데이터의 출처가 프로토콜 레벨에서 분리돼 있다는 게 포인트다.

  • 물론 FastCGI가 만능은 아님. 30년 된 기술답게 단점도 선명함

    • WebSocket을 지원하지 않음
    • curl로 FastCGI 서버에 바로 요청을 보낼 방법도 없음. 저자는 curl이 FTP, Gopher, SMTP까지 지원하면서 FastCGI는 안 된다고 꼬집음
    • Go FastCGI 서버를 여러 리버스 프록시 뒤에서 벤치마크했을 때 일부 워크로드는 HTTP/1.1이나 HTTP/2보다 처리량이 낮았다고 함
    • 다만 저자는 이게 프로토콜 자체의 한계라기보다 FastCGI 코드 경로가 HTTP만큼 최적화되지 않았기 때문일 수 있다고 봄
  • 결론은 꽤 현실적임. WebSocket이 필요 없고 성능 병목이 아니라면 FastCGI는 지금도 충분히 쓸 만하다는 것

    • 저자는 SSLMate에서 FastCGI를 10년 넘게 프로덕션에서 쓰고 있다고 밝힘
    • 병목이 생기면 HTTP 리버스 프록시의 보안 지뢰밭을 감당하기보다 차라리 하드웨어를 더 사겠다는 입장임

기술 맥락

  • 여기서 선택지는 “프록시와 백엔드 사이도 HTTP로 말할 것인가, 아니면 FastCGI 같은 전용 프로토콜을 쓸 것인가”예요. 보통은 관성적으로 HTTP를 고르지만, 저자는 이 구간에서는 범용성보다 메시지 경계와 신뢰 정보 분리가 더 중요하다고 보는 거예요.

  • HTTP/1.1이 위험해지는 이유는 요청의 끝을 메시지 내부 값으로 판단하기 때문이에요. 프록시와 백엔드가 Content-Length, Transfer-Encoding 같은 단서를 다르게 해석하면 같은 바이트 스트림이 서로 다른 요청으로 보일 수 있거든요.

  • FastCGI는 요청을 레코드 단위로 나누고, 클라이언트 헤더와 프록시가 추가한 환경 정보를 다른 이름 공간으로 보냅니다. 그래서 X-Real-IP를 지웠는지, 대소문자 변형까지 처리했는지 같은 방어 로직에 덜 기대게 돼요.

  • 트레이드오프도 분명해요. WebSocket이 필요하거나 HTTP 도구 생태계에 깊게 기대는 서비스라면 FastCGI 전환이 번거로울 수 있어요. 반대로 일반적인 요청-응답 백엔드이고 앞단 보안 경계가 중요하다면, 오래된 프로토콜이라는 이유만으로 제외할 선택지는 아니라는 얘기예요.

요지는 '오래된 기술이라 낡았다'가 아니라, 특정 문제를 풀기 위해 설계된 프로토콜이 범용 HTTP보다 나을 수 있다는 얘기다. 백엔드 앞단을 HTTP로만 붙이는 게 당연하다고 생각했다면 한 번쯤 아키텍처 기본값을 의심해볼 만하다.

댓글

댓글

댓글을 불러오는 중...

backend

SQLite를 프로덕션에서 쓰려면 WAL, 락, VFS를 제대로 알아야 한다

이 글은 SQLite를 단순한 로컬 임베디드 DB가 아니라 저지연 서버용 데이터베이스로 쓰는 방법을 정리한다. WAL 모드, busy timeout, BEGIN IMMEDIATE, cache_size, mmap_size, VFS 기반 복제까지 실전 설정이 꽤 구체적으로 나온다. 다만 분산 쓰기나 테라바이트급 데이터셋에는 여전히 PostgreSQL 같은 서버형 DB가 맞다는 선도 분명히 긋는다.

backend

데카트론 독일, 웹 결제에 유럽식 계좌이체 ‘웨로’ 붙였다

데카트론 독일이 자사 웹사이트 결제 수단에 유럽 결제 이니셔티브의 웨로를 도입했다. 카드 네트워크를 거치지 않는 실시간 계좌 간 결제로 수수료를 낮추고, 멤버십과 결제를 묶어 고객 유지까지 노리는 움직임이다.

backend

SMTP를 HTTP 위에서 다시 만든다면? 기존 부품만 조립한 현대식 이메일 실험

글쓴이는 SMTP를 대체하려는 게 아니라, HTTP·WebFinger·Ed25519·HPKE·JMAP 같은 이미 검증된 기술로 이메일의 문제를 다시 설계해보는 HMTP 실험을 소개해. 핵심은 주소 형태만 user@domain으로 유지하고, 전송·발견·서명·암호화·스팸 방어·읽기 모델을 전부 현대 웹 인프라 위에 얹는 거야.

backend

버셀 실험실의 Scriptc, TypeScript를 Node 없이 네이티브 바이너리로 컴파일

Vercel Labs가 공개한 Scriptc는 일반 TypeScript 코드를 Node, V8, JavaScript 엔진 없이 작은 네이티브 실행 파일로 컴파일하는 실험적 컴파일러다. 정적으로 컴파일 가능한 코드와 동적 실행이 필요한 코드를 명확히 나누고, Node와 결과가 바이트 단위로 맞는지 800개 이상 테스트로 검증한다.

backend

고의 새 가비지 컬렉터 ‘그린 티’, 힙을 훑는 방식이 어떻게 달라졌나

고 1.25에서 도입되고 1.26에서 기본값이 된 새 가비지 컬렉터 ‘그린 티’를 실제 힙 배치와 성능 측정으로 뜯어본 글이다. 핵심은 포인터를 무작정 따라가는 대신 메모리 span 단위로 훑어 캐시 친화성을 높였다는 점이고, 동시에 고의 비이동식 컬렉터가 여전히 sparse page 회수에 약하다는 한계도 같이 보여준다.