Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 706 – tarfile.extractall을 위한 필터

Author:
Petr Viktorin <encukou at gmail.com>
Discussions-To:
Discourse thread
Status:
Final
Type:
Standards Track
Created:
09-Feb-2023
Python-Version:
3.12
Post-History:
25-Jan-2023, 15-Feb-2023
Resolution:
Discourse message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at tarfile documentation.

×

See PEP 1 for how to propose changes.

초록

tarfile의 추출 메서드에 filter 인자가 추가되어, 아카이브를 추출하는 동안 파일을 거부하거나 메타데이터를 수정할 수 있습니다. 예상치 못하거나 위험할 수 있는 기능을 제한하기 위해 이름이 붙은 내장 필터 세 가지를 제공합니다. 이 필터들은 그대로 사용할 수도 있고, 사용자 정의 필터의 기반으로 삼을 수도 있습니다.

폐지 예고 기간이 지나면 엄격한 (하지만 더 안전한) 필터가 기본값이 됩니다.

동기

tar 형식은 여러 사용 사례에 쓰이며, 그중 다수는 서로 다른 요구 사항을 가지고 있습니다. 예를 들면 다음과 같습니다.

  • UNIX 워크스테이션의 백업은 파일 권한, 시스템 설정에 대한 심볼릭 링크, 다양한 종류의 특수 파일 등 모든 종류의 세부 사항을 충실하게 보존해야 합니다.
  • 데이터 번들을 풀 때는 압축 해제가 의도치 않은 결과를 낳지 않는 것이 훨씬 더 중요합니다 – 비밀번호 파일을 공개된 위치로 심볼릭 링크하여 노출하는 것과 같은 결과 말입니다.

이 모든 사용 사례를 지원하기 위해 tar 형식에는 많은 기능이 있습니다. 많은 경우 아카이브를 추출할 때는 그중 일부를 무시하거나 허용하지 않는 것이 가장 좋습니다.

Python에서는 tarfile.TarFile.extractall()을 사용해 tar 아카이브를 추출할 수 있으며, 그 문서는 신뢰할 수 없는 출처의 아카이브를 사전 검사 없이 절대 추출하지 말라고 경고합니다. 하지만 어떤 종류의 검사를 해야 하는지는 명확하지 않습니다. 실제로 그러한 검사를 올바르게 수행하는 것은 상당히 까다롭습니다. 그 결과 많은 사람이 검사를 아예 하지 않거나 잘못된 방식으로 검사하며, 이는 CVE-2007-4559와 같은 보안 문제로 이어집니다.

tarfile이 처음 작성된 이후로, 문서의 경고만으로는 충분하지 않다는 인식이 더 널리 받아들여지게 되었습니다. 가능하다면 안전하지 않은 작업은 명시적으로 요청되어야 하며, 잠재적으로 위험한 작업은 위험해 보여야 합니다. 하지만 TarFile.extractall은 코드 리뷰에서 무해해 보입니다.

tarfile 추출은 shutil.unpack_archive()를 통해서도 노출되는데, 이 함수를 사용하면 다루는 아카이브의 종류를 신경 쓰지 않아도 됩니다. 문서에서 역시 경고하고 있음에도, 이 API는 사전 검사 없이 아카이브를 추출하도록 매우 쉽게 유도합니다.

Python이 잘못된 것이 아니라 문서화된 그대로 동작할 뿐이라는 주장도 있었지만, 그것은 요점이 아닙니다. 책임을 지우거나 피하기보다는 상황을 개선합시다. 상황을 개선하기에 가장 좋은 곳은 Python과 그 문서입니다.

근거

어떻게 상황을 개선할 수 있겠습니까? 안타깝게도 기본값을 변경해야 하며, 이는 하위 호환성을 깨뜨린다는 것을 의미합니다. TarFile.extractall은 사람들이 tarball을 추출해야 할 때 가장 먼저 찾는 메서드입니다. 이 메서드의 기본 동작이 바뀌어야 합니다.

