flawopen.com/パストラバーサル/Python os.path.join()
Python の os.path.join() はパストラバーサルを防げますか?
CWE-22: Path Traversalリファレンスページ
要点
いいえ。ベースを部屋とすると、.. は親の部屋を通って外へ進み、絶対パスは新しい住所なので os.path.join() は最初の部屋を破棄します。開く前に最終パスを解決し、許可ディレクトリ内にあるか確認してください。
VULNERABLE
import os base_dir = "/srv/uploads" user_filename = "../../outside/demo.txt" path = os.path.join(base_dir, user_filename) # Relative traversal can leave the base. path = os.path.join(base_dir, "/outside/demo.txt") # An absolute segment discards base_dir.
SECURE — resolve and check containment
import os
def resolve_under_base(base_dir, user_filename):
if not isinstance(user_filename, str) or not user_filename:
raise ValueError("Invalid filename")
base = os.path.realpath(base_dir)
candidate = os.path.realpath(
os.path.join(base, user_filename)
)
try:
inside = os.path.commonpath((base, candidate)) == base
except ValueError: # e.g. different Windows drives
inside = False
if not inside or candidate == base:
raise ValueError("Invalid filename")
return candidate
考慮すべき2つのパス結合動作
.. は相対パスを想定外のディレクトリへ導きます。Python の説明では、絶対パス要素があると os.path.join() は前の要素を破棄します。Windows のドライブとルート規則は POSIX と異なります。pathlib.Path(base) / user_input も同様です。結合や正規化は認可ではありません。
commonpath() が文字列 prefix より安全な理由
比較前に信頼できるベースと候補を解決します。os.path.commonpath() は文字列ではなくパス要素を比較し、互換性のないルートやドライブで ValueError を送出するため拒否します。realpath() は検査時点のリンクを解決しますが、その後の変更は防ぎません。ツリーを変更できる第三者がいる場合は適切なディスクリプターアクセスと no-follow を使います。
- ✓空のパスや文字列以外の入力を拒否します。
- ✓ベースと候補を解決し、
commonpath((base, candidate)) == baseを要求します。ValueErrorを拒否し、ファイルが必要ならベース自体も拒否します。 - ✓範囲確認と認可を分け、必要なファイル形式と操作だけを許可します。
- ✓リンクを変更できる利用者がいる場合、適切なディスクリプター API で検査と使用の競合を防ぎます。
よくある質問
pathlib も同じ動作ですか?
はい。Path の / は次のパスが絶対パスなら前の要素を破棄します。結果を解決し、許可ベース内か確認します。
realpath() で競合状態を防げますか?
いいえ。検証時点のリンクを解決するだけで、別プロセスがファイルを開く前に変更できます。書き込みを制限するか、対応環境では no-follow のディスクリプター相対操作を使います。