ぽかぽかコード日和

とっても暑い夏の日にプログラミングはじめました☀️

【Security】ディレクトリ・トラバーサル+Laravel実装

概要

ディレクトリ・トラバーサル攻撃とは、
外部からのパラメータをファイル名として直接指定しているWebアプリケーションで、
攻撃者が相対パスを使ってファイル名を指定し、非公開ファイルへアクセスする攻撃手法。


発生しうる脅威
  • 重要情報の漏洩 サーバーに格納されている重要な情報が盗まれる。企業の信頼を大きく損なうことにもつながる。

  • サーバー内ファイルの改ざん、削除
    ファイルやプログラムのソースコードの書き換え、作成、削除などをされ、サービスの信頼性が損なわれる。


$file = '/var/app/files/' . $data;
↓
$file = '/var/app/files/' . ../../../etc/passwd;

☀︎$dataに「../../../etc/passwd」と入力されることで、 上位ディレクトリに存在する「etc/passwd」ファイルを閲覧でき、パスワード情報などが盗まれる。

原因

ユーザーからの入力をそのままファイル名に指定する実装。

  • 任意のディレクトリ名やファイル名を指定できる状態になっている。
  • サーバー内にあるファイルへのアクセス権限を正しく管理していない。
  • ユーザーが入力した内容をチェックしていない。(パス名パラメーターの未チェック)

対策

根本的対策

1.ファイル名を直接指定する実装自体を避ける

仕様や設計から見直す。

2.固定のディレクトリを指定&ファイル名にディレクトリ名を含まないように実装する(basename関数)
$dirname = '/var/app/files/';
$filename = '$_GET['filename']';
//ファイルを開く処理

fopen($filename);
↓
fopen($dirname.basename($filename), ’rb’);

☀︎$dirnameで固定のディレクトリを指定、basename()でパス名からファイル名のみを取り出している。

  • 相対パス「../」や、絶対パス「/etc/passwd」を入力しても非公開のファイルまで辿り着けない。

保険的対策

3.ファイルのアクセス権限を適切に設定する

Webサーバー内のファイルやアプリケーション、ユーザーに、必要最低限の権限のみ付与する。
そのため攻撃者が不正のリクエストを送っても、その権限の範囲を超えるアクセスが拒否されたり、被害を最小限に抑えることができる。

4.ファイル名のチェックをする
  • ホワイトリスト方式によるバリデーション
    指定で許可する文字の組み合わせを決めておき、それ以外は許可しない方式。
    「数字とアルファベットのみからなる文字列であること」をチェックする。

  • エスケープ処理をする
    特別な意味を持つ文字を変換や無害化(サニタイジング)、除外する。
    ( /, .., \, |, <, > など)

  • ディレクトリ指定文字列をチェック
    ディレクトリ移動や指定をする文字列(.. , / など)が含まれていないかチェックし、含まれていればエラーとする。
    URLのときは、「../」をURLエンコーディングすると、「%2e%2e%2f」となる。
    さらにもう一度エンコーディングすると「%252e%252e%252f」となるため、
    複数回デコードをして「../」が含まれていないかチェックする。

5.WAF(Web Application Firewall)の導入

ディレクトリ・トラバーサルを含むさまざまな攻撃を検知して自動的にブロックする。
しかし誤検知(False Positive)のリスクがあり、また多重エンコードなど検知の隙間を突いた攻撃や未知の攻撃手法に対しては完全に防げないため、
他のセキュリティ対策も合わせて実施する。

6.サーバー上に必要のない非公開情報を置かない。

もし攻撃されてもファイル自体がないため情報が漏洩しない。

Laravelでの実装例

1.basename関数でファイル名のみ取得する

basename()は、引数のパスからファイル名だけを返してくれるPHPの関数。

2.バリデーション処理をする(validate関数)

外部からファイル名を入力されるところに

public function download (Request $request) {
    $request->validate([
        'filename' => ['required', 'regex:/^[a-zA-Z0-9_\-\.]+$/']
    ]);
}

☀︎英数字・アンダースコア・ハイフン・ピリオド以外があるときは通過できないようにする。

  • required属性で必須入力として、空欄であってもエラーとなる。

3.Storageファザードでファイルを操作する

Storage::get();を使ってファイルを取得する。


参考にしたサイト