설명
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로 기존 파일도 보존합니다.