가장 좋은 동작은 무엇이겠습니까? 그것은 사용 사례에 따라 다릅니다. 그래서 추출을 제어하는 몇 가지 일반적인 “정책”을 추가하겠습니다. 이 정책들은 사용 사례에 기반하며, 이상적으로는 보안상의 영향이 명확해야 합니다:

  • 현재 동작: 아카이브를 신뢰합니다. 예를 들어 검사를 직접 수행하는 라이브러리의 구성 요소로 쓰거나, 방금 직접 만든 아카이브를 추출할 때 적합합니다.
  • UNIX 아카이브 풀기: 대체로 GNU tar를 따르며, 예를 들어 파일 이름 앞의 /를 제거합니다.
  • 일반 데이터 아카이브 풀기: shutil.unpack_archive() 사용 사례로, tar나 Unix 계열 파일 시스템에 특화된 세부 사항을 보존하는 것이 중요하지 않은 경우입니다.

폐지 예고 기간이 지나면 마지막 옵션, 즉 가장 제한적이지만 가장 안전한 옵션이 기본값이 됩니다.

더 나은 일반 기본값이 있더라도, 사용자는 여전히 추출하는 아카이브를 검증해야 하며 경우에 따라 메타데이터 일부를 수정해야 할 수도 있습니다. 겉보기에는 다음이 현재 이를 수행하는 합리적인 방법처럼 보입니다:

  • TarFile.getmembers를 호출합니다
  • 각 멤버의 TarInfo를 검증하거나 수정합니다
  • 그 결과를 extractall의 members에 전달합니다

그러나 이 방식에는 몇 가지 문제가 있습니다:

  • TarInfo 객체를 수정할 수는 있지만, 그 변경 사항은 같은 TarFile 객체에 대한 이후의 모든 작업에 영향을 줍니다. 이 동작은 대부분의 용도에서는 문제가 없지만, 그렇더라도 TarFile.extractall이 기본적으로 이렇게 동작한다면 매우 놀라운 일일 것입니다.
  • getmembers를 호출하는 것은 비용이 클 수 있으며, 탐색(seek) 가능한 아카이브가 필요합니다.
  • 멤버를 미리 검증할 때는 각 멤버가 파일 시스템을 어떻게 변경했을지, 예를 들어 심볼릭 링크가 어떻게 설정되는지를 추적해야 할 수도 있습니다. 이것은 어렵습니다. 사용자에게 이를 기대할 수는 없습니다.

이러한 문제를 해결하기 위해 다음과 같이 하겠습니다:

  • TarInfo 객체를 “복제”하고 수정하는 공식적으로 지원되는 방법을 제공합니다. dataclasses.replace()나 namedtuple._replace와 비슷한 replace 메서드면 충분할 것입니다.
  • extractall의 루프 안에 멤버가 처리되기 전에 이를 수정하거나 버릴 수 있는 “필터” 훅을 제공합니다.
  • 이 훅이 각 멤버를 추출하기 직전에 호출되도록 요구하여, 디스크의 현재 상태를 검사할 수 있게 합니다. 이렇게 하면 (표준 라이브러리와 사용자 코드 모두에서) 정책 구현이 크게 단순해지지만, 그 대가로 정확한 “시험 실행(dry run)”은 할 수 없게 됩니다.

훅 API는 TarFile.add의 기존 filter 인자와 매우 비슷할 것입니다. 이름도 filter로 하겠습니다. (경우에 따라서는 “정책”이 더 적합한 이름이겠지만, 이 API는 보안 정책 이외의 용도로도 사용할 수 있습니다.)

위에서 설명한 내장 정책/필터는 공개 필터 API를 사용하여 구현되므로, 구성 요소나 예제로 사용할 수 있습니다.

선례 만들기

zipfile 같은 다른 아카이브 추출 라이브러리가 비슷한 기능을 갖추게 된다면, 합리적인 범위 내에서 최대한 이 API를 따라야 합니다.

간단한 경우에도 이를 쓸 수 있도록 내장 필터에는 문자열 이름이 붙습니다. 예를 들어 사용자는 TarInfo 객체를 다루는 특정 함수 대신 filter='data'를 전달할 수 있습니다.

