PRO DBDATA PRO DBDATA

HTTP/3 QUIC 0-RTT 동작 원리와 Replay Attack 방어 설계 가이드

읽는 시간 약 18분
HTTP/3 QUIC 0-RTT 지연시간 분석과 Replay Attack 방어 설계

HTTP/3를 도입하면 TCP 연결과 별도의 TLS 협상에 필요했던 대기 시간을 줄일 수 있습니다. 특히 QUIC 0-RTT는 이전에 접속한 클라이언트가 서버 응답을 기다리지 않고 첫 패킷에 HTTP 요청을 담아 보낼 수 있어, 네트워크 왕복 지연이 큰 환경에서 초기 응답 시간을 단축하는 데 유리합니다.

그러나 0-RTT가 활성화됐다고 해서 모든 요청이 빨라지는 것은 아닙니다. 최초 접속에는 사용할 수 없고, 세션 재개 조건과 Session Ticket이 유효해야 합니다. 게이트웨이와 애플리케이션이 Early Data를 안전하게 처리하지 못하면 425 응답 증가, 0-RTT 거부, 동일 주문이나 결제 요청의 중복 처리와 같은 문제가 발생할 수 있습니다.

일반적인 네트워크 모니터링만으로 원인을 찾기도 어렵습니다. QUIC은 UDP를 사용하지만 대부분의 전송 제어 정보와 애플리케이션 데이터가 암호화되므로, 패킷 캡처만으로 요청 메서드나 0-RTT 승인 여부를 직접 확인하기 어렵기 때문입니다. 엣지 서버의 TLS 상태와 HTTP 로그, 애플리케이션의 업무 처리 결과를 함께 봐야 합니다.

이 글에서는 QUIC Handshake와 TLS 1.3 Session Ticket의 관계, 0-RTT가 실제로 지연을 줄이는 과정, Replay Attack이 발생하는 구조, 운영 환경에서 확인할 지표와 안전한 복구·방어 방법을 순서대로 살펴봅니다.

장애 증상과 초기 판단

운영 환경에서 가장 흔하게 볼 수 있는 증상은 HTTP/3 적용 후에도 초기 응답 시간이 기대만큼 줄지 않는 상황입니다. HTTP/3 요청 비율은 증가했지만 0-RTT 승인 비율이 낮다면 대부분의 연결이 일반적인 1-RTT Handshake를 거치므로 체감 성능은 크게 달라지지 않을 수 있습니다.

예를 들어 해외 사용자의 네트워크 RTT가 120ms인 환경을 가정해 보겠습니다. 재방문 요청에서 0-RTT가 정상적으로 사용되면 서버 응답을 기다리는 한 번의 왕복 시간을 줄일 가능성이 있지만, Session Ticket이 만료됐거나 서버 구성이 변경됐다면 0-RTT는 거부되고 정상 Handshake 이후 요청이 다시 전송됩니다. 이때 평균 응답 시간보다 신규 연결과 재개 연결의 p95·p99 지연을 분리해서 봐야 차이를 발견할 수 있습니다.

보안 설계가 부족한 경우에는 더 위험한 증상이 나타납니다. 동일한 주문 번호로 레코드가 두 개 생성되거나 쿠폰이 중복 사용되고, 인증 코드 발송 API가 반복 실행되는 사례가 대표적입니다. UDP 패킷 손실이나 클라이언트 재시도 문제로 보일 수 있지만, 0-RTT 요청이 공격자 또는 중간 경로에 의해 재전송됐을 가능성도 함께 검토해야 합니다.

반대로 425 Too Early 응답이 증가했다고 해서 곧바로 장애로 판단해서는 안 됩니다. 425는 서버가 재전송 가능성이 있는 요청을 안전하게 처리할 수 없다고 판단해 Handshake 완료 후 다시 요청하도록 유도하는 보호 장치입니다. 보호 정책을 배포한 직후 425가 늘었다면 차단 대상 API와 클라이언트 재시도 동작을 먼저 확인해야 합니다.

다음은 구조를 설명하기 위해 단순화한 로그 예시입니다. 실제 필드명은 CDN, 프록시, QUIC 구현체마다 다릅니다.

protocol=h3 tls_resumed=true early_data=accepted method=GET path=/catalog status=200
protocol=h3 tls_resumed=true early_data=accepted method=POST path=/orders status=425
protocol=h3 tls_resumed=true early_data=rejected method=GET path=/catalog status=200

두 번째 로그는 TLS 계층에서 Early Data를 수신했지만 애플리케이션이 주문 생성 요청의 재전송 위험을 허용하지 않았음을 의미할 수 있습니다. 세 번째 로그의 200 응답은 0-RTT가 거부된 뒤 Handshake를 완료하고 요청을 정상적으로 다시 보낸 결과일 수 있으므로, 최종 HTTP 상태 코드만으로 0-RTT 성공 여부를 판단하면 안 됩니다.

