flawopen.com/path-traversal/Ruby
How File.join in Ruby leaves relative paths unresolved, and how to verify boundaries using File.expand_path.
ホテルの客室カードリーダーが2階の部屋のみを開けるよう制限されている場面を例えに考えてみてください。もし宿泊客がドアのキーパッドに「../../master-safe」と入力すると、不備のある鍵が廊下を抜け出して支配人の金庫を直接解錠してしまいます。
Web Application SecurityCWE-918.CWE-918CWE-918):Standard Common Weakness Enumeration classification for path-traversal-ruby.Defense-in-DepthエンドポイントがHTTPリクエストパラメータからユーザー指定のファイル名やパスを受け取ります。
攻撃者が '../' や '..%2f' などの相対パス記号や絶対パス指定を挿入します。
バックエンドが正規化パスの検証を行わずに文字列を連結し、公開フォルダ外へのアクセスを許容します。
OSが /etc/passwd や設定ファイルなどの重要ファイルを読み取り、攻撃者へ返却します。
# VULNERABLE: File.join does not resolve traversal dots
class DownloadsController < ApplicationController
BASE_DIR = Rails.root.join('storage', 'public').to_s
def show
# Attacker input: "../../config/master.key"
file_path = File.join(BASE_DIR, params[:file])
# Leaks private Rails master encryption key!
send_file file_path
end
end
# HARDENED: Canonicalize with File.realpath and enforce prefix
class DownloadsController < ApplicationController
BASE_DIR = File.realpath(Rails.root.join('storage', 'public').to_s)
def show
user_file = params[:file].to_s
return head :bad_request if user_file.blank?
# 1. Expand path relative to BASE_DIR
target_path = File.expand_path(user_file, BASE_DIR)
# 2. Strict boundary check with trailing separator
allowed_prefix = BASE_DIR.end_with?('/') ? BASE_DIR : "#{BASE_DIR}/"
unless target_path.start_with?(allowed_prefix)
return render plain: 'Forbidden: Path Traversal detected', status: :forbidden
end
return head :not_found unless File.file?(target_path)
send_file target_path
end
end