Ответ 1
Примечание.. Начиная с python 2.7.4, это не проблема для ZIP-архивов. Подробности в нижней части ответа. Этот ответ посвящен архивам tar.
Чтобы выяснить, на что указывает путь, используйте os.path.abspath()
(но обратите внимание на оговорку о символических ссылках в качестве компонентов пути). Если вы нормализуете путь из своего zip файла с помощью abspath
и он не содержит текущий каталог в качестве префикса, он указывает на него.
Но вам также нужно проверить значение любой символической ссылки, извлеченной из вашего архива (оба файла tarfiles и unix zipfiles могут хранить символические ссылки). Это важно, если вас беспокоит пресловутый "злонамеренный пользователь", который намеренно обошел вашу безопасность, а не приложение, которое просто устанавливает себя в системных библиотеках.
Чтобы упомянутое выше оговорка: abspath
будет введена в заблуждение, если ваша песочница уже содержит символическую ссылку, указывающую на каталог. Даже символическая ссылка, указывающая в песочнице, может быть опасной: символическая ссылка sandbox/subdir/foo -> ..
указывает на sandbox
, поэтому путь sandbox/subdir/foo/../.bashrc
должен быть запрещен. Самый простой способ сделать это - подождать, пока не будут извлечены предыдущие файлы, и используйте os.path.realpath()
. К счастью, extractall()
принимает генератор, поэтому это легко сделать.
Поскольку вы запрашиваете код, вот немного, что объясняет алгоритм. Он запрещает не только извлечение файлов в местах за пределами песочницы (это то, что было запрошено), но и создание ссылок внутри песочницы, указывающих на места вне песочницы. Мне любопытно услышать, может ли кто-нибудь прокрасться в какие-нибудь бездомные файлы или ссылки.
import tarfile
from os.path import abspath, realpath, dirname, join as joinpath
from sys import stderr
resolved = lambda x: realpath(abspath(x))
def badpath(path, base):
# joinpath will ignore base if path is absolute
return not resolved(joinpath(base,path)).startswith(base)
def badlink(info, base):
# Links are interpreted relative to the directory containing the link
tip = resolved(joinpath(base, dirname(info.name)))
return badpath(info.linkname, base=tip)
def safemembers(members):
base = resolved(".")
for finfo in members:
if badpath(finfo.name, base):
print >>stderr, finfo.name, "is blocked (illegal path)"
elif finfo.issym() and badlink(finfo,base):
print >>stderr, finfo.name, "is blocked: Hard link to", finfo.linkname
elif finfo.islnk() and badlink(finfo,base):
print >>stderr, finfo.name, "is blocked: Symlink to", finfo.linkname
else:
yield finfo
ar = tarfile.open("testtar.tar")
ar.extractall(path="./sandbox", members=safemembers(ar))
ar.close()
Изменить: Начиная с python 2.7.4, это не проблема для ZIP-архивов: метод zipfile.extract()
запрещает создание файлов вне песочницы:
Примечание. Если имя элемента-члена является абсолютным путем, разделительная точка диска /UNC и ведущая (обратная) слэши будут удалены, например:
///foo/bar
становитсяfoo/bar
в Unix иC:\foo\bar
становитсяfoo\bar
в Windows. И все компоненты".."
в имени элемента-члена будут удалены, например:../../foo../../ba..r
станетfoo../ba..r
. В Windows недопустимые символы (:
,<
,>
,|
,"
,?
и*
)) заменены символом подчеркивания (_).
Класс tarfile
не был подобным образом дезинфицирован, поэтому приведенный выше ответ все еще сохраняется.