Zip Slip

검증하지 않은 ZIP 엔트리 경로로 인한 추출 디렉터리 밖의 파일 쓰기

설명

ZIP 엔트리 이름을 검증하지 않고 추출 경로로 사용하면 ../ 경로를 통해 대상 디렉터리 밖에 파일을 쓸 수 있습니다.

잠재적 영향

  • 임의 파일 쓰기, 설정 덮어쓰기, 웹 루트 파일 생성으로 이어질 수 있습니다.

해결 방법

각 엔트리의 최종 경로를 canonical path로 계산하고, 대상 디렉터리 경계를 벗어나면 추출하지 않습니다. 추출 대상은 공격자가 쓸 수 없는 서비스 전용 디렉터리 아래에 새로 만들고 기존 파일을 덮어쓰지 않습니다.

예시

변경 전

csharp
using System.IO;
using System.IO.Compression;

sealed class ZipExtractor
{
    public static void ExtractEntry(
        ZipArchiveEntry entry,
        string targetDirectory)
    {
        var path = Path.GetFullPath(Path.Combine(targetDirectory, entry.FullName));
        entry.ExtractToFile(path, overwrite: true);
    }
}

변경 후

csharp
using System;
using System.IO;
using System.IO.Compression;

static string ExtractToNewDirectory(ZipArchive archive, string serviceOwnedRoot)
{
    // serviceOwnedRoot는 다른 사용자나 프로세스가 쓸 수 없어야 합니다.
    var targetRoot = Directory.CreateDirectory(
        Path.Combine(serviceOwnedRoot, Path.GetRandomFileName())).FullName;
    var targetPrefix = Path.GetFullPath(targetRoot) + Path.DirectorySeparatorChar;

    foreach (var entry in archive.Entries)
    {
        var path = Path.GetFullPath(Path.Combine(targetRoot, entry.FullName));
        if (!path.StartsWith(targetPrefix, StringComparison.Ordinal))
        {
            throw new InvalidDataException("Archive entry escapes the target directory");
        }

        if (string.IsNullOrEmpty(entry.Name))
        {
            Directory.CreateDirectory(path);
            continue;
        }

        Directory.CreateDirectory(Path.GetDirectoryName(path)!);
        entry.ExtractToFile(path, overwrite: false);
    }

    return targetRoot;
}

설명:

  • 변경 전: 정규화된 대상을 검증하지 않고 아카이브 엔트리를 추출하면 ../가 포함된 엔트리가 대상 디렉터리 밖에 파일을 쓸 수 있습니다.
  • 변경 후: 서비스만 쓸 수 있는 위치에 매번 새 추출 디렉터리를 만들고, Ordinal 경계 검사로 대소문자를 구분하는 파일 시스템에서도 경로 이탈을 차단합니다. overwrite: false로 기존 파일도 보존합니다.

참조