블로그로 돌아가기

게임 서버 DDoS 보호: 10분 공격 시대의 런북

게시일 2026년 10월 30일
게임 서버 DDoS 보호: 10분 공격 시대의 런북 AI의 도움으로 생성됨

핵심 요약

게임 서버 DDoS 보호 런북을 확인하세요: 10분 이내에 끝나는 저대역폭 공격의 탐지, 대응, 예방 전략을 커널 카운터와 nftables로 구현하는 실전 가이드로, 96%의 공격이 10분 안에 끝나는 시대에 소규모 백엔드가 반드시 대비해야 할 모든 것을 담았다.

매치메이킹 대기열이 21:14에 멈춘다. 21:19에는 다시 돌아온다. 그래프를 확인해 보면 620Mbps 스파이크, 6분 폭의 트래픽이 이미 사라져 있고, 알림이 휴대폰에 도달하기 전에 끝나 있다. 두 명뿐인 팀 중 아무도 실시간으로 보지 못했고, 플레이어들은 그냥 "렉"으로 분류했다.

그런 형태의 공격이 이제 기본값이다. Cloudflare의 2026년 Q3 DDoS 위협 보고서는 해당 분기에 완화된 네트워크 계층 공격을 1,820만 건으로 집계했다. 시간당 약 8,226건, 즉 0.44초마다 한 건꼴이다. 그중 96%는 10분 이내에 종료됐고 98%는 1Gbps 미만이었다. 1Tbps를 초과하는 초대용량(hyper-volumetric) 공격은 Q2의 805건에서 Q3의 251건으로 약 69% 감소했는데, 보고서는 이를 Aisuru, KimWolf, JackSkid, Mossad IoT 봇넷에 대한 법 집행 기관의 단속 덕분으로 돌린다.

거인은 줄고, 떼는 늘었다. 그리고 이 '떼'는 소규모 게임 백엔드가 가장 대비하지 못한 위협 모델이다. 공급업체의 완화(mitigation) 등급을 발동시키기에는 너무 작고, 단일 게임 인스턴스를 포화시키기에는 충분히 크기 때문이다.

이 글은 바로 그 대역폭 구간을 위한 런북이다.

2026년 Q3가 실제로 바꾼 것

게임 서버를 운영한다면 세 가지 변화가 중요하다. 첫째는 지속 시간이다. 짧은 버스트의 저대역폭 공격이 전년 대비 208% 급증한 것은 DDoS 대여 "부터(booter)" 서비스의 전형적인 운영 패턴이다. 값싸고, 반복 가능하며, 일제 사격 형태로 발사된다. 하룻밤에 한 건이 아니라 40건을 예상해야 한다.

둘째는 공격 벡터의 변동이다. CLDAP 반사 공격 — UDP 389 포트의 공용 도메인 컨트롤러를 악용 — 은 상반기 내내 활발했지만, 분기 대비 99.5% 급감해 공격의 0.05%에 불과해졌다. 공격자들은 멈추지 않았다. 벡터를 돌린 것이다. DNS 증폭 공격은 215% 급증해 네트워크 계층 공격의 3.43%를 차지했고, 일반 UDP 플러드는 14.06%에서 31.31%로 올라갔다.

셋째는 표적이 넓어졌다는 점이다. 미디어는 가장 많이 공격받는 업종을 유지했지만 Q2 점유율의 약 4분의 3을 잃었다. 인터넷 인프라는 10위권 밖에서 6위로 올라섰고, 소비자 서비스와 성인 엔터테인먼트가 8위와 9위로 진입했다. 게임은 소비자 대상이면서 지연 시간에 민감한 이 표적군의 한가운데 있다.

방어자를 위한 교훈: 지난 분기의 벡터에 맞춰 플레이북을 만들지 말라. UDP 플러드, DNS 증폭, 반사 공격 악용, SYN 고갈의 네 가지 범주를 기준으로 만들고, 비중은 계속 움직인다고 가정하라.

게임 백엔드에서 가장 먼저 무너지는 것

