← 문제로

24. 인증 전 연결의 핸드셰이크 타임아웃·자원 상한 부재 (slowloris 류 고갈)

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

해설 — 인증 전 연결의 핸드셰이크 타임아웃·자원 상한 부재 (slowloris 류 고갈)

난이도: 중상

요약

OnAccept 는 연결마다 세션을 만들고(A) 활성 목록에 넣은 뒤(B) 64KB 수신 버퍼를 즉시 잡는다(C). 그러나 인증 단계에 마감 시한이 없다: 인증을 끝내지 않은 연결은 ReceiveLoopAsyncReceiveAsync 에서 무한정 대기하며 세션 슬롯·버퍼·소켓을 영구 점유한다. 게다가 미인증/대기 연결 수에 상한이 없어, 공격자가 "접속만 하고 인증을 안 보내는" 또는 "1바이트씩 천천히 보내는" 연결 수천 개를 만들면 메모리(세션×64KB)·소켓 핸들·세션 목록이 고갈되어 정상 유저가 접속하지 못한다(저대역 DoS = slowloris).

문제점

1. (자원 누수 / 무기한 점유) 인증 단계 타임아웃 부재 — (A)

  • 증상: 인증 패킷을 안 보내는 연결은 ReceiveAsync 에서 영원히 펜딩. 세션이 죽지 않고 남는다.
  • 재현조건: connect 후 아무것도 보내지 않거나, 16바이트 미만을 아주 느리게 흘림(TryAuthenticate 가 계속 false).
  • 근본원인: "연결 수립 ~ 인증 완료"에 데드라인 타이머가 없다. 정상 세션의 heartbeat/유휴 타임아웃과 별개로, 인증 전 단계 전용 시한이 필요하다.

2. (자원 고갈) 미인증 연결 수 무제한 — (B)

  • 증상: _sessions.Add 가 무조건 성공. 인증 안 된 연결이 몇 개든 받아들여 슬롯·메모리가 선형 증가.
  • 근본원인: 미인증(pending) 연결에 대한 동시 상한(예: IP당/전체) 이 없다. accept 자체를 막지 않더라도, 인증 전 자원 점유에 쿼터가 필요.

3. (메모리 증폭) 연결 즉시 대형 버퍼 선할당 — (C)

  • 인증도 안 된 연결에 64KB 를 곧장 잡는다. N개면 N×64KB. 인증 전에는 작은 버퍼로 시작하거나 풀에서 빌려 인증 성공 후 확장하는 편이 공격 비용을 키운다.

수정안

인증 전용 데드라인 + 미인증 연결 쿼터 + 작은 초기 버퍼를 도입한다.

public void OnAccept(Socket socket)
{
    // 미인증 연결 수 상한
    if (Interlocked.Increment(ref _pending) > MaxPendingAuth)
    {
        Interlocked.Decrement(ref _pending);
        try { socket.Close(); } catch { }   // 즉시 거절
        return;
    }
    var session = new Session(socket);
    lock (_lock) { _sessions.Add(session); }
    _ = ReceiveLoopAsync(session);
    _ = AuthDeadlineAsync(session);          // 인증 마감 타이머
}

private async Task AuthDeadlineAsync(Session s)
{
    await Task.Delay(TimeSpan.FromSeconds(5));
    if (!Volatile.Read(ref s.Authenticated)) Drop(s);   // 시한 내 미인증 → 강제 종료
}

private void OnAuthenticated(Session s)
{
    s.Authenticated = true;
    Interlocked.Decrement(ref _pending);     // 승격되면 pending 에서 해제
}
  • 인증 성공/연결 종료 양 경로에서 _pending 을 정확히 감소(누수 방지).
  • 초기 수신 버퍼는 작게(예: 512B) 시작, 인증 성공 후 64KB 로 확장하거나 버퍼 풀에서 대여.
  • IP/서브넷당 동시 미인증 연결 상한을 추가하면 단일 출처 공격을 더 좁힐 수 있다.

더 나은 설계

  • 연결 수명 상태기계를 명시: Accepted → Authenticating(데드라인 t초) → Authenticated. 각 전이에 타임아웃·쿼터를 건다. 인증 전/후의 자원 정책을 분리한다.
  • 인증 전 연결은 별도 경량 풀에서 관리해 정상 세션 풀과 자원을 격리(공격 격벽).
  • 게이트웨이/LB 단에서 SYN/연결 레이트, 미완료 핸드셰이크 비율을 모니터링·차단.

면접 포인트

  1. 이 결함은 accept 폭주(연결 속도)인증 후 유휴 타임아웃 과 다르다 — 핵심은 "TCP 는 맺었지만 앱 인증을 끝내지 않는 연결"이 자원을 무기한 점유하는 것이며, 인증 단계 전용 데드라인이 해법이다.
  2. 신뢰 불가 단계(인증 전)에는 자원 쿼터(연결 수·버퍼 크기)를 작게 잡고, 승격된 뒤에 자원을 키운다 — 공격 비용을 방어 비용보다 비싸게.
  3. pending 카운터는 성공·실패·예외 모든 경로에서 정확히 정리해야 한다(누수 시 그 자체가 또 다른 고갈원).