29

ユーザーが送信した zip および tar ファイルをディレクトリに抽出しようとしています。zipfile のextractallメソッドのドキュメント (tarfile の extractall と同様)には、パスが絶対..パスであるか、宛先パスの外にあるパスを含む可能性があると記載されています。代わりに、次extractのように自分自身を使用できます。

some_path = '/destination/path'
some_zip = '/some/file.zip'
zipf = zipfile.ZipFile(some_zip, mode='r')
for subfile in zipf.namelist():
    zipf.extract(subfile, some_path)

これは安全ですか?some_pathこの場合、アーカイブ内のファイルが外部に巻き上げられる可能性はありますか? もしそうなら、ファイルが宛先ディレクトリの外に出ないようにするにはどうすればよいですか?

4

4 に答える 4

44

注: Python 2.7.4 以降では、これは ZIP アーカイブの問題ではありません。回答の下部に詳細があります。この回答は、tar アーカイブに焦点を当てています。

パスが実際に指している場所を把握するには、を使用しますos.path.abspath()(ただし、パス コンポーネントとしてのシンボリック リンクに関する警告に注意してください)。zipfile からのパスを正規化し、プレフィックスとして現在のディレクトリが含まれabspathていない場合は、その外側を指しています。

ただし、アーカイブから抽出されたシンボリック リンクの値も確認する必要があります (tarfile と unix zip ファイルの両方にシンボリック リンクを格納できます)。これは、システム ライブラリに自分自身をインストールするだけのアプリケーションではなく、意図的にセキュリティをバイパスすることわざのような「悪意のあるユーザー」を心配している場合に重要です。

これは前述の注意事項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 共有ポイントと先頭の (バック) スラッシュは取り除かれます。たとえば、Unix では に///foo/barなり、Windows ではになります。また、メンバー ファイル名のすべてのコンポーネントが削除されます。たとえば、 になります。Windows では、不正な文字 ( 、、、、、および) [は] アンダースコア (_) に置き換えられます。foo/barC:\foo\barfoo\bar".."../../foo../../ba..rfoo../ba..r:<>|"?*

クラスはtarfile同様にサニタイズされていないため、上記の回答は引き続き適用されます。

于 2012-04-09T17:44:16.187 に答える
3

zip ファイルを空のディレクトリにコピーします。次に、 を使用os.chrootしてそのディレクトリをルート ディレクトリにします。そこで解凍します。

または、ディレクトリを無視するフラグを使用しunzipて自分自身を呼び出すこともできます。-j

import subprocess
filename = '/some/file.zip'
rv = subprocess.call(['unzip', '-j', filename])
于 2012-04-15T11:57:50.483 に答える
3

ZipFile.infolist()//を使用してアーカイブ内の各エントリに関する情報を取得し、パスを正規化し、自分でファイルを開き、/TarFile.next()を使用してエントリのファイルのようなものを取得し、エントリ データを自分でコピーします。TarFile.getmembers()ZipFile.open()TarFile.extractfile()

于 2012-04-08T03:19:54.560 に答える
3

一般的な回答とは反対に、ファイルを安全に解凍することは、Python 2.7.4 の時点では完全には解決されていません。extractall メソッドは依然として危険であり、直接またはシンボリック リンクの解凍を通じて、パス トラバーサルにつながる可能性があります。これは、extract メソッドが脆弱であった Python 2.7.4 より前のバージョンであっても、Python のすべてのバージョンで両方の攻撃を防ぐ必要がある私の最終的な解決策です。

import zipfile, os

def safe_unzip(zip_file, extract_path='.'):
    with zipfile.ZipFile(zip_file, 'r') as zf:
        for member in zf.infolist():
            file_path = os.path.realpath(os.path.join(extract_path, member.filename))
            if file_path.startswith(os.path.realpath(extract_path)):
                zf.extract(member, extract_path)

編集 1:変数名の競合を修正しました。ありがとうございます。

編集 2: s/abspath/realpath/g . ありがとう

于 2016-04-12T20:53:56.830 に答える