내부 동작 원리와 장애 발생 구조

QUIC은 UDP 위에서 신뢰성 있는 전송, 혼잡 제어, 스트림 다중화와 TLS 1.3 보안을 통합합니다. IETF RFC 9000은 0-RTT를 클라이언트가 서버 응답을 받기 전에 애플리케이션 데이터를 보낼 수 있는 기능으로 정의하면서, Replay Attack에 대한 보호는 제공하지 않는다고 명시합니다.

최초 연결에서는 클라이언트와 서버가 QUIC 및 TLS Handshake를 수행합니다. 서버가 이후의 연결 재개와 0-RTT를 허용하면 클라이언트에 TLS Session Ticket을 발급합니다. 클라이언트는 이 티켓과 이전 연결에서 협상한 QUIC Transport Parameter, 애플리케이션 프로토콜 정보를 보관합니다.

재접속할 때 클라이언트는 Session Ticket을 ClientHello에 포함하고, 이전 연결에서 파생된 정보를 바탕으로 0-RTT 키를 생성합니다. 이어서 서버의 Handshake 응답을 기다리지 않고 0-RTT 패킷에 HTTP/3 요청을 담아 전송합니다. 서버가 티켓과 이전 설정의 호환성을 확인하고 Early Data를 승인하면 요청을 즉시 처리할 수 있습니다.

QUIC용 TLS 규격인 RFC 9001은 서버가 0-RTT를 거부했을 때 해당 패킷을 처리해서는 안 된다고 규정합니다. 이 경우 클라이언트는 이전에 가정했던 스트림과 애플리케이션 상태를 초기화하고, Handshake 완료 후 요청을 전송해야 합니다. 따라서 0-RTT 거부 자체는 정상적인 프로토콜 동작이며 서비스 실패와 동일하지 않습니다.

문제는 Session Ticket과 함께 전송된 Early Data를 공격자가 복제해 서버에 다시 보낼 수 있다는 점입니다. QUIC 프레임 처리는 중복이나 순서 변경이 연결 상태를 손상시키지 않도록 설계돼 있지만, 프레임 안에 실린 HTTP 요청의 업무 효과까지 자동으로 멱등성이 보장되는 것은 아닙니다.

가령 POST /orders가 호출될 때마다 새로운 주문을 생성한다면 동일한 0-RTT 데이터가 두 번 처리될 경우 주문도 두 번 만들어질 수 있습니다. 특히 여러 엣지 노드가 암호화된 Stateless Session Ticket을 공유하면서 Anti-Replay 상태는 공유하지 않는 구조라면, 동일한 티켓이 서로 다른 노드에서 각각 정상 티켓으로 받아들여질 가능성을 고려해야 합니다.

HTTP 메서드 이름만 보고 안전성을 확정하는 것도 위험합니다. RFC 9110의 HTTP Semantics에 따르면 GET, HEAD, OPTIONS, TRACE는 안전한 메서드이며 PUT, DELETE와 안전한 메서드는 멱등성을 가집니다. 하지만 GET /coupon/use처럼 GET 요청에 상태 변경 기능을 잘못 구현했다면 표준상 안전한 메서드라는 이유만으로 0-RTT를 허용해서는 안 됩니다.

확인 지표와 진단 명령어

먼저 클라이언트 측 테스트 도구가 HTTP/3를 지원하는지 확인합니다. curl은 빌드 방식에 따라 HTTP/3 지원 여부가 다르므로 명령어가 존재한다고 가정해서는 안 됩니다.

curl -V
Protocols: file ftp ftps http https
Features: alt-svc AsynchDNS HSTS HTTP2 HTTP3 HTTPS-proxy IPv6 SSL threadsafe

위 결과는 설명을 위한 예시입니다. FeaturesHTTP3가 표시돼야 해당 curl 빌드에서 HTTP/3 테스트가 가능합니다. 표시되지 않는 환경에서는 --http3-only 옵션을 사용해도 정상 테스트할 수 없습니다.

다음 명령으로 실제 요청이 HTTP/3로 처리되는지와 연결 구간별 시간을 확인할 수 있습니다.

curl --http3-only -sS -o /dev/null \
  -w 'http_version=%{http_version} connect=%{time_connect} appconnect=%{time_appconnect} starttransfer=%{time_starttransfer} total=%{time_total}\n' \
  https://www.example.com/health
http_version=3 connect=0.018432 appconnect=0.041817 starttransfer=0.066205 total=0.068114

