4. 게임플레이 패킷 검증 (길이/시퀀스/페이로드)

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 게임플레이 패킷 검증 (길이/시퀀스/페이로드)
// ----------------------------------------------------------------------------
// 시나리오:
//   - 인증을 마친 세션이 실시간 게임플레이 패킷을 주고받는다.
//   - 와이어 포맷: [ushort totalLen][ushort packetId][uint seq][payload...]
//       * totalLen 은 헤더(8B) + payload 를 포함한 전체 길이.
//       * seq 는 송신 측이 1씩 증가시키는 시퀀스 번호(중복/재전송 탐지용).
//       * 모든 정수는 little-endian 으로 합의되어 있다.
//   - 예시로 C_UseItem(아이템 사용) 패킷을 처리한다:
//       payload = [uint itemSlot][ushort count]
//   - 클라이언트는 신뢰할 수 없다(치터/프록시/패킷 변조 가능).
//
// 요구사항:
//   - 변조/재전송/길이 위조에 견뎌야 하고, 어떤 입력이 와도 서버가 죽거나
//     상태가 깨지면 안 된다.
//   - 같은 행동이 두 번 처리되면(재전송 공격) 아이템 복제 등 경제 사고가 난다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 악용되는지 설명하고,
//   수정안과 더 나은 검증 설계를 제시하라.
// ============================================================================

using System;

public class Inventory
{
    public int[] slots = new int[64];                 // 슬롯별 보유 수량
    public void Consume(uint slot, ushort count) { slots[slot] -= count; }
}

public class Session
{
    public Inventory inv = new Inventory();
    public uint lastSeq = 0;                           // 마지막으로 본 시퀀스
    public void Disconnect() { /* 세션 종료 */ }
}

public class GameplayHandler
{
    private const int HeaderSize = 8;                  // ushort + ushort + uint

    // recvBuf[0..recvLen) : 길이 프리픽스 단계에서 이미 한 패킷 분량으로 잘라 넘겨졌다고 가정
    public void OnPacket(Session s, byte[] recvBuf, int recvLen)
    {
        // (A) 헤더 해석
        ushort totalLen = BitConverter.ToUInt16(recvBuf, 0);
        ushort packetId = BitConverter.ToUInt16(recvBuf, 2);
        uint   seq      = BitConverter.ToUInt32(recvBuf, 4);

        // (B) 시퀀스 검사: 과거 시퀀스면 무시
        if (seq < s.lastSeq)
            return;
        s.lastSeq = seq;

        // payload 시작 위치와 길이
        int payloadOffset = HeaderSize;
        int payloadLen    = totalLen - HeaderSize;     // (C)

        switch (packetId)
        {
            case 1001: // C_UseItem
                HandleUseItem(s, recvBuf, payloadOffset, payloadLen);
                break;
            default:
                break;
        }
    }

    private void HandleUseItem(Session s, byte[] buf, int offset, int payloadLen)
    {
        // (D) payload 를 필드로 해석해 바로 사용
        uint   itemSlot = BitConverter.ToUInt32(buf, offset);
        ushort count    = BitConverter.ToUInt16(buf, offset + 4);

        // (E) 아이템 소비
        s.inv.Consume(itemSlot, count);
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.