shutil.unpack_archive() 함수에는 filter 인자가 추가되며, 이 함수는 이 인자를 extractall에 전달합니다.

여러 아카이브 형식에 걸쳐 동작하는 함수 기반 API를 추가하는 것은 이 PEP의 범위를 벗어납니다.

이해관계 공개 및 재배포자 정보

이 PEP 작성자는 Red Hat에서 일하며, Red Hat은 일반적인 CPython과는 다른 보안 요구 사항과 지원 기간을 가진 Python 재배포자입니다. 이러한 재배포자는 다음을 위해 벤더 패치를 유지하고자 할 수 있습니다:

  • 시스템 전체에서 기본값을 설정할 수 있도록 합니다. 그리고
  • 이전 Python 버전에서도 가능한 한 빨리 기본값을 변경합니다.

이 제안은 이를 쉽게 할 수 있게 하며, 사용자가 설정을 조회할 수 있도록 합니다.

명세

멤버 메타데이터 수정 및 제거

TarInfo 클래스에는 dataclasses.replace와 비슷하게 동작하는 새 메서드 replace()가 추가됩니다. 이 메서드는 키워드 전용 인자로 지정한 대로 속성을 교체한 TarInfo 객체의 복사본을 반환합니다:

  • name
  • mtime
  • mode
  • linkname
  • uid
  • gid
  • uname
  • gname

이 중 name과 linkname을 제외한 모든 항목은 None으로 설정할 수 있습니다. extract 또는 extractall이 이러한 None을 만나면 해당 메타데이터 항목을 설정하지 않습니다. (uname 또는 gname이 None이면, 이름을 찾지 못한 경우처럼 uid 또는 gid로 대체합니다.) addfile 또는 tobuf가 이러한 None을 만나면 ValueError를 발생시킵니다. list가 이러한 None을 만나면 자리 표시자 문자열을 출력합니다.

문서에는 이 메서드가 존재하는 이유를 설명합니다. TarFile.getmembers에서 가져온 TarInfo 객체는 “살아 있는” 객체이므로, 이를 직접 수정하면 이후에 수행하는 관련 없는 작업에도 영향을 줍니다.

필터

TarFile.extract 및 TarFile.extractall 메서드에는 filter 키워드 전용 매개변수가 추가됩니다. 이 매개변수는 다음과 같이 호출할 수 있는 호출 가능 객체를 받습니다:

filter(/, member: TarInfo, path: str) -> TarInfo|None

여기서 member는 추출할 멤버이고, path는 아카이브를 추출하는 경로입니다(즉, 모든 멤버에 대해 같습니다).

필터를 사용하면 각 멤버를 추출할 때마다 필터가 호출되며, 추출은 그 결과를 사용해 진행됩니다. 필터가 None을 반환하면 해당 멤버를 건너뜁니다.

이 함수는 예외를 발생시킬 수도 있습니다. 이 경우 TarFile.errorlevel에 따라 추출이 중단되거나 해당 멤버를 건너뛰게 됩니다.

Note

추출이 중단되면 아카이브가 부분적으로만 추출된 상태로 남을 수 있습니다. 정리 작업은 사용자의 책임입니다.

