
Authoritative Server로 이동 Speed Hack 막기: 서버 패킷 검증 구현 가이드
클라이언트가 보낸 위치와 속도를 그대로 신뢰하면 이동 speed hack에 취약해진다. Authoritative Server에서 입력·시간·물리 제약을 검증해 불가능한 이동을 차단하는 실전 패킷 검증 방법을 정리한다.
핵심 요약
이동 speed hack은 클라이언트가 조작한 좌표나 속도를 서버가 그대로 반영할 때 발생한다. 해결책은 클라이언트가 위치 결과가 아니라 입력 명령을 보내고 서버가 자신의 시간과 물리 규칙으로 이동 결과를 계산하는 Authoritative Server 구조를 만드는 것이다.
좌표 동기화가 필요한 경우에도 서버는 이전 상태, 경과 시간, 최대 속도, 가속도, 지형 충돌을 함께 검증해 허용 범위를 벗어난 패킷을 거부하거나 보정해야 한다.
이동 speed hack은 왜 발생하는가?
가장 위험한 설계는 클라이언트가 계산한 위치를 서버가 신뢰하는 방식이다.
{
"type": "Move",
"x": 183.4,
"y": 27.0,
"z": 91.8
}
이 패킷만 받는 서버는 x, y, z가 정상적인 조작과 물리 계산의 결과인지 알 수 없다. 메모리 변조, 패킷 프록시, 재전송 도구를 사용하면 공격자는 한 틱에 수십 미터를 이동한 좌표를 보낼 수 있다.
| 방식 | 클라이언트가 보내는 값 | 서버 역할 | speed hack 위험 |
|---|---|---|---|
| 위치 신뢰형 | 최종 좌표 | 좌표 저장·브로드캐스트 | 매우 높음 |
| 속도 신뢰형 | 속도 벡터 | 위치 적분 | 높음 |
| 입력 권한형 | 이동 방향, 점프, 시퀀스 | 물리·충돌·위치 계산 | 낮음 |
핵심은 네트워크 패킷이 아니라 게임 상태의 결정권이다. 클라이언트는 입력을 요청할 수 있지만 플레이어의 실제 위치는 서버만 확정해야 한다.
Authoritative Server는 이동을 어떻게 검증할까?
서버 권한 모델에서는 클라이언트가 MoveInput을 보내고 서버가 틱 단위로 캐릭터 상태를 갱신한다. 서버가 보유하는 상태는 최소한 다음과 같다.
- 마지막 확정 위치와 회전
- 현재 속도와 낙하 상태
- 이동 속도, 대시, 버프, 감속 같은 스탯
- 마지막 처리 입력 시퀀스 번호
- 서버 기준 마지막 시뮬레이션 시각
- 충돌체 및 지형 질의 결과
flowchart LR
C[클라이언트 입력] --> P[MoveInput 패킷]
P --> V[서버 시퀀스·범위 검증]
V --> S[서버 물리 시뮬레이션]
S --> K[충돌·지형 검증]
K --> A[권위 상태 확정]
A --> B[스냅샷 브로드캐스트]
B --> C
클라이언트는 서버 스냅샷을 받아 자신의 예측 위치를 보정한다. 이 구조에서는 클라이언트 시간을 빠르게 하거나 좌표를 바꿔도 서버의 위치 계산에는 직접 영향을 줄 수 없다.

