24. 인증 전 연결의 핸드셰이크 타임아웃·자원 상한 부재 (slowloris 류 고갈)
난이도 중내 리뷰 · C#
내 리뷰 · C++
해설 · C#
해설 — 인증 전 연결의 핸드셰이크 타임아웃·자원 상한 부재 (slowloris 류 고갈)
난이도: 중상
요약
OnAccept 는 연결마다 세션을 만들고(A) 활성 목록에 넣은 뒤(B) 64KB 수신 버퍼를 즉시 잡는다(C). 그러나 인증 단계에 마감 시한이 없다: 인증을 끝내지 않은 연결은 ReceiveLoopAsync 가 ReceiveAsync 에서 무한정 대기하며 세션 슬롯·버퍼·소켓을 영구 점유한다. 게다가 미인증/대기 연결 수에 상한이 없어, 공격자가 "접속만 하고 인증을 안 보내는" 또는 "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/연결 레이트, 미완료 핸드셰이크 비율을 모니터링·차단.
면접 포인트
- 이 결함은 accept 폭주(연결 속도) 나 인증 후 유휴 타임아웃 과 다르다 — 핵심은 "TCP 는 맺었지만 앱 인증을 끝내지 않는 연결"이 자원을 무기한 점유하는 것이며, 인증 단계 전용 데드라인이 해법이다.
- 신뢰 불가 단계(인증 전)에는 자원 쿼터(연결 수·버퍼 크기)를 작게 잡고, 승격된 뒤에 자원을 키운다 — 공격 비용을 방어 비용보다 비싸게.
- pending 카운터는 성공·실패·예외 모든 경로에서 정확히 정리해야 한다(누수 시 그 자체가 또 다른 고갈원).
해설 · C++
해설 — 인증 전 연결의 핸드셰이크 타임아웃·자원 상한 부재 (slowloris 류 고갈)
난이도: 중상
요약
onAccept 는 연결마다 세션을 만들고(A) 활성 목록에 넣은 뒤(B) 64KB 수신 버퍼를 즉시 잡는다(C). 그러나 인증 단계에 마감 시한이 없다: 인증을 끝내지 않은 연결의 receiveLoop 는 recvSome 에서 무한정 블록(또는 펜딩)하며 세션·버퍼·fd 를 영구 점유한다. 또 미인증 연결 수에 상한이 없어, 접속만 하고 인증을 안 보내거나 1바이트씩 흘리는 연결 수천 개로 메모리(세션×64KB)·소켓 fd·세션 벡터가 고갈되어 정상 유저 접속이 막힌다(저대역 DoS = slowloris).
문제점
1. (자원 누수 / 무기한 점유) 인증 단계 타임아웃 부재 — (A)
- 증상: 인증 패킷을 안 보내는 연결은
recvSome에서 영영 대기. 세션이 죽지 않는다. - 재현조건: connect 후 미전송, 또는
tryAuthenticate가 계속 false 가 되도록 느리게 흘림. - 근본원인: "수립 ~ 인증 완료" 구간에 데드라인 타이머가 없다. 정상 세션의 heartbeat 와 별개로, 인증 전 단계 전용 시한이 필요하다.
2. (자원 고갈) 미인증 연결 수 무제한 — (B)
- 증상:
push_back이 무조건 성공. 미인증 연결이 몇 개든 받아 슬롯·메모리가 선형 증가. - 근본원인: pending 연결에 동시 상한(IP당/전체 쿼터) 이 없다.
3. (메모리 증폭) 즉시 대형 버퍼 선할당 — (C)
- 인증도 안 된 연결에 64KB 를 곧장 확보. N개면 N×64KB. 인증 전엔 작은 버퍼로 시작하거나 풀 대여 후 승격 시 확장하는 편이 공격 비용을 키운다.
수정안
인증 전용 데드라인 + 미인증 쿼터 + 작은 초기 버퍼.
#include <atomic>
#include <chrono>
std::atomic<int> pending_{0};
static constexpr int kMaxPending = 1000;
void SessionManager::onAccept(int fd) {
if (pending_.fetch_add(1) >= kMaxPending) { // 미인증 연결 상한
pending_.fetch_sub(1);
closeFd(fd); // 즉시 거절
return;
}
auto s = std::make_shared<Session>(fd);
s->deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); // 인증 마감
{
std::lock_guard<std::mutex> lk(mtx_);
sessions_.push_back(s);
}
// 타이머 휠/스케줄러가 deadline 경과 + !authenticated 인 세션을 drop().
}
void SessionManager::onAuthenticated(const std::shared_ptr<Session>& s) {
s->authenticated = true;
pending_.fetch_sub(1); // 승격 시 pending 해제
}
- 타이머 휠(또는 주기 sweep)로
deadline지난 미인증 세션을 강제 종료. pending_은 인증 성공·연결 종료·예외 모든 경로에서 정확히 감소(누수 금지).- 초기 버퍼는 작게 시작(예: 512B), 인증 후 확장하거나 풀에서 대여. IP당 쿼터 추가.
더 나은 설계
- 연결 수명 상태기계(
Accepted → Authenticating(데드라인) → Authenticated)를 명시하고 각 전이에 타임아웃·쿼터를 건다. 인증 전/후 자원 정책을 분리. - 미인증 연결은 경량 풀로 격리해 정상 세션 풀과 자원 격벽.
- 게이트웨이/LB 에서 미완료 핸드셰이크 비율·연결 레이트 모니터링·차단.
면접 포인트
- 이 결함은 accept 폭주(문제7)나 인증 후 유휴 타임아웃(문제1)과 다르다 — 핵심은 "TCP 는 맺었으나 앱 인증을 끝내지 않는 연결"의 무기한 자원 점유이며, 인증 단계 전용 데드라인이 정답이다.
- 신뢰 불가 단계엔 자원 쿼터를 작게(연결 수·버퍼), 승격 후 키운다 — 공격 비용 > 방어 비용.
- pending 카운터는 모든 종료 경로에서 정확히 정리. 누수 시 그 자체가 또 다른 고갈원이 된다.