또한 일반적인 사용 사례를 위한 기본값 모음도 제공합니다. filter 인자에는 함수 외에도 다음 문자열 중 하나를 지정할 수 있습니다.

  • 'fully_trusted': 현재 동작입니다. 메타데이터를 있는 그대로 따릅니다. 사용자가 아카이브를 완전히 신뢰하거나 자체적으로 복잡한 검증을 구현하는 경우에 사용해야 합니다.
  • 'tar': GNU tar 명령의 기본값을 대략적으로 따릅니다(일반 사용자로 실행할 때 기준).
    • 파일 이름 앞에 붙은 '/'와 os.sep을 제거합니다.
    • 절대 경로를 가진 파일의 추출을 거부합니다(위의 / 제거 이후 기준이며, 예를 들어 Windows의 C:/foo가 해당합니다).
    • (심볼릭 링크를 따라간 후의) 절대 경로가 대상 위치 바깥이 되는 파일의 추출을 거부합니다. (GNU tar는 이 대신 일부 링크의 생성을 지연시킨다는 점에 유의하십시오.)
    • 상위 모드 비트(setuid, setgid, sticky)와 그룹/기타 사용자 쓰기 비트(S_IWGRP|S_IWOTH)를 지웁니다. (이는 현재 umask 설정으로 모드를 제한하는 GNU tar의 기본 동작을 근사한 것입니다.)
  • 'data': “데이터” 아카이브를 추출합니다. 일반적인 공격 벡터를 허용하지 않는 대신 기능이 제한됩니다. 특히 UNIX 스타일 파일 시스템(또는 마찬가지로 tar 아카이브 형식)에 특화된 많은 기능이 무시되므로, 크로스 플랫폼 아카이브에 적합한 필터입니다. tar의 동작에 더하여 다음을 수행합니다.
    • 절대 경로를 가리키는 링크(하드 링크 또는 소프트 링크)의 추출을 거부합니다.
    • 결과적으로 대상 위치 바깥의 경로를 가리키게 되는 링크(하드 링크 또는 소프트 링크)의 추출을 거부합니다. (링크를 지원하지 않는 시스템에서는 tarfile이 대부분의 경우 일반 파일을 생성하는 방식으로 대체합니다. 이 제안은 그 동작을 변경하지 않습니다.)
    • 장치 파일(파이프 포함)의 추출을 거부합니다.
    • 일반 파일과 하드 링크에는 다음을 적용합니다.
      • 소유자 읽기 및 쓰기 권한(S_IRUSR|S_IWUSR)을 설정합니다.
      • 소유자에게 실행 권한(S_IXUSR)이 없으면 그룹 및 기타 사용자의 실행 권한(S_IXGRP|S_IXOTH)을 제거합니다.
    • 그 밖의 파일(디렉터리)은 모드를 완전히 무시합니다(None으로 설정합니다).
    • 사용자 및 그룹 정보를 무시합니다(uid, gid, uname, gname을 None으로 설정합니다).

그 밖의 문자열은 ValueError를 발생시킵니다.

대응하는 필터 함수는 tarfile.fully_trusted_filter(), tarfile.tar_filter() 등으로 제공되므로 사용자 정의 정책에서 쉽게 사용할 수 있습니다.

이 필터들은 절대 None을 반환하지 않는다는 점에 유의하십시오. 이런 방식으로 멤버를 건너뛰는 것은 사용자 정의 필터를 위한 기능입니다.

기본값과 그 설정

TarFile에는 기본 필터를 설정할 수 있도록 extraction_filter라는 새 속성이 추가됩니다. 기본값은 None이지만, 사용자는 이를 filter 인자가 없거나 None일 때 사용될 호출 가능 객체로 설정할 수 있습니다.

Note

여기서는 문자열 이름을 허용하지 않습니다. 문자열 이름을 허용하면 my_tarfile.extraction_filter = 'data' 같은 코드를 쓰도록 부추기게 됩니다. 이 기능이 없는 Python 버전에서는 이 코드가 아무 일도 하지 않으므로, 보안 관련 요청이 조용히 무시됩니다.

인자와 속성이 모두 None이면:

  • Python 3.12-3.13에서는 DeprecationWarning이 발생하고, 추출에 'fully_trusted' 필터를 사용합니다.
  • Python 3.14 이상에서는 'data' 필터를 사용합니다.

애플리케이션과 시스템 통합자는 TarFile 클래스 자체의 extraction_filter를 변경하여 전역 기본값을 설정할 수 있습니다. 함수를 사용할 때는 self 인자가 주입되지 않도록 대개 staticmethod()로 감싸는 것이 좋습니다.

TarFile의 서브클래스도 extraction_filter를 재정의할 수 있습니다.

FilterError