모든 것이 한꺼번에 실패하지는 않는다. 순서를 알면 잘못된 불을 끄느라 헤매지 않는다.

  • 게임 포트. 게임 서버 포트에 대한 UDP 플러드는 CPU보다 먼저 NIC 큐를 소진한다. 프로세스가 살아 있으므로 오케스트레이터의 헬스 체크에는 서버가 "정상"으로 보이지만, 커널에서는 패킷이 버려지고 있다.
  • 세션 및 로그인 엔드포인트. HTTP 플러드가 인증을 때린다. 폭주 중간에 연결이 끊긴 플레이어는 다시 들어갈 수 없고, 이미 게임 중이던 플레이어는 재연결 루프가 실패해 이탈한다.
  • 리더보드 읽기. 대부분의 게임 백엔드에서 가장 증폭이 심한 읽기 경로다. 클라이언트 요청 하나가 데이터베이스 범위 스캔으로 확산된다. 초당 5,000건의 요청은 보내기는 쉬워도 처리 비용은 비싸다.
  • 텔레메트리 수집. 대개 인증이 없거나 느슨하게 레이트 리밋이 걸려 있어, 때리기 가장 싼 엔드포인트인 경우가 많다.
  • conntrack 테이블. 이 테이블을 가득 채우면 SSH 접속을 포함해 호스트의 모든 서비스가 다운된다. 4분짜리 공격이 4시간짜리 장애로 변하는 경로다.

반사 공격에 대해 한 가지 짚고 넘어가야 한다. 트래픽이 정당한 DNS 리졸버와 다른 제3자 서버에서 도착하므로 IP 평판 차단은 설계상 실패한다. 당신은 무고한 인프라에 맞고 있는 것이다.

탐지: 그래프보다 먼저 작동하는 신호

대시보드는 느리다. 커널 카운터는 그렇지 않다. 가장 중요한 사전 지표는 초당 패킷 수 대비 초당 비트 수 비율이다. 정상적인 300Mbps 게임 트래픽은 일반적인 페이로드 크기에서 대략 초당 25,000~40,000패킷이다. 64바이트 패킷으로 구성된 300Mbps UDP 플러드는 초당 500,000패킷에 가깝다. 같은 대역폭인데 패킷 수는 15배다. 대역폭 그래프가 평범해 보이는 동안 호스트를 죽인다.

의심되는 박스에서 다음을 실행하라:

# socket and memory pressure summary
ss -s

# per-protocol counters and the ones that predict a lockup
nstat -az | grep -E 'UdpInDatagrams|UdpNoPorts|UdpInErrors|TcpExtListenDrops|TcpExtTCPReqQFullDrop'

# current conntrack table occupancy vs max
conntrack -C && sysctl net.netfilter.nf_conntrack_max

# what is actually arriving on the wire
tcpdump -nn -i eth0 'udp and not port 7777' -c 200

이렇게 해석하라:

  • 게임 루프가 더 적은 플레이어 수를 보고하는 동안 UdpInDatagrams가 오르면 = 플러드.
  • UdpNoPorts가 치솟으면 = 포트 범위를 훑는 스캐너, 흔히 전조 증상이다.
  • TcpExtTCPReqQFullDrop이 0이 아니면 = SYN 큐 오버플로, 이미 정상적인 핸드셰이크를 놓치고 있다는 뜻이다.
  • conntrack -C가 nf_conntrack_max의 70%를 넘으면 = 게임 서버 실패가 아니라 호스트 전체 실패까지 몇 분 남지 않은 것이다.

커널 관점이 아니라 엔드포인트별 관점이 필요하다면, UEFN 서버 크래시 진단 프로토콜에서 네트워크 계층 정체와 엔진 계층 정체를 구분하는 방법을 다룬다. 모든 트라이지의 첫 번째 결정이다.

통과하는 쓰레기에 대한 프로세스 내 방어로는 소켓 계층의 소스별 토큰 버킷이 저렴하고 효과적이다. 다음 C# 예제는 시뮬레이션 틱에 도달하기 전에 인바운드 패킷을 게이트한다:

using System.Diagnostics;
using System.Net;

public sealed class PacketGate
{
    private readonly Dictionary<IPAddress, Bucket> _buckets = new();
    private readonly int _ratePerSecond;
    private readonly int _burst;

    // A legit client at 30 Hz sends ~30 packets/s. 120/s allows
    // headroom for retransmits without letting a flood through.
    public PacketGate(int ratePerSecond = 120, int burst = 240)
    {
        _ratePerSecond = ratePerSecond;
        _burst = burst;
    }

    public bool Allow(IPAddress source)
    {
        long now = Stopwatch.GetTimestamp();

        if (!_buckets.TryGetValue(source, out var bucket))
        {
            bucket = new Bucket { Tokens = _burst, LastRefill = now };
            _buckets[source] = bucket;
        }

        double elapsed = (now - bucket.LastRefill) / (double)Stopwatch.Frequency;
        bucket.Tokens = Math.Min(_burst, bucket.Tokens + elapsed * _ratePerSecond);
        bucket.LastRefill = now;

        if (bucket.Tokens < 1.0) return false;
        bucket.Tokens -= 1.0;
        return true;
    }

    private sealed class Bucket
    {
        public double Tokens;
        public long LastRefill;
    }
}

이 코드가 무엇을 하고 무엇을 하지 못하는지 분명히 하자. 가용 대역폭 안에 들어오는 소스별 쓰레기를 필터링한다. 파이프가 가득 차면 아무것도 하지 못한다. 그 시점에서는 코드가 무엇을 결정하든 패킷은 프로세스 업스트림에서 버려진다.

대응 런북: 첫 10분

공격의 96%는 사람이 대응하기 전에 끝나므로, 런북의 목적은 공격을 고치는 것이 아니다. 공격이 타오르는 동안 증거를 확보하고 피해 범위(blast radius)를 줄이는 것이다.

T+0 ~ T+2분 — 확인하고, 스케일링하지 말라. 인스턴스를 추가하려는 반사적 충동을 참아라. 플러드 속으로 스케일링하면 비용과 공격 표면이 배가된다. 위 카운터로 벡터를 확인하고, pps와 대역폭을 기록하고, conntrack 테이블이 차오르는지 확인하라. 차오르고 있다면 즉시 T+0 긴급 호스트 보호로 전환하라.

T+2 ~ T+5분 — 필수가 아닌 부하를 덜어내라. 게임플레이가 아닌 모든 것을 비활성화하라: 텔레메트리 배칭, 리더보드 갱신, 스토어프론트 폴링, 소셜 피드. 백엔드가 원격 구성을 지원한다면 배포가 아니라 플래그 하나를 뒤집는 것만으로 끝난다.

T+5 ~ T+10분 — 호스트 수준 레이트 리밋을 적용하라. 커널 필터링은 애플리케이션 필터링보다 몇 자릿수 더 저렴하다:

table inet gameguard {
    set udp_flood {
        type ipv4_addr
        flags dynamic, timeout
        timeout 60s
    }

    chain input {
        type filter hook input priority filter; policy drop;

        iif lo accept
        ct state established,related accept
        tcp dport { 22, 443 } accept
        tcp dport 7777-7800 accept
        udp dport 7777-7800 accept

        # drop fragmented UDP - your game protocol should never need it
        ip frag-off & 0x1fff != 0 udp drop

        # per-source UDP cap well above a real client's send rate
        udp dport 7777-7800 update @udp_flood \
            { ip saddr limit rate over 200/second burst 400 packets } drop
    }
}

nft -f gameguard.nft로 로드하고, nft list ruleset과 nft list set inet gameguard udp_flood로 검증하라. 이걸 잘못 적용해서 자신을 잠그면 네트워크 사고가 물리적 접근 사고로 변한다. 먼저 스테이징에서 테스트하고, 규칙은 버전 관리에 보관하라.

