임의 파일 쓰기 아카이브 추출을 통한 공격 (Tar Slip)

Arbitrary File Write via Archive Extraction (Tar Slip)

설명

Tar Slip 공격은 공격자가 특수하게 조작된 tar 아카이브 파일을 통해, 파일이 아카이브에서 추출될 때 임의의 위치에 파일을 작성하도록 하는 공격입니다. 이 공격은 절대 경로 및 상대 경로(../)를 사용하여 발생하며, 이를 통해 중요한 시스템 파일이나 민감한 정보가 덮어쓰여질 수 있습니다.

잠재적 영향

  • 파일 시스템 손상: 공격자가 시스템 파일을 덮어써서 시스템을 손상시킬 수 있습니다.
  • 정보 유출: 민감한 정보가 공격자에게 노출될 수 있습니다.
  • 권한 상승: 공격자가 시스템의 중요한 파일을 수정하여 권한을 상승시킬 수 있습니다.

해결 방법

  • 표준 추출 필터 사용: 지원되는 Python에서는 extractall(..., filter="data")를 명시하여 절대 경로, 대상 밖 링크, 특수 파일과 과도한 권한을 제한합니다.
  • 안전한 추출 경로 사용: 신뢰할 수 있는 부모 아래에 새 전용 디렉터리를 만들어 추출합니다.
  • 레거시 런타임 방어: 추출 필터를 지원하지 않는 버전에서는 최종 경로의 디렉터리 경계를 확인하고 심볼릭 링크, 하드 링크, 장치 파일 같은 비정규 엔트리를 거부합니다.
  • 자원 제한: 추출 필터만으로 압축 폭탄이나 과도한 파일 수를 막을 수 없으므로 전체 크기, 파일 수, 처리 시간을 별도로 제한합니다.

예시

변경 전

python
# Tar extraction using the runtime default filter
import tarfile

def unsafe_extract(tar_file_path, extract_path):
    with tarfile.open(tar_file_path) as tar:
        tar.extractall(path=extract_path)

변경 후

python
# Safe tar extraction
import tarfile
import os
import shutil
from pathlib import Path

def safe_extract(tar_file_path, extract_path):
    destination = Path(extract_path)
    # 기존 파일이나 링크가 없는 새 전용 디렉터리에서만 추출
    destination.mkdir(mode=0o700, exist_ok=False)
    destination = destination.resolve()

    with tarfile.open(tar_file_path) as tar:
        if hasattr(tarfile, "data_filter"):
            # 추출 필터를 지원하는 런타임의 제한적 데이터 추출 정책
            tar.extractall(path=destination, filter="data")
            return

        # 레거시 fallback: 링크와 특수 파일을 모두 거부하고 경계를 검증
        for member in tar.getmembers():
            if member.issym() or member.islnk():
                raise ValueError("Tar links are not allowed")
            if not (member.isfile() or member.isdir()):
                raise ValueError("Unsupported tar entry type")

            target = (destination / member.name).resolve()
            try:
                target.relative_to(destination)
            except ValueError as error:
                raise ValueError("Tar entry escapes extraction directory") from error

            if member.isdir():
                target.mkdir(parents=True, exist_ok=True, mode=0o700)
                continue

            target.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
            source = tar.extractfile(member)
            if source is None:
                raise ValueError("Tar entry has no file data")
            fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
            with source, os.fdopen(fd, "wb") as output:
                shutil.copyfileobj(source, output)

설명:

  • 변경 전: 추출 필터를 명시하지 않습니다. Python 3.14부터 기본 필터는 data이므로 이 코드만으로 경로 이탈이 허용되는 것은 아닙니다. 이전 버전이나 기본 필터를 변경한 환경에서는 절대 경로 및 상대 경로(../)를 이용한 쓰기가 허용될 수 있습니다.
  • 변경 후: 새 전용 디렉터리를 만든 뒤 지원되는 런타임에서는 표준 data 필터를 명시합니다. 레거시 fallback은 각 최종 경로가 추출 디렉터리 내부인지 확인하고 심볼릭 링크·하드 링크·특수 파일을 거부하므로, 단순히 이름에 ..가 있는지만 검사할 때 놓치는 링크 대상 우회를 차단합니다.

참조