http_version=3은 요청이 HTTP/3로 처리됐다는 의미입니다. connect는 연결 단계, appconnect는 보안 연결이 준비된 시점, starttransfer는 첫 응답 바이트를 받은 시점을 나타냅니다. 다만 이 결과만으로 요청이 0-RTT에 실렸다고 확정할 수는 없습니다. 일반적인 단일 curl 요청은 Session Ticket 보관과 Early Data 사용 조건을 충족하지 않을 수 있으므로 서버 측 0-RTT 로그와 함께 확인해야 합니다.

QUIC은 암호화 범위가 넓기 때문에 패킷 캡처는 UDP 443 통신 존재와 손실·경로 문제를 확인하는 보조 자료로 사용합니다.

sudo tcpdump -ni any 'udp port 443'
IP 192.0.2.25.53142 > 198.51.100.10.443: UDP, length 1200
IP 198.51.100.10.443 > 192.0.2.25.53142: UDP, length 1187
IP 192.0.2.25.53142 > 198.51.100.10.443: UDP, length 86

이 결과에서는 QUIC으로 추정되는 UDP 통신을 확인할 수 있지만, HTTP 메서드나 Early Data 승인 여부는 알 수 없습니다. 패킷 수가 증가했다는 이유만으로 Replay Attack이라고 판단해서도 안 됩니다. QUIC의 정상적인 손실 복구와 경로 변경에서도 재전송이 발생할 수 있기 때문입니다.

서버 또는 게이트웨이가 JSON 로그에 protocol, tls_resumed, early_data, method, status와 같은 정보를 남긴다고 가정하면 다음과 같이 분포를 집계할 수 있습니다. 로그 경로와 필드명은 실제 제품 설정에 맞게 변경해야 합니다.

jq -r '
  select(.protocol == "h3") |
  [.tls_resumed, .early_data, .method, .status] |
  @tsv
' /var/log/gateway/access.json | sort | uniq -c
840 true  accepted GET  200
 52 true  rejected GET  200
 13 true  accepted POST 425

첫 번째 값은 재개 연결에서 GET 요청이 0-RTT로 승인된 건수입니다. 두 번째 값은 0-RTT가 거부됐지만 Handshake 이후 정상 처리된 건수일 수 있습니다. 세 번째 값은 Early Data로 전달된 POST 요청을 애플리케이션이 425로 거절한 경우입니다.

누적 건수보다 5분 또는 10분 단위 증가량과 비율을 확인하는 것이 중요합니다. 특히 다음 지표를 교차 비교해야 합니다.

  • HTTP/3 요청 비율과 HTTP/2 폴백 비율
  • TLS 세션 재개 성공률
  • 0-RTT 시도·승인·거부 비율
  • 425 Too Early 응답률
  • 신규 연결과 재개 연결의 p50·p95·p99 TTFB
  • Session Ticket 발급량과 사용 시점의 티켓 나이
  • 동일 업무 키, 주문 번호 또는 요청 식별자의 중복 발생량
  • 엣지 노드별 0-RTT 승인 편차

개인적으로 이런 유형을 분석할 때는 먼저 0-RTT 승인률과 업무 중복 방지 지표를 함께 확인하는 편입니다. 응답 시간이 줄었다는 지표만 보고 성공으로 판단하면 보안 위험을 놓칠 수 있고, 425 증가만 보고 기능을 제거하면 정상적인 보호 동작을 장애로 오인할 수 있습니다.

원인별 복구 절차와 해결 방법

중복 주문이나 결제처럼 실제 업무 영향이 발생했다면 가장 먼저 상태 변경 API의 0-RTT 처리를 중단해야 합니다. 제품별 설정을 확인해 위험한 경로를 Handshake 완료 후 처리하도록 제한하고, 경로별 제어가 불가능하다면 영향 범위가 확인될 때까지 전체 0-RTT를 비활성화하는 것이 안전합니다.

0-RTT를 끄는 것은 긴급 조치이지 항상 근본 해결책은 아닙니다. 이후 게이트웨이, 애플리케이션과 데이터베이스에서 동일 요청이 반복돼도 결과가 한 번만 반영되도록 설계해야 합니다.

주문 생성과 같은 POST 요청에는 클라이언트가 고유한 Idempotency Key를 보내도록 하고, 서버는 해당 키와 처리 결과를 원자적으로 저장해야 합니다. 데이터베이스에는 업무 범위에 맞는 유일성 제약조건을 두어 동시 요청이 서로 다른 애플리케이션 인스턴스에서 처리되더라도 중복 레코드가 생성되지 않게 해야 합니다. 단순히 애플리케이션 메모리에 처리된 키를 보관하면 재시작이나 다중 인스턴스 환경에서 보호가 깨질 수 있습니다.

