← 문제로

47. 섹션 오프셋 테이블의 offset/length 무검증 (임의 접근 경계 밖 읽기)

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

해설 — 섹션 오프셋 테이블의 offset/length 무검증 (임의 접근 경계 밖 읽기)

난이도: 중상

요약

ReadSection이 디렉터리의 dir.Length로 버퍼를 할당하고 (A), dir.Offset에서 dir.Length만큼 Array.Copy 한다 (B). 그런데 Offset/Length가 이번 패킷의 유효 범위(_headerEnd <= Offset, Offset + Length <= _payloadLen, Length >= 0) 안에 있는지 전혀 검증하지 않는다. 오프셋은 순차 파서와 달리 임의 접근이라 공격자가 본문 밖(헤더 영역, 이전 패킷 잔여, 다른 섹션)을 가리키게 만들 수 있다. 재사용 풀 버퍼라 물리적 배열 범위 안이면 예외 없이 잔여 데이터를 조용히 읽어(정보 유출), 물리적 범위를 넘으면 ArgumentException(크래시/DoS). 과대 Length는 대량 할당(메모리 압박)도 유발한다.

문제점

  • 분류태그: 역직렬화 임의 접근 오프셋/길이 무검증(입력 신뢰) → 경계 밖 읽기/정보 유출/과대 할당
  • 증상: 조작 패킷으로 서버 메모리 잔여 노출(다른 세션 데이터 유출), 크래시, 과대 할당으로 인한 GC 압박/OOM.
  • 재현조건: Offset < _headerEnd(헤더 침범), Offset + Length > _payloadLen(본문 초과), Length 과대, 음수 값.
  • 근본원인: offset/length가 신뢰 불가 입력인데 payload 경계와 대조하지 않는다. 순차 파서는 커서가 자연히 앞으로만 가지만, 오프셋 테이블은 임의 위치를 지시하므로 각 접근마다 명시적 범위 검증이 필요하다.

수정안

읽기 전에 오프셋·길이를 payload 범위로 검증하고, 실패 시 패킷을 거부한다. 오버플로도 주의(offset+length가 int 오버플로).

public bool TryReadSection(SectionDir dir, out byte[] section)
{
    section = Array.Empty<byte>();
    if (dir.Length < 0 || dir.Offset < _headerEnd) return false;
    // 오버플로 안전 비교: long 승격
    if ((long)dir.Offset + dir.Length > _payloadLen) return false;
    if (dir.Length > MaxSectionBytes) return false;            // 과대 할당 방어
    section = new byte[dir.Length];
    Array.Copy(_buf, dir.Offset, section, 0, dir.Length);
    return true;
}

Parse는 반환값을 확인하고 하나라도 실패하면 폐기. 섹션끼리 겹치면 안 되는 규약이면 오프셋 단조·비중첩도 검증.

더 나은 설계

  • 경계는 payload 슬라이스로 강제: ReadOnlySpan<byte>로 payload 구간만 넘기고 span.Slice(offset, length)를 쓰면 슬라이스가 범위를 자동 검사하고 잔여 노출이 불가능해진다.
  • 오프셋을 상대(본문 시작 기준)로 정의하고 전 섹션을 한 번에 검증(디렉터리 스캔 단계에서 모두 통과해야 파싱 진행).
  • 재사용 풀 버퍼는 유효 길이 밖 접근을 물리적으로 차단(슬라이스) 또는 사용 전 클리어.

면접 포인트

  • 순차 파서 vs 오프셋 테이블(임의 접근)의 검증 차이 — 오프셋마다 경계 검사 필요.
  • 정수 오버플로를 고려한 경계 비교(offset+length를 long으로).
  • 재사용 풀 버퍼가 정보 유출 벡터가 되는 이유와 span/슬라이스 방어.