← 문제로

44. UDP 연결 마이그레이션의 경로 검증 부재 (세션 탈취·오배달, C#)

난이도 중
내 리뷰 · C#
해설 · C#

해설 — UDP 연결 마이그레이션의 경로 검증 부재 (세션 탈취·오배달, C#)

난이도: 중상

요약

OnPacket 은 connectionId 로 세션을 찾은 뒤, 패킷의 소스 주소가 세션의 현재 remote 와 다르면 즉시 세션의 원격 주소를 그 소스로 갱신한다. 새 경로가 정당한지 검증(챌린지-리스폰스)하지 않으므로, 공격자가 세션의 트래픽을 자신에게 끌어오거나(탈취) 임의의 희생자 주소로 향하게(오배달/증폭) 만들 수 있다.

문제점

분류 태그: 경로 검증 부재 / 인증 이전 상태 변경 (연결 마이그레이션 취약점)

결함 (A) — connectionId 만으로 remote 즉시 갱신

UDP 소스 주소는 위조 가능하고, connectionId 는 "이 세션을 지목한다"는 라우팅 힌트일 뿐 송신자가 그 경로의 정당한 주인임을 증명하지 못한다.

  • 세션 탈취: 공격자가 어떤 경로로 유효 connectionId 가 담긴 패킷을 보내면(예: 재전송/추측/사이드채널) 서버가 remote 를 공격자 주소로 바꿔, 이후 게임 상태·채팅·민감 응답이 공격자에게 배달된다.
  • 오배달/증폭 DoS: 공격자가 소스 주소를 희생자 IP로 위조해 보내면, 서버가 세션 트래픽을 희생자에게 쏟아붓는다(반사 증폭).

결함 (B) — 갱신된 주소를 신뢰해 처리·송신

AEAD 검증(AeadOpen)은 페이로드의 무결성·기밀성은 지키지만 소스 주소의 정당성과는 무관하다. 인증이 통과해도 그것은 "이 connectionId 의 키로 만든 정상 패킷"임을 말할 뿐, "이 새 주소로 보내도 안전"함을 뜻하지 않는다(정상 패킷을 캡처해 다른 주소에서 재주입 가능). 그런데 (A)에서 이미 remote 를 바꿨으므로, 이후 Dispatch 로 생성되는 응답이 검증 안 된 경로로 나간다.

세션이 정당하게 이동(모바일 전환)하는 경우와 공격이 코드상 구분되지 않는 것이 핵심 결함이다.

수정안

QUIC 식 경로 검증(path validation): 새 주소가 관측되면 즉시 마이그레이션하지 말고, 그 주소로 예측 불가한 챌린지 토큰을 보내고 동일 토큰이 그 주소에서 되돌아올 때만 마이그레이션한다. 검증 전에는 기존 경로로 계속 송신한다.

public void OnPacket(IPEndPoint source, long connId, byte[] frame)
{
    if (!_byConn.TryGetValue(connId, out var s)) return;

    byte[] plaintext = AeadOpen(s.Key, frame);   // 먼저 인증
    if (plaintext == null) return;               // 위조/변조 패킷은 여기서 탈락

    // 경로 검증 응답이면 처리하고, 성공 시에만 remote 교체
    if (IsPathChallengeResponse(plaintext, out var token))
    {
        if (s.PendingMigration != null &&
            s.PendingMigration.Address.Equals(source) &&
            ConstantTimeEquals(token, s.PendingMigration.Token))
        {
            s.Remote = source;                   // 검증된 경로로만 마이그레이션
            s.PendingMigration = null;
        }
        return;
    }

    if (!s.Remote.Equals(source))
    {
        // 새 주소 관측: 즉시 바꾸지 말고 챌린지 시작(레이트 리밋 필요)
        if (s.PendingMigration == null || !s.PendingMigration.Address.Equals(source))
        {
            var tok = RandomToken();
            s.PendingMigration = new Migration(source, tok);
            s.SendTo(source, MakePathChallenge(s.Key, tok)); // 새 경로로 챌린지
        }
        // 검증 전까지 이 패킷의 로직은 처리하되, 응답은 기존 Remote 로만
    }

    s.Dispatch(plaintext);   // 응답 송신은 항상 검증된 s.Remote 로
}

원칙: (1) 인증을 먼저, (2) 주소 변경은 챌린지-리스폰스로만, (3) 검증 전 송신은 기존 경로로, (4) 챌린지에 레이트 리밋(증폭 방지).

더 나은 설계

  • anti-amplification 한도: 미검증 경로로는 받은 바이트의 N배 이하만 보내도록 제한(QUIC 3배 규칙).
  • 동시 마이그레이션 억제: 여러 새 주소가 번갈아 오면 각기 챌린지하되, 검증 성공 1건만 채택.
  • connectionId 회전: 마이그레이션 시 새 connectionId 를 발급해 경로 상관(linkability)·추적을 줄인다.
  • 트레이드오프: 경로 검증은 1 RTT 지연을 추가하지만 모바일 전환 빈도가 낮아 체감 영향은 작다. 반면 검증 생략의 대가는 세션 탈취/증폭이라 타협 불가.

면접 포인트

  • AEAD 인증 통과와 "이 주소로 보내도 안전"이 왜 별개인가(캡처-재주입).
  • QUIC 경로 검증(챌린지-리스폰스)과 anti-amplification 3배 규칙의 목적.
  • 정당한 NAT 리바인딩과 스푸핑 공격을 코드가 어떻게 구분해야 하는가.