최대 이동 거리 검증은 어떻게 구현할까?
레거시 프로토콜처럼 좌표를 받아야 하거나 서버 시뮬레이션 앞단의 방어선을 추가하려면 이전 위치와 서버 경과 시간으로 최대 이동 가능 거리를 계산한다.
- : 버프와 장비 효과를 반영한 현재 최대 속도
- : 클라이언트 타임스탬프가 아닌 서버가 측정한 경과 시간
- : 최대 가속도
- : 부동소수점 오차와 네트워크 지터를 위한 작은 허용치
실제 이동 거리 가 보다 크면 해당 이동은 불가능한 상태 전이로 판단한다.
Step 1. 패킷 형식을 입력 중심으로 제한한다
좌표와 속도를 일반 이동 패킷에 넣지 않는다. 이동 축, 버튼 상태, 클라이언트 입력 번호처럼 의도만 전달한다.
public readonly record struct MoveInput(
uint Sequence,
sbyte MoveX,
sbyte MoveZ,
bool JumpPressed
);
MoveX, MoveZ는 -1, 0, 1만 허용한다. 범위를 벗어난 값, 미래 시퀀스가 과도하게 앞선 값, 이미 처리한 시퀀스는 즉시 폐기한다.
Step 2. 서버 틱에서 속도와 위치를 계산한다
다음 예시는 20Hz 서버 틱에서 수평 이동을 계산하는 단순화된 C# 코드다. 실제 서비스에서는 경사, 넉백, 상태 이상, 이동 기술을 같은 권위 시뮬레이션에 포함해야 한다.
const float TickSeconds = 0.05f;
const float MaxSpeed = 6.0f;
const float Acceleration = 30.0f;
void SimulateMove(Player player, MoveInput input)
{
var direction = new Vector3(input.MoveX, 0, input.MoveZ);
if (direction.LengthSquared() > 1.0f)
direction = Vector3.Normalize(direction);
var targetVelocity = direction * player.EffectiveMaxSpeed;
player.Velocity = MoveTowards(
player.Velocity,
targetVelocity,
Acceleration * TickSeconds);
var requestedPosition = player.Position + player.Velocity * TickSeconds;
player.Position = world.ResolveCapsuleMovement(
player.Collider,
player.Position,
requestedPosition);
}
EffectiveMaxSpeed는 고정 상수가 아니라 서버가 계산한 값이어야 한다. 예를 들어 대시가 가능한 상태인지 이동 불가 디버프가 있는지 탈것에 탑승했는지는 모두 서버 상태에서 판정한다.
Step 3. 불가능한 상태 전이를 별도로 차단한다
거리 검증만으로는 벽 통과, 공중 부양, 쿨다운 무시를 막을 수 없다. 다음 검증을 조합한다.
| 검증 항목 | 확인할 서버 상태 | 차단하는 공격 |
|---|---|---|
| 지형 충돌 | 이동 구간의 Capsule Cast 또는 Sweep | 벽 통과, 텔레포트 |
| 수직 속도 | 중력, 점프 시작 시각, 지면 접촉 | 비행, 무한 점프 |
| 스킬 조건 | 쿨다운, 자원, 목표 거리 | 대시 연속 사용 |
| 순서 검증 | 입력 시퀀스와 마지막 처리 번호 | 과거 패킷 재전송 |
| 속도 한도 | 버프 포함 최대 속도와 가속도 | speed hack |
좌표 보고형 프로토콜을 당장 교체할 수 없다면 허용 거리 초과 패킷을 무조건 접속 해제하기보다 먼저 서버의 마지막 정상 좌표로 되돌리고 위반 점수를 올리는 편이 안전하다. 일시적인 패킷 지연과 악의적 조작을 구분할 여지가 생긴다.
bool IsReachable(Vector3 previous, Vector3 reported, float elapsedSeconds)
{
float maxDistance = MaxSpeed * elapsedSeconds
+ 0.5f * MaxAcceleration * elapsedSeconds * elapsedSeconds
+ PositionTolerance;
return Vector3.Distance(previous, reported) <= maxDistance;
}
void HandleReportedPosition(Player player, Vector3 reported)
{
var elapsed = clock.Now - player.LastAcceptedMoveAt;
if (!IsReachable(player.Position, reported, elapsed) ||
world.HasBlockingCollision(player.Collider, player.Position, reported))
{
player.ViolationScore++;
SendCorrection(player, player.Position);
return;
}
player.Position = reported;
player.LastAcceptedMoveAt = clock.Now;
}
클라이언트 타임스탬프를 신뢰하면 안 되는 이유는?
클라이언트 타임스탬프는 입력 정렬이나 지연 추정에는 쓸 수 있지만 최대 이동 거리를 결정하는 신뢰 근거로 쓰면 안 된다. 공격자가 시간을 앞당기면 서버가 큰 를 계산하게 만들 수 있기 때문이다.
이동 검증의 경과 시간은 LastAcceptedMoveAt과 현재 서버 단조 시계(monotonic clock)의 차이로 구한다. 패킷이 늦게 도착했다고 해서 한 번에 무제한 이동을 허용해서도 안 된다. 서버는 긴 지연 구간을 여러 틱으로 나누어 시뮬레이션하거나 허용 가능한 최대 보정 시간을 제한해야 한다.
float elapsed = MathF.Min(
clock.Now - player.LastSimulationAt,
0.25f // 한 번의 처리에서 최대 250ms만 보정
);
보정, 로그, 제재는 어떻게 운영할까?
패킷 하나로 영구 제재를 결정하면 네트워크 품질이 나쁜 정상 사용자를 오탐할 수 있다. 권장 순서는 다음과 같다.
- 즉시 보정: 거부한 이동을 적용하지 않고 마지막 권위 좌표를 스냅샷으로 전송한다.
- 위반 점수 누적: 거리 초과량, 충돌 우회 여부, 반복 빈도를 서버 로그에 기록한다.
- 점진적 대응: 짧은 시간에 반복되면 관전 격리, 매치 퇴장, 일시 정지, 최종 제재 순으로 대응한다.
로그에는 원본 패킷 전체보다 분석에 필요한 최소 정보를 남긴다. 플레이어 ID, 서버 틱, 이전·보고·보정 좌표, 계산된 최대 거리, 이동 상태, 위반 사유가 유용하다. 개인정보나 인증 토큰은 치트 로그에 기록하지 않는다.
자주 묻는 질문 (FAQ)
좌표 패킷을 완전히 없애야 하나요?
가능하면 일반 이동은 입력 패킷으로 전환하는 것이 가장 안전하다. 다만 텔레포트, 리스폰, 서버 스크립트 이동처럼 서버가 승인한 위치 변경은 별도의 서버 이벤트로 처리할 수 있다.
서버 검증만 하면 랙 보정은 필요 없나요?
필요하다. 서버 권한은 치트 방지 정책이고 클라이언트 예측과 서버 보정은 조작감을 위한 기술이다. 둘을 함께 적용해야 조작감과 신뢰성을 모두 확보할 수 있다.
최대 속도만 검사하면 speed hack을 막을 수 있나요?
아니다. 가속도, 충돌, 수직 이동, 스킬 쿨다운, 패킷 순서도 함께 검증해야 한다. 특히 벽을 통과한 뒤 정상 속도로 걷는 공격은 속도 검사만으로 발견하기 어렵다.
정리
speed hack 방지의 출발점은 클라이언트를 신뢰하지 않는 것이다. 이동 결과를 받는 서버에서 입력을 받는 서버로 전환하고 서버 시간·물리·충돌·상태 조건을 기준으로 위치를 확정하자. 이후에는 보정과 위반 점수 기반 운영을 더해 정상 사용자의 지연은 흡수하면서 조작 패킷은 지속적으로 차단할 수 있다.


