Новое в Python 3.12
Формат tar
предназначен для отображения всех деталей UNIX-подобной файловой системы, что делает его очень мощным. К сожалению, эти функции позволяют легко создавать tar
–файлы, которые при извлечении имеют непреднамеренные и, возможно, вредоносные последствия. Например, извлечение файла tar
может перезаписать произвольные файлы различными способами (например, с помощью абсолютных путей, компонентов ..
path или символических ссылок, которые влияют на более поздние элементы).
В большинстве случаев полная функциональность не требуется. Таким образом, tarfile
поддерживает фильтры извлечения: механизм ограничения функциональности и, таким образом, устранения некоторых проблем безопасности.
Фильтры извлечения были добавлены в Python 3.12, но могут быть перенесены в более старые версии в качестве обновлений безопасности. Чтобы проверить, доступна ли эта функция, вместо проверки версии Python используйте
hasattr(tarfile, 'data_filter')
.
Аргументом фильтра для TarFile.extract()
или TarFile.extractall()
может быть:
строка 'fully_trusted'
: соблюдает все метаданные, указанные в архиве. Следует использовать, если пользователь полностью доверяет архиву или реализует свою собственную комплексную проверку.
строка 'tar'
: учитывает большинство функций, специфичных для tar
(т. е. функций UNIX-подобных файловых систем), но блокирует функции, которые с большой вероятностью могут оказаться неожиданными или вредоносными. Подробности смотрите в tarfile.tar_filter()
.
строка 'data'
: игнорировать или блокировать большинство функций, специфичных для UNIX-подобных файловых систем. Предназначен для извлечения кроссплатформенных архивов данных. Подробности смотрите в tarfile.data_filter()
.
None
(по умолчанию): использует TarFile.extraction_filter
.
Если это значение также равно None
(по умолчанию), то поднимается исключение DeprecationWarning
и возвращается к фильтру 'fully_trusted'
, опасное поведение которого соответствует предыдущим версиям Python.
В Python 3.14 по умолчанию будет использоваться фильтр 'data'
. Можно переключиться и раньше; смотрите описание TarFile.extraction_filter
.
Вызываемый объект, который будет вызываться для каждого извлеченного элемента с tarfile.TarInfo
, описывающим элемент и путь назначения, куда извлекается архив (т. е. один и тот же путь используется для всех членов):
filter(member: TarInfo, path: str, /) -> TarInfo | None
Вызываемый объект вызывается непосредственно перед извлечением каждого члена, поэтому он может учитывать текущее состояние диска.
Он может:
tarfile.TarInfo
, который будет использоваться вместо метаданных в архиве, илиNone
, и в этом случае элемент будет пропущен, илиTarFile.errorlevel
. Обратите внимание: когда извлечение прерывается, TarFile.extractall()
может оставить архив частично извлеченным.import tarfile tar = tarfile.open("sample.tar.gz") tar.extractall(filter='data') tar.close() # Эквивалентно with tarfile.open("sample.tar.gz") as tar: tar.extractall(filter='data')
Предопределенные именованные фильтры доступны как функции, поэтому их можно повторно использовать в пользовательских фильтрах:
tarfile.fully_trusted_filter(member, path)
:Фильтр tarfile.fully_trusted_filter()
возвращает элемент member
без изменений. Это реализует фильтр 'fully_trusted'.
tarfile.tar_filter(member, path)
:Фильтр tarfile.tar_filter()
реализует фильтр 'tar'
. Удаляет начальные косые черты (/
и os.sep
) из имен файлов.
C:/foo
в Windows). Это вызывает исключение AbsolutePathError
.path
. Это вызывает ошибку OutsideDestinationError
.(setuid, setgid, sticky)
и групповые/другие биты записи (S_IWOTH
).Возвращает измененный элемент tarfile.TarInfo.
tarfile.data_filter(member, path)
:Фильтр tarfile.data_filter()
реализует фильтр 'data'
. В дополнение к тому, что делает tarfile.tar_filter()
:
Отказывается извлекать ссылки (жесткие или программные), которые ссылаются на абсолютные пути, или те, которые ссылаются за пределы path
. Это вызывает ошибку AbsoluteLinkError
или LinkOutsideDestinationError
.
Обратите внимание, что такие файлы отклоняются даже на платформах, которые не поддерживают символьные ссылки.
Отказывается извлекать файлы устройств (включая каналы). Это вызывает SpecialFileError
.
Для обычных файлов, включая жесткие ссылки:
S_IWUSR
).S_IXOTH
), если у владельца его нет (S_IXUSR
).Для других файлов (каталогов) устанавливает значение mode=None
, чтобы методы извлечения не применяли биты разрешений.
Устанавливает для информации о пользователе и группе (uid, gid, uname, gname)
значение None
, чтобы методы извлечения их не устанавливали.
Возвращает измененный элемент tarfile.TarInfo.
Когда фильтр отказывается извлечь файл, он выдает соответствующее исключение - подкласс FilterError
.
Если TarFile.errorlevel
равен 1 или более, то это прервет извлечение файлов из архива. При errorlevel=0
ошибка будет зарегистрирована и элемент будет пропущен, но извлечение продолжится.
Даже фильтр filter='data'
не подходит для извлечения ненадежных файлов без предварительной проверки. Помимо прочего, предопределенные фильтры не предотвращают атаки типа "отказ в обслуживании". Пользователям следует выполнить дополнительные проверки.
Вот неполный список вещей, на которые стоит обратить внимание:
Также обратите внимание, что:
tar
могут содержать несколько версий одного и того же файла. Ожидается, что более поздние записи перезапишут более ранние. Эта функция имеет решающее значение для обновления ленточных архивов, но ею можно злоупотреблять.tarfile
не защищает от проблем с "живыми" данными, например. злоумышленник возится с каталогом назначения (или исходным каталогом) во время извлечения (или архивирования).В следующих примерах показано, как поддерживать версии Python с этой функцией и без нее. Обратите внимание, что установка 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()
Хотя методы извлечения tar-файла используют простой вызываемый фильтр, пользовательские фильтры могут представлять собой более сложные объекты с внутренним состоянием. Их полезно писать как менеджеры контекста, чтобы потом использовать следующим образом:
with StatefulFilter() as filter_func: tar.extractall(path, filter=filter_func)
Такой фильтр можно написать как-то так:
class StatefulFilter: def __init__(self): self.file_count = 0 def __enter__(self): return self def __call__(self, member, path): self.file_count += 1 return member def __exit__(self, *exc_info): print(f'{self.file_count} files extracted')