12

ディレクトリとその内容をユーザーに公開するファイル ブラウザ アプリケーションがあります。

ファイルパスであるユーザー入力をサニタイズして、「/tmp/」などの絶対パスと「../../etc」などの相対パスを許可しないようにしたい

プラットフォーム間でこれを行う python 関数はありますか?

4

6 に答える 6

21

A/./B -> A/Bまた、A/B/../C -> A/Cパスを取り除く方法を探している人にも。そのために使えますos.path.normpath

于 2016-05-13T08:31:56.803 に答える
10

Python 用の包括的なファイルパス サニタイザー

パスをサニタイズするために利用できる方法のどれにも満足できなかったので、独自の比較的包括的なパス サニタイザーを作成しました。これは、パブリック エンドポイント (http アップロード、REST エンドポイントなど) から入力を取得し、結果のファイル パスにデータを保存する場合にシステムに損傷を与えないことを保証するのに適しています**。(注: このコードは Python 3+ を対象としています。2.x で動作させるには、おそらくいくつかの変更を加える必要があります)

無保証!ご自身で十分に確認せずに、このコードに依存しないでください。

**繰り返しますが、保証はありません! あなたはまだ何かクレイジーなことをして、* nixシステムのルートパスを/dev/または/bin/そのようなものに設定することができます. そうしないでください。Windows には、損傷を引き起こす可能性のあるエッジ ケース (デバイス ファイル名など) もいくつかありsecure_filenameます。werkzeugutils

使い方

  • ルート パスを指定する必要があります。サニタイザーは、返されるすべてのパスがこのルートの下にあることを確認します。get_root_pathこれを行う場所については、関数を確認してください。ルート パスの値は、ユーザーからの入力ではなく、独自の構成からのものであることを確認してください。
  • ファイル名サニタイザーがあります。
    • Unicode を ASCII に変換します
    • パスの区切り記号をアンダースコアに変換します
    • ファイル名にホワイトリストの特定の文字のみを許可します。ホワイトリストには、すべての小文字と大文字、すべての数字、ハイフン、アンダースコア、スペース、開き括弧と閉じ括弧、ピリオド (ピリオド) が含まれます。必要に応じて、このホワイトリストをカスタマイズできます。
    • すべての名前に少なくとも 1 つの文字または数字が含まれていることを確認します (「..」のような名前を避けるため)。
  • 有効なファイル パスを取得するには、 を呼び出す必要がありますmake_valid_file_path。必要に応じて、パラメーターでサブディレクトリ パスを渡すことができpathます。これはルート パスの下のパスであり、ユーザー入力から取得できます。必要に応じて、パラメーターでファイル名を渡すことができfilenameます。これは、ユーザー入力から取得することもできます。渡すファイル名のパス情報は、ファイルのパスを決定するために使用されません。代わりに、ファイル名の有効で安全なコンポーネントにフラット化されます。
    • パスまたはファイル名がない場合は、ホスト ファイル システム用に正しくフォーマットされたルート パスが返され、末尾にパス セパレータ (/) が付きます。
    • サブディレクトリ パスが存在する場合は、それをコンポーネント パーツに分割し、それぞれをファイル名サニタイザーでサニタイズし、先頭のパス セパレーターなしでパスを再構築します。
    • ファイル名がある場合、サニタイザーで名前をサニタイズします。
    • ファイルへの最終的なパスを取得するos.path.joinパス コンポーネントになります。
    • 結果のパスが有効で安全であることの最終的な再確認として、結果のパスがルート パスの下のどこかにあることを確認します。このチェックは、ある文字列が別の文字列で始まることを確認するだけでなく、パスのコンポーネント部分を分割して比較することによって適切に行われます。

OK、十分な警告と説明があります。コードは次のとおりです。

import os

def ensure_directory_exists(path_directory):
    if not os.path.exists(path_directory):
        os.makedirs(path_directory)

def os_path_separators():
    seps = []
    for sep in os.path.sep, os.path.altsep:
        if sep:
            seps.append(sep)
    return seps