새로운 예외인 FilterError가 tarfile 모듈에 추가됩니다. 이 예외에는 위에서 설명한 거부 사유마다 하나씩 여러 개의 새 서브클래스가 있습니다. FilterError의 member 속성에는 관련된 TarInfo가 담깁니다.

위 목록에서 파일 추출을 “거부”한다는 것은 FilterError가 발생한다는 뜻입니다. 다른 추출 오류와 마찬가지로 TarFile.errorlevel이 1 이상이면 추출이 중단됩니다. errorlevel=0이면 오류가 기록되고 해당 멤버는 무시되지만 추출은 계속됩니다. extractall()은 아카이브를 부분적으로만 추출한 상태로 남길 수 있으며, 이를 정리하는 것은 사용자의 책임이라는 점에 유의하십시오.

Errorlevel과 치명적/비치명적 오류

현재 TarFile에는 오류 처리 방식을 지정하는 errorlevel 인자/속성이 있습니다:

  • errorlevel=0이면, 문서에는 “extract()와 extractall()을 사용할 때 모든 오류가 무시됩니다”라고 되어 있습니다. 하지만 코드는 비치명적 오류와 치명적 오류(아래 참조)만 무시하므로, 예를 들어 대상 경로로 None을 전달하면 여전히 TypeError가 발생합니다.
  • 기본값인 errorlevel=1이면 모든 비치명적 오류가 무시됩니다. (debug 인자/속성을 설정하면 이 오류들을 sys.stderr에 기록할 수 있습니다.) 어떤 오류가 비치명적인지는 문서에 정의되어 있지 않지만, 코드는 ExtractionError를 비치명적 오류로 취급합니다. 구체적으로는 다음과 같은 문제들입니다:
    • “아카이브 내부의 링크를 해석할 수 없음” (심볼릭 링크를 지원하지 않는 시스템에서 발생)
    • “시스템이 fifo/특수 장치를 지원하지 않음” (시스템이 이를 지원하는 경우의 실패, 예를 들어 PermissionError에는 사용되지 않음)
    • “소유자/모드/수정 시각을 변경할 수 없음”

    예를 들어 파일 이름이 너무 김이나 디스크 공간 부족은 여기에 해당하지 않는다는 점에 유의하십시오. 치명적이지 않은 오류는 Unix 계열 시스템에서는 잘 발생하지 않습니다.

  • errorlevel=2이면 치명적 오류를 포함한 모든 오류가 발생합니다. 어떤 오류가 치명적인지도 역시 정의되어 있지 않으며, 실제로는 OSError입니다.

필터가 멤버 추출을 거부하는 경우는 치명적/비치명적 범주에 깔끔하게 들어맞지 않습니다.

  • 이 PEP는 기존 동작을 변경하지 않습니다. (개선 아이디어는 Discourse topic 25970에서 환영합니다.)
  • 필터가 멤버 추출을 거부할 때, 기본적으로 그 오류가 조용히 넘어가서는 안 됩니다.

이를 충족하기 위해 FilterError는 치명적 오류로 간주됩니다. 즉, errorlevel=0일 때만 무시됩니다.

FilterError는 무시하되 다른 치명적 오류는 무시하지 않으려는 사용자는 사용자 정의 필터 함수를 만들고, 그 안의 try 블록에서 다른 필터를 호출해야 합니다.

추가 검증을 위한 힌트

제안된 변경 사항이 적용되더라도, tarfile은 사전 검사 없이 신뢰할 수 없는 파일을 추출하는 데 적합하지 않습니다. 무엇보다도, 제안된 정책은 서비스 거부 공격을 막지 못합니다. 사용자는 추가 검사를 수행해야 합니다.

