← 문제로

36. 압축 해제(decompression bomb)의 크기 상한·비율 검증 부재 (C#)

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

해설 — 압축 해제(decompression bomb)의 크기 상한·비율 검증 부재 (C#)

난이도: 상

요약

신뢰할 수 없는 클라이언트가 보낸 압축 프레임을 해제하면서 공격자가 정한 declaredSize를 그대로 믿어 버퍼를 할당하고, **해제 출력에 상한(최대 크기·압축 비율)**을 두지 않는다. 공격자는 (1) declaredSize를 거대한 값(예: 2GB)으로 보내 즉시 대용량 할당을 유발하거나 음수/과대값으로 예외를 던지고, (2) 작은 압축 데이터가 거대하게 풀리는 **압축 폭탄(zip bomb)**으로 서버 메모리를 고갈시킨다.

문제점

1) [힌트 신뢰] declaredSize 로 버퍼 할당 (증상: 대용량 할당·예외·OOM)

  • 증상: (A)에서 프레임 앞 4바이트를 declaredSize로 읽고 (B)에서 new byte[declaredSize]. 이 값은 공격자가 정한다. 2GB 같은 값이면 즉시 거대한 할당(또는 OutOfMemoryException/OverflowException), 음수면 OverflowException. 실제 압축 내용과 무관하게 선언값만으로 자원을 소비한다.
  • 근본원인: 신뢰 경계를 넘은 크기 필드를 검증 없이 할당에 사용.

2) [출력 상한 부재] 압축 폭탄 (증상: 메모리 고갈 DoS)

  • 증상: (C)의 루프는 output(=declaredSize 크기)까지 읽는다. declaredSize를 크게 잡고 그만큼 실제로 풀리는 데이터를 넣으면(작은 압축 → 큰 원본, 예: 반복 바이트) 서버가 그 전부를 메모리에 적재한다. 여러 연결이 동시에 하면 서버가 죽는다. 압축 비율(입력 대비 출력)에 상한이 없다.
  • 근본원인: "해제 출력 총량 ≤ 정책상 최대치" 및 "압축비 ≤ 상한" 검증 부재.

3) [프레임 경계·정합성 미검증] (부수)

  • (A)는 frame.Length >= 4인지, declaredSize가 실제 해제 결과와 일치하는지 확인하지 않는다. declaredSize보다 실제 해제가 짧으면 output 뒤가 0으로 남고, 길면 (C)가 조기 종료해 데이터가 잘린다(선언과 실제 불일치를 검출 못 함).

수정안

정책상 최대 크기로 상한을 두고, declaredSize 는 상한 이내에서만 신뢰하며, 해제 중 누적 출력이 상한을 넘으면 중단.

public static byte[] Decompress(byte[] frame, int maxOutput) // maxOutput: 정책 상한
{
    if (frame.Length < 4) throw new InvalidDataException("short frame");
    int declared = BitConverter.ToInt32(frame, 0);
    if (declared < 0 || declared > maxOutput)                 // 힌트 신뢰 상한
        throw new InvalidDataException("declared size out of range");

    using var input = new MemoryStream(frame, 4, frame.Length - 4);
    using var deflate = new DeflateStream(input, CompressionMode.Decompress);
    using var output = new MemoryStream(capacity: Math.Min(declared, 64 * 1024));

    var buf = new byte[8192];
    int total = 0, n;
    while ((n = deflate.Read(buf, 0, buf.Length)) > 0)
    {
        total += n;
        if (total > maxOutput)                                // 해제 중 누적 상한
            throw new InvalidDataException("decompression bomb: output too large");
        output.Write(buf, 0, n);
    }
    if (total != declared)                                    // 선언 ↔ 실제 일치 검증
        throw new InvalidDataException("declared/actual size mismatch");
    return output.ToArray();
}
  • maxOutput(정책 상한)으로 선언값과 실제 출력 둘 다 제한한다. 버퍼는 선언값이 아니라 작은 청크로 스트리밍하며 누적하다가 상한 초과 시 즉시 중단.
  • 선언값과 실제 해제 크기 일치까지 검증해 프레임 위조를 걸러낸다.

더 나은 설계 (트레이드오프)

  • 압축비 상한: 출력/입력 비율 상한(예: 100:1)을 두면 폭탄을 조기에 차단. 트레이드오프: 정상 데이터의 최대 압축비를 넘지 않게 튜닝 필요.
  • 스트리밍 소비: 해제 결과를 통째로 메모리에 담지 말고 파서가 스트림으로 소비하면 피크 메모리를 줄인다. 트레이드오프: 파서가 스트리밍 지원해야.
  • 프레임당·연결당 예산: 연결별 해제 총량/속도에 예산을 두고 초과 시 스로틀·킥. 트레이드오프: 회계 오버헤드.

면접 포인트

  • "declaredSize 를 믿고 할당" 이 왜 위험한지(신뢰 경계)와, 해제 출력에 상한이 필요한 이유(압축 폭탄).
  • 선언 크기·실제 해제 크기·압축비를 각각 검증하는 이유와 스트리밍 해제의 이점.
  • 연결당 자원 예산·스로틀 같은 DoS 방어 설계.