karlley's tech blog

学習メモや記録とか

ディレクトリ・トラバーサルとは

Sinatraのメモアプリのプルリクでディレクトリ・トラバーサルの危険性があると指摘されました。 初めて聞く言葉だったのでディレクトリ・トラバーサルについて調べました。

結論

ウェブアプリケーションの中には、外部からのパラメータにウェブサーバ内のファイル名を直接指定しているものがあります。このようなウェブアプリケーションでは、ファイル名指定の実装に問題がある場合、攻撃者に任意のファイルを指定され、ウェブアプリケーションが意図しない処理を行ってしまう可能性があります。このような問題の一種を「ディレクトリ・トラバーサルの脆弱性」と呼び、この問題を悪用した攻撃手法の一つに、「ディレクトリ・トラバーサル攻撃」があります。

パス名パラメータを悪用してファイル操作を行う攻撃のようです。

ディレクトリ・トラバーサルが発生しやすいwebアプリの特徴

外部パラメータにwebサーバ内のファイル名を直接指定している。

対策

  • 外部パラメータにwebサーバ内のファイル名を直接指定する仕様を避ける。
  • ファイルを開く際は、固定のディレクトリを指定し、かつファイル名にディレクトリ名が含まれないようにする。

保険的な対策

  • ウェブサーバ内のファイルへのアクセス権限の設定を正しく管理する。
  • ファイル名の指定時にディレクトリを指定できる文字列が含まれていないかチェックする。

ディレクトリ・トラバーサルをやってみる

悲しいことに今回作成したアプリの仕様はまさにディレクトリ・トラバーサルが起こりうる仕様になっていました... せっかくなので作成したアプリで実際にディレクトリ・トラバーサルを試してみます。

リポジトリ: karlley/sinatra-memo: メモを管理できる簡易的なアプリケーション

# 対象のコード

patch '/memos/:id' do |id|
  update_data = { id: id.to_s, title: title_with_default_text, content: params[:content] }
  File.open("data/#{id}.json", 'w') { |file| JSON.dump(update_data, file) }
  redirect "/memos/#{id}"
  erb :show
end
  1. メモの編集画面でDevToolsを開く。
  2. formタグのaction要素のパスを任意の文字列に変更します。(今回は分かりやすくmemos/directory_traversal に変更)
  3. submitボタンをクリックすると任意の文字列を元にした新たなファイルが作成されてしまいます。(今回の場合はmomos/directory_traversal.json )

このように簡単に攻撃を受けてしまいました。

今回の場合のディレクトリ・トラバーサルの対策

本来であれば「外部パラメータにwebサーバ内のファイル名を直接指定する仕様を避ける」という対策が一番良さそうでしたが、ほぼ作り直しになりそうだったので簡易的に対策を行ってみました。対策後のレビューと確認はまだ終わっていないので間違っているかもしれません。

helperにmemo_exists? メソッドを作成し、作成済みのファイルかどうか判断する処理を追加しました。

# helpers/crud_helper.rb

helpers do
  def memo_exists?(id)
    all_ids = Dir.glob('data/*.json').map do |file|
      JSON.parse(File.read(file), symbolize_names: true)[:id]
    end
    all_ids.include?(id)
  end
end

作成したhelperをrequire_relative で読み込み、作成済みのファイルの場合のみupdateの処理が走るように修正しました。作成済みのファイルでない場合は事前に作成しておいたnot_found.erb を表示するように分岐しています。

require_relative 'helpers/crud_helper'

patch '/memos/:id' do |id|
  if memo_exists?(id)
    update_data = { id: id.to_s, title: title_with_default_text, content: params[:content] }
    File.open("data/#{id}.json", 'w') { |file| JSON.dump(update_data, file) }
    redirect "/memos/#{id}"
    erb :show
  else
    erb :not_found
  end
end

上記の修正を追加したことで未作成のファイルへのupdateの処理を防止することができました。

まとめ

ディレクトリ・トラバーサルという攻撃を知る良い機会になって良かったです。 やはりセキュリティは奥が深くとても難しい。 とにかく場数を踏んで色々と覚えていくしかなさそうです。

参照

安全なウェブサイトの作り方 - 1.3 パス名パラメータの未チェック/ディレクトリ・トラバーサル:IPA 独立行政法人 情報処理推進機構

hawksnowlog: Sinatra の Rack::Protection で有効になっているパストラバーサルの機能を無効にしてどのような影響があるのか確認してみた