새 문서는 사용자에게 다음 사항을 고려하도록 안내할 것입니다:

  • 새로운 빈 디렉터리에 추출하기,
  • 디스크, 메모리, CPU 사용량에 외부(예: OS 수준) 제한 적용하기,
  • 허용된 문자 목록에 비추어 파일 이름 검사하기(제어 문자, 혼동하기 쉬운 문자 등을 걸러내기 위해),
  • 파일 이름에 예상된 확장자가 있는지 검사하기(“클릭하면” 실행되는 파일이나, Windows 특수 장치 이름처럼 확장자가 없는 파일을 피하기 위해),
  • 추출되는 파일 수, 추출되는 데이터의 전체 크기, 개별 파일의 크기 제한하기,
  • 대소문자를 구분하지 않는 파일 시스템에서 다른 파일에 가려지게 될 파일이 있는지 검사하기.

또한, 문서에는 다음 사항이 명시될 것입니다:

  • tar 파일에는 흔히 같은 파일의 여러 버전이 들어 있으며, 추출 시 나중 버전이 이전 버전을 덮어쓰는 것이 예상된 동작이라는 점,
  • tarfile은 “라이브” 데이터와 관련된 문제, 예를 들어 추출(또는 추가)이 진행되는 동안 공격자가 대상 디렉터리를 조작하는 경우를 막지 못한다는 점(자세한 내용은 GNU tar 설명서를 참조하십시오).

이 목록이 모든 것을 다루지는 않지만, 문서는 이러한 일반적인 팁을 모아 두기에 좋은 곳입니다. 목록이 너무 길어지거나 zipfile 또는 shutil과 통합해야 하는 경우(이는 이 제안의 범위를 벗어납니다)에는 별도의 문서로 옮길 수 있습니다.

TarInfo 동일성, 그리고 offset

replace()를 사용하는 필터에서는, 추출 메커니즘이 처리하는 TarInfo 객체가 members에 있는 객체와 반드시 같은 객체라고 할 수 없습니다. 이는 makelink 같은 메서드를 재정의하면서 객체 동일성에 의존하는 TarInfo 서브클래스에 영향을 줄 수 있습니다.

그러한 코드는 파일 내 멤버 헤더의 위치인 offset을 비교하는 방식으로 전환할 수 있습니다.

재정의 가능한 메서드와 offset 모두 소스 주석에만 문서화되어 있다는 점에 유의하십시오.

tarfile CLI

CLI(python -m tarfile)에 제공되는 기본 필터 중 하나의 이름을 받는 --filter 옵션이 추가됩니다. 사용자 정의 필터 함수는 지정할 수 없습니다.

--filter가 주어지지 않으면 CLI는 기본 필터를 사용합니다(현재는 폐지 경고와 함께 'fully_trusted'를, Python 3.14부터는 'data'를 사용합니다).

짧은 옵션은 제공되지 않습니다. (-f는 GNU tar의 파일 이름 옵션과 혼동될 만큼 비슷하기 때문입니다.)

다른 아카이브 라이브러리

zipfile 같은 다른 아카이브 라이브러리가 향후 비슷한 기능을 갖추게 된다면, 그 추출 함수는 적어도 문자열 'fully_trusted' (모든 보안 예방 조치를 비활성화해야 합니다)와 'data' (사용자를 놀라게 할 수 있는 기능을 피해야 합니다)를 받는 filter 인자를 사용해야 합니다.

함수 기반 필터 API의 표준화는 이 PEP의 범위를 벗어납니다.

Shutil

shutil.unpack_archive()에 filter 인자가 추가됩니다. 이 인자가 주어지면 내부 추출 함수로 전달됩니다. zip 아카이브에 이 인자를 전달하면 당분간은 실패합니다(zipfile에 filter 인자가 추가될 때까지이며, 추가되지 않을 수도 있습니다).

filter를 지정하지 않으면(또는 None으로 두면) 이 인자는 전달되지 않으므로, tarball을 추출할 때 기본 필터가 사용됩니다(현재는 폐지 경고와 함께 'fully_trusted'가, Python 3.14부터는 'data'가 사용됩니다).

복잡한 필터

예를 들어 일부 사용자 정의 필터는 추출된 멤버의 수를 세거나 후처리를 수행해야 한다는 점에 유의하십시오. 이를 위해서는 filter 호출 가능 객체보다 더 복잡한 API가 필요합니다. 그러나 그 복잡한 API를 tarfile에 노출할 필요는 없습니다. 예를 들어 가상의 StatefulFilter가 있다면 사용자는 다음과 같이 작성하게 됩니다:

with StatefulFilter() as filter_func:
    my_tar.extract(path, filter=filter_func)

간단한 StatefulFilter 예제가 문서에 추가될 것입니다.

Note

상태를 갖는 필터가 필요하다는 점은 'fully_trusted', 'tar', 'data' 외에 사용자 정의 필터 이름의 등록을 허용하지 않는 이유 중 하나입니다. 그러한 메커니즘이 있다면 (최소한) 설정과 정리를 위한 API를 확정해 두어야 할 것입니다.

하위 호환성

TarFile.extract와 TarFile.extractall의 기본 동작은 2개 릴리스 동안 DeprecationWarning을 발생시킨 후 변경됩니다(Python의 하위 호환성 정책에서 허용하는 가장 짧은 폐지 기간입니다).

또한 tarfile.TarInfo 객체의 동일성에 의존하는 코드는 깨질 수 있습니다. TarInfo 동일성, 그리고 offset을 참고하십시오.

백포팅과 상위 호환성

이 기능은 이전 버전의 Python으로 백포트될 수 있습니다.

CPython에서는 패치 릴리스에 경고를 추가하지 않으므로, 백포트에서는 기본 필터를 'fully_trusted'로 변경해야 합니다.

그 외에는 tarfile에 대한 변경 사항을 모두 백포트해야 하며, 그래야 hasattr(tarfile, 'data_filter')가 모든 새 기능을 확인하는 신뢰할 수 있는 수단이 됩니다.

CPython의 일반적인 정책은 보안 백포트에서 새로운 API를 추가하지 않는 것이라는 점에 유의하십시오. 이 기능은 새로운 API(TarFile.extraction_filter와 filter 인자) 없이는 의미가 없으므로 예외로 합니다. (자세한 내용은 Discourse 댓글 23149/16을 참고하십시오.)

다음은 tarfile에 제안된 기능이 있을 수도 있고 없을 수도 있다는 점을 고려한 코드 예제입니다.

이 코드 조각을 복사할 때는 extraction_filter를 설정하면 이후의 작업에 영향을 준다는 점에 유의하십시오.

  • 완전히 신뢰하는 아카이브:
    my_tarfile.extraction_filter = (lambda member, path: member)
    my_tarfile.extractall()
    
  • 가능하면 'data' 필터를 사용하되, 이 기능을 사용할 수 없으면 Python 3.11의 동작('fully_trusted')으로 되돌아갑니다:
    my_tarfile.extraction_filter = getattr(tarfile, 'data_filter',
                                           (lambda member, path: member))
    my_tarfile.extractall()
    

    (이는 안전하지 않은 작업이므로 명시적으로, 가능하면 주석과 함께 작성해야 합니다.)

  • 'data' 필터를 사용하고, 사용할 수 없으면 실패합니다:
    my_tarfile.extractall(filter=tarfile.data_filter)
    

    또는:

    my_tarfile.extraction_filter = tarfile.data_filter
    my_tarfile.extractall()
    
  • 'data' 필터를 사용하고, 사용할 수 없으면 경고합니다:
    if hasattr(tarfile, 'data_filter'):
        my_tarfile.extractall(filter='data')
    else:
        # remove this when no longer needed
        warn_the_user('Extracting may be unsafe; consider updating Python')
        my_tarfile.extractall()
    

보안 영향

이 제안은 하위 호환성을 희생하는 대신 보안을 향상합니다. 특히 사용자가 CVE-2007-4559를 피하는 데 도움이 됩니다.

교육 방법

API, 사용 시 참고 사항, 추가 검증을 위한 팁이 문서에 추가될 것입니다. 이 내용은 아카이브 전반에는 익숙하지만 UNIX 파일 시스템의 세부 사항이나 관련 보안 문제에는 익숙하지 않은 사용자도 활용할 수 있어야 합니다.

참조 구현