프록시나 CDN을 사용하는 환경에서는 Early Data가 전달됐다는 사실이 원본 서버까지 보존돼야 합니다. RFC 8470은 중간 장비가 Early Data로 받은 요청을 전달할 때 Early-Data: 1 헤더를 사용하고, 안전하게 처리할 수 없는 서버는 425 Too Early를 반환하도록 정의합니다. 425를 받은 클라이언트의 재시도는 Early Data가 아닌 Handshake 완료 연결에서 수행돼야 합니다.

원본 서버가 Early-Data 헤더와 425 동작을 이해하지 못한다면 게이트웨이는 요청을 즉시 전달하지 말고 Handshake 완료까지 보류하거나 425로 거부하는 편이 안전합니다. 중간 장비가 헤더를 제거하거나 정상 요청으로 변환하면 애플리케이션은 재전송 위험을 판단할 근거를 잃게 됩니다.

TLS 계층에서도 Session Ticket 수명 제한, 키 순환, 티켓 재사용 감지와 Anti-Replay 저장소를 검토해야 합니다. 여러 리전이나 엣지 노드에서 티켓 키를 공유한다면 Replay 상태도 필요한 범위에서 공유할 수 있는지 확인해야 합니다. 모든 티켓 사용 이력을 전역 동기화하면 지연과 가용성 비용이 커질 수 있으므로, 위험도가 높은 요청을 0-RTT 대상에서 제외하는 애플리케이션 정책과 병행하는 것이 현실적입니다.

클라이언트 IP나 네트워크 위치를 티켓에 강하게 결합하는 방식은 이동통신, NAT, 로밍 환경에서 정상 요청까지 거부할 수 있습니다. 따라서 주소 검증은 하나의 보조 신호로 사용하고, 업무 멱등성과 요청별 허용 정책을 핵심 방어선으로 삼아야 합니다.

지연시간만 보고 서버 사양이나 네트워크 대역폭을 증설하면 원인을 놓칠 수 있습니다. 0-RTT 승인률이 낮은 원인이 티켓 키 불일치, 배포 후 설정 호환성 변화, 리전 간 상태 불일치라면 자원을 늘려도 세션 재개 성능은 개선되지 않습니다.

설정 변경 후에는 최소한 0-RTT 승인률과 425 비율, 재개 연결 TTFB, 업무 중복 건수를 함께 비교해야 합니다. 변경 전 설정을 백업하고 테스트 환경에서 재방문 연결, 티켓 만료, 엣지 노드 변경, 동일 요청 재전송 시나리오를 검증한 뒤 변경 관리 절차에 따라 운영에 반영하는 것이 좋습니다.

재발 방지와 마치며

0-RTT 모니터링은 단순 활성화 여부보다 승인 결과와 업무 안전성을 중심으로 구성해야 합니다. 정상 트래픽에서 기준선을 먼저 수집한 다음 0-RTT 거부율, 425 비율, 세션 재개 실패율이 평소 범위를 벗어날 때 경보가 발생하도록 설정합니다. 고정 임계치만 사용하기보다 배포 전후와 리전·엣지 노드별 편차를 비교하면 구성 불일치를 빠르게 찾을 수 있습니다.

부하 테스트에서는 최초 접속만 반복해서는 안 됩니다. Session Ticket을 받은 동일 클라이언트의 재접속, 티켓 만료 후 접속, 키 순환 직후 접속, 0-RTT 거부 후 정상 재전송까지 포함해야 합니다. 네트워크 RTT를 단계적으로 높여 실제로 절약되는 시간이 어느 구간에서 의미가 있는지도 확인해야 합니다.

운영 전에는 다음 항목을 점검하는 것이 좋습니다.

  • 0-RTT를 허용할 메서드와 API 경로가 명시돼 있는가
  • GET 요청에 상태 변경 기능이 숨어 있지 않은가
  • POST 요청에 업무 단위 Idempotency Key가 적용돼 있는가
  • 데이터베이스가 중복 처리를 최종적으로 차단하는가
  • 게이트웨이가 Early-Data 정보를 원본 서버까지 전달하는가
  • 애플리케이션과 클라이언트가 425 응답을 올바르게 처리하는가
  • 다중 엣지 환경의 Session Ticket 키와 Anti-Replay 범위가 정의돼 있는가
  • 신규 연결과 재개 연결의 성능 지표가 분리돼 있는가

HTTP/3 QUIC 0-RTT의 목적은 모든 요청을 무조건 빠르게 보내는 것이 아닙니다. 재전송돼도 안전한 요청만 Handshake 전에 처리해 지연을 줄이고, 상태를 변경하는 요청은 멱등성과 중복 방지 장치를 갖춘 뒤 제한적으로 허용해야 합니다. 운영 판단 기준은 단순한 TTFB 감소가 아니라 지연시간 개선, 0-RTT 승인 안정성, 업무 데이터의 무결성이 동시에 유지되는지 여부입니다.

PRODB

댓글 0

첫 댓글을 남겨보세요.

error: Content is protected !!

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.