T+10분 이후 — 업스트림으로 에스컬레이션하라. 호스트가 아니라 네트워크 엣지에서 스크러빙 또는 블랙홀 기능이 필요하다. 대부분의 관리형 게임 호스팅 제공업체가 이 기능을 제공한다. 제공하지 않는다면 다음 공격 전에 해결해야 할 조달 문제다. 특히 1Gbps 미만 UDP 완화 임계값을 물어보라. 대부분의 공개 SLA는 당신이 결코 보지 못할 테라비트급 공격을 기준으로 작성되어 있기 때문이다.

내내 타임스탬프가 찍힌 로그를 유지하라. 공격 시작, 벡터, 최대 pps, 최대 Gbps, 무엇을 언제 변경했는지. 포스트모템은 완화 조치보다 가치가 있다.

벡터 플레이북: 공격 형태에 맞춰 대응하기

UDP 플러드(공격의 31.31%). 무작위 또는 특정 포트를 공격해 호스트가 리스너를 확인하고 ICMP unreachable을 생성하도록 강제한다. 네트워크 계층 폴백 아키텍처 문서는 기본 전송 경로가 공격받을 때 성능은 떨어지지만 플레이 가능한 모드를 유지하는 방법을 다룬다. 대응: nftables 소스별 레이트 리밋, 게임 포트가 아닌 모든 UDP 포트 차단, 호스트에서 ICMP unreachable 생성 비활성화.

DNS 증폭(3.43%, 215% 증가). 오픈 리졸버에 위조된 소스로 쿼리를 보내고, 큰 응답을 당신에게 향하게 한다. 대응: 소스가 정당한 리졸버이므로 소스 기준 차단은 불가능하다. 응답 크기와 비율로 흡수하거나 필터링할 수밖에 없다. dig +short txt chaos version.bind @your.ip로 자신이 오픈 리졸버를 실행 중이 아닌지 확인하라. 응답한다면 오늘 당장 닫아라.

CLDAP(0.05%로 붕괴). 현재는 조용하지만 역사적으로 거대했다. CLDAP 증폭이 UDP 389에서 실행된다는 점을 기억하라. LDAP이 필요하지 않다면 호스트와 클라우드 방화벽 양쪽에서 필터링을 유지하라. 공격자들은 벡터를 돌려올 때 보호되지 않은 포트로 돌아온다.

SYN 플러드. 용량 문제라기보다 항상 고갈 문제다. SYN 쿠키를 활성화하고(sysctl -w net.ipv4.tcp_syncookies=1), net.core.somaxconn을 올리고, conntrack 테이블이 기본값이 아니라 최악의 현실적 시나리오에 맞게 크기가 정해졌는지 확인하라.

예방: 4분 버스트를 견디는 아키텍처

용량 계산부터 하자. 많은 희망적 사고를 없애준다. 64인, 100틱 서버는 플레이어당 약 15~20KB/s를 처리한다. 인스턴스당 1.2MB/s, 즉 약 10Mbps다. 400Mbps 공격은 그 인스턴스 정상 트래픽의 40배다. 98%의 공격이 1Gbps 미만이므로, 현재 분포에서 거의 모든 공격은 단일 게임 인스턴스를 초토화할 만큼 크면서도 티어 1 스크러빙 제공업체의 자동 완화가 관심을 가질 만큼은 훨씬 못 되는 규모다.

이 사실은 아키텍처를 세 가지 측면에서 제약한다.

계정 플레인과 게임 플레인을 분리하라. 로그인 엔드포인트, 리더보드 읽기, 플레이어 저장이 게임 서버와 같은 호스트에서 실행된다면, 게임 포트에 대한 플러드가 계정까지 무너뜨려 플레이어가 "잠시만 기다려 주세요" 화면조차 볼 수 없다. 인증, Cloud Save, 리더보드가 모두 관리되는 horizOn에서 계정 바인딩 서비스를 실행하면, 게임 포트에 대한 버스트가 발생해도 모든 플레이어의 프로필을 잠그지 않고 게임플레이만 저하시킨다.