GitHub의 pull request #102953을 참조하십시오.

기각된 아이디어

SafeTarFile

Lars Gustäbel의 초기 아이디어는 보안 검사를 구현하는 별도의 클래스를 제공하는 것이었습니다(gh-65308 참조). 이 접근 방식에는 두 가지 주요 문제가 있습니다.

  • 이름이 오해의 소지가 있습니다. 일반적인 아카이브 작업은 정당한 사용 사례에 영향을 주지 않고서는 모든 종류의 원치 않는 동작으로부터 결코 “안전”하게 만들 수 없습니다.
  • 안전하지 않은 기본값 문제를 해결하지 못합니다.

그러나 SafeTarFile의 바탕이 된 아이디어 중 상당수가 이 PEP에서 재사용되었습니다.

tarfile에 absolute_path 옵션 추가

이슈 gh-73974는 추출 메서드에 absolute_path 옵션을 추가할 것을 요청합니다. 이는 CVE-2007-4559를 공식적으로 해결하기 위한 최소한의 변경일 것입니다. 이것만으로는 잘 모르는 사람을 보호하기에도, 성실하고 호기심 많은 사람에게 힘을 실어 주기에도 충분하지 않습니다.

'tar' 필터의 다른 이름

'tar' 필터는 UNIX 계열 파일 시스템에 특화된 기능을 노출하므로, 'unix'라는 이름을 붙일 수도 있습니다. 아니면 'unix-like', 'nix', '*nix', 'posix'는 어떻습니까?

기능 면에서 tar 형식과 UNIX 계열 파일 시스템은 본질적으로 동등하므로, tar는 좋은 이름입니다.

가능한 향후 작업

zipfile과 shutil.unpack_archive에 필터 추가

일관성을 위해 zipfile과 shutil.unpack_archive()도 filter 인자를 지원하게 될 수 있습니다. 그러나 이를 위해서는 이 PEP의 저자가 Python 3.12까지 약속할 수 없는 조사가 필요합니다.

zipfile용 필터는 아마 보안에 도움이 되지 않을 것입니다. Zip은 주로 크로스 플랫폼 데이터 묶음에 사용되며, 그에 맞게 ZipFile.extract의 기본값은 이미 'data' 필터가 하는 것과 비슷합니다. 절대 경로와 .. 경로 구성 요소를 새로 허용하는 'fully_trusted' 필터는 통합된 unpack_archive API 외에는 별로 쓸모가 없을 수 있습니다.

필터는 보안 이외의 사용 사례에도 유용해야 하지만, 그런 경우에는 대개 사용자 정의 필터 함수가 필요하고, 그 함수에는 TarInfo와 ZipInfo 모두에서 동작하는 API가 필요합니다. 그것은 분명히 이 PEP의 범위를 벗어납니다.

이 PEP만 구현되고 zipfile에는 아무 변화가 없다면, unpack_archive 호출자에게 미치는 영향은 tar 파일의 기본값이 'fully_trusted'에서 더 적절한 'data'로 바뀐다는 것입니다. 과도기 동안 Python 3.12-3.13은 DeprecationWarning을 발생시킵니다. 번거롭기는 하지만 이를 처리하는 방법은 여러 가지가 있습니다. 예를 들어 filter 인자를 조건부로 추가하거나, TarFile.extraction_filter를 전역으로 설정하거나, Python 3.14까지 경고를 무시하거나 억제할 수 있습니다.

또한 unpack_archive 호출 중 상당수는 안전하지 않을 가능성이 높으므로, DeprecationWarning이 영향을 받는 코드를 검토하라는 유용한 힌트가 되는 경우가 많기를 기대합니다.

감사의 말

이 제안은 여러 사람이 앞서 진행한 작업과 논의를 바탕으로 합니다. 특히 Lars Gustäbel, Gregory P. Smith, Larry Hastings, Joachim Wagner, Jan Matejek, Jakub Wilk, Daniel Garcia, Lumír Balhar, Miro Hrončok을 비롯한 많은 분들께 빚지고 있습니다.

참고 문헌