def sanitise_filesystem_name(potential_file_path_name):
    # Sort out unicode characters
    valid_filename = normalize('NFKD', potential_file_path_name).encode('ascii', 'ignore').decode('ascii')
    # Replace path separators with underscores
    for sep in os_path_separators():
        valid_filename = valid_filename.replace(sep, '_')
    # Ensure only valid characters
    valid_chars = "-_.() {0}{1}".format(string.ascii_letters, string.digits)
    valid_filename = "".join(ch for ch in valid_filename if ch in valid_chars)
    # Ensure at least one letter or number to ignore names such as '..'
    valid_chars = "{0}{1}".format(string.ascii_letters, string.digits)
    test_filename = "".join(ch for ch in potential_file_path_name if ch in valid_chars)
    if len(test_filename) == 0:
        # Replace empty file name or file path part with the following
        valid_filename = "(Empty Name)"
    return valid_filename

def get_root_path():
    # Replace with your own root file path, e.g. '/place/to/save/files/'
    filepath = get_file_root_from_config()
    filepath = os.path.abspath(filepath)
    # ensure trailing path separator (/)
    if not any(filepath[-1] == sep for sep in os_path_separators()):
        filepath = '{0}{1}'.format(filepath, os.path.sep)
    ensure_directory_exists(filepath)
    return filepath

def path_split_into_list(path):
    # Gets all parts of the path as a list, excluding path separators
    parts = []
    while True:
        newpath, tail = os.path.split(path)
        if newpath == path:
            assert not tail
            if path and path not in os_path_separators():
                parts.append(path)
            break
        if tail and tail not in os_path_separators():
            parts.append(tail)
        path = newpath
    parts.reverse()
    return parts

def sanitise_filesystem_path(potential_file_path):
    # Splits up a path and sanitises the name of each part separately
    path_parts_list = path_split_into_list(potential_file_path)
    sanitised_path = ''
    for path_component in path_parts_list:
        sanitised_path = '{0}{1}{2}'.format(sanitised_path, sanitise_filesystem_name(path_component), os.path.sep)
    return sanitised_path

def check_if_path_is_under(parent_path, child_path):
    # Using the function to split paths into lists of component parts, check that one path is underneath another
    child_parts = path_split_into_list(child_path)
    parent_parts = path_split_into_list(parent_path)
    if len(parent_parts) > len(child_parts):
        return False
    return all(part1==part2 for part1, part2 in zip(child_parts, parent_parts))

def make_valid_file_path(path=None, filename=None):
    root_path = get_root_path()
    if path:
        sanitised_path = sanitise_filesystem_path(path)
        if filename:
            sanitised_filename = sanitise_filesystem_name(filename)
            complete_path = os.path.join(root_path, sanitised_path, sanitised_filename)
        else:
            complete_path = os.path.join(root_path, sanitised_path)
    else:
        if filename:
            sanitised_filename = sanitise_filesystem_name(filename)
            complete_path = os.path.join(root_path, sanitised_filename)
        else:
            complete_path = complete_path
    complete_path = os.path.abspath(complete_path)
    if check_if_path_is_under(root_path, complete_path):
        return complete_path
    else:
        return None
于 2016-05-08T02:11:35.237 に答える
3

これにより、ユーザーは次のようなファイル名を入力../../../../etc/shadowできなくなりますが、以下のサブディレクトリ内のファイルも許可されませんbasedir(つまりbasedir/subdir/moredir、ブロックされます)。

from pathlib import Path
test_path = (Path(basedir) / user_input).resolve()
if test_path.parent != Path(basedir).resolve():
    raise Exception(f"Filename {test_path} is not in {Path(basedir)} directory")

以下のサブディレクトリを許可する場合basedir:

if not Path(basedir).resolve() in test_path.resolve().parents:
    raise Exception(f"Filename {test_path} is not in {Path(basedir)} directory")
于 2019-05-12T09:15:48.933 に答える