기능 셰딩을 배포가 아니라 구성 변경으로 만들어라. 플러드가 닥치면 몇 초밖에 없다. 원격 구성을 사용하면 비싼 읽기 경로를 즉시 끌 수 있다. 빌드를 푸시하는 것만이 유일한 옵션이라면, 당신은 그걸 사용하지 않을 것이다.

헬스 체크가 네트워크 장애를 감지하도록 만들어라. 프로세스 체크는 커널이 패킷의 90%를 버리는 동안 "정상"이라고 보고한다. 실제 게임플레이 도달 가능성을 프로브하라. 실제 핸드셰이크를 수행하고 250ms 이내에 응답을 기대하는 합성 클라이언트로 말이다. 그리고 그 결과가 장애 조치(failover)를 구동하게 하라.

모범 사례: 이번 주에 적용할 가치가 있는 5가지 변경

  1. 대역폭뿐 아니라 패킷 레이트로 알림을 설정하라. 95퍼센타일 pps의 2.5배를 임계값으로 설정하라. 대역폭만 보는 알림은 짧은 버스트를 완전히 놓친다. pps 이상 징후 탐지는 1Gbps 링크를 가진 호스트에서 어떤 대역폭 그래프도 표시하지 못하는 300Mbps 플러드를 잡아낸다.
  2. conntrack 테이블 크기를 의도적으로 설정하라. net.netfilter.nf_conntrack_max를 배포판 기본값이 아니라 최대 동시 세션 기준으로 설정하고, 70% 점유율에서 알림을 울려라. 이 한 가지 변경만으로 가장 흔한 "공격이 장애가 된" 에스컬레이션을 막을 수 있다.
  3. nftables 규칙셋을 미리 작성하고 분기마다 테스트하라. 배포 구성 옆에 저장하라. 장애 중에 적용된 테스트되지 않은 방화벽 규칙은 두 번째 장애다.
  4. 플레이어에게 저하 모드(degraded-mode) 계약을 공개하라. 완화 중에 리더보드와 채팅이 10분간 꺼진다면 패치 노트와 상태 페이지에 그렇게 밝혀라. 침묵은 죽은 게임으로 읽힌다. 이에 대한 모델이 필요하다면, 침해 후 백엔드 아키텍처 분석의 복원력 패턴이 가용성 장애에 그대로 적용된다.
  5. 모든 공격을 구조화된 이벤트로 로그하라. 타임스탬프, 벡터, 최대 pps, 최대 Gbps, 지속 시간, 적용된 완화 조치. 다섯 건이 지나면 자신의 패턴이 보일 것이다. 대부분의 인디 백엔드는 같은 요일, 같은 시간, 같은 부터에게 공격받는다.

결론

2026년 위협 프로파일은 테라비트급 공격을 계획하면서 400Mbps 6분 버스트를 무시하는 팀에게 적대적이다. 거인은 69% 줄었고, 떼는 208% 늘었다. 완화는 자동화되어야 하고, 저렴해야 하며, 커널에 있어야 한다. 알림을 읽기도 전에 끝나는 공격 안에서 인간이 행동할 수는 없기 때문이다.

대시보드가 아니라 카운터에서 시작하라. 소스별 레이트 리밋을 추가하고, conntrack 테이블 크기를 정하고, 계정 플레인과 게임 플레인을 분리하고, 10분 런북이 지루해질 때까지 리허설하라.

그 분리의 일부가 당신이 맡고 싶지 않은 화려하지 않은 작업이라면 — 플레이어 인증, 리비전 인식 Cloud Save, 리더보드, 장애 중에 뒤집을 수 있는 원격 구성 — 그 부분은 horizOn이 처리하므로, 당신의 온콜 시간은 게임 서버에 쏟을 수 있다. 무료로 사용해 보거나 다음 출시 전에 문서를 읽어보라.


출처: Cloudflare DDoS Threat Report 2026 Q3: 더 많은 떼, 더 적은 거인