全ての入力データはバリデーション済み(またはバリデーション済みと信頼可能)であるため出力時にバリデーションを行うことに抵抗を感じる(≒ 省略したくなる)方も多いと思います。「同じ、ほぼ同じような処理を繰り返したくない」と感じるのは普通の開発者の感覚でしょう。
そこで「ファイルパスを安全に出力する方法」を考えてみます。
※ ここではUNIX系OSのファイルシステムを前提とします。安全なファイルパス出力からセキュアコーディングの考え方を紹介しています。
ファイルパスへのインジェクション攻撃
ファイルパスへの攻撃は概ね
- ディレクトリトラバーサル – 相対パス指定が可能な脆弱性を利用した不正ファイルパスアクセス
- フルパス指定 – フルパスが指定が可能な脆弱性を利用した不正ファイルパスアクセス
の2つに分類できます。これらもインジェクション攻撃の一種と考えることができます。
大抵の場合、ファイルパスへの攻撃は相対パス指定によるディレクトリトラバーサルです。例:/path/set/by/program/../../../../etc/passwd
一般にプログラム中でファイルパスを指定する場合、完全にパス(=フルパス)を指定できるケースはほぼありません。これは誰が見ても明らかに危険だからです。例:/etc/passwd
ファイルパスインジェクションの防止
話を簡単にするため、ファイル名のみがユーザー入力だとします。
ディレクトリトラバーサルでもフルパス指定でも、必ずディレクトリ区切り文字(この場合は / )が必要になります。ディレクトリ区切り文字さえ安全に処理できればファイルパスインジェクションを防止できます。
実際に確認してみましょう。
php > $f = '../../etc/passwd';
php > var_dump(file_get_contents($f), $f);
string(4696) "root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
以下省略
多くのファイルシステムではヌル文字以外の全てをファイル名に使用可能ですが、今使っているファイルシステムがbtrfsで”/”は禁止文字です。エスケープの代わりに”_”に置換することにします。(”/”を含むファイル名を作れる場合でも、シェルからの削除が難しくなるので、作成はあまりお勧めしません)
php > $f = '../../etc/passwd';
php > $f = strtr($f, '/', '_');
php > var_dump($f, file_get_contents($f));
PHP Warning: file_get_contents(.._.._etc_passwd): failed to open stream: No such file or directory in php shell code on line 1
string(16) ".._.._etc_passwd"
bool(false)
ファイルパスが”.._.._etc_passwd”になり、当然ですが/etc/passwdにはアクセスできなくなります。
取り敢えずインジェクション攻撃が出来ない、で良いのか?
これを見て「これで完璧で安全!」と思う開発者は居ないと思います。先の例では読み取りだったので、失敗しましたがファイル作成の場合は「おかしなファイル名」が出来てしまいます。
「取り敢えずパスとして正しい形に変換/処理する」を行ってしまうと
- HTMLを含んだファイル名を作られ、それをそのまま表示してしまうコードでHTMLインジェクションが可能となる
- JavaScriptを含んだファイル名を作られ…
- SQLを含んだファイル名を作られ…
- 以下省略
パスにインジェクションできなくても「おかしなファイル名」のファイルが出来てしまうのは問題である、と直ぐに解ります。
「 取り敢えずインジェクション攻撃が出来ない」は「おかしな対策」で危険な対策だと言えます。
同じく「おかしな対策」がSQLやHTMLのインジェクション対策では当たり前!!
パス名だと直感的に「おかしな対策」だと理解ります。しかし、SQLやHTMLの場合は直観的に理解らないケースが少なくありません。
- SQLインジェクション対策にはプリペアードクエリだけ使っていれば完璧!
- HTML/JavaScriptインジェクション対策には適切なエスケープだけ行っていれば完璧!
といった不十分で危険なセキュリティ対策が、まるで「完全無欠のセキュリティ対策」であるかのように解説されている(されていた?)ケースは少くありません。
“おかしなデータ”を取り敢えずその場限りでインジェクション攻撃できないようにしても「おかしな対策」にしかなりません。
結局どうすれば良いのか?
コンピューターサイエンティストが四半世紀、半世紀以上前からこうすべきと助言しているのは
です。コンピューターは妥当なデータでしか正しく動作できないので、先ずその前提条件を満たさないとなりません。
出力データのエスケープやサニタイズでは全然ダメ、バリデーションしても出力時点では遅過ぎです。 出力対策だけでなんとかしよう、とするのは構造的な間違いです。
”出力対策だけで何とかしよう”とするのは、プログラミングの基本原則であるフェイルファースト(失敗するモノはできる限り早く失敗させる)に反しています。基本構造から間違っているのです。
残念ながらソフトウェアセキュリティ対策として基礎の基礎から間違っている方法論や対策がまかり通っているのが現状です。
IPAさえ基礎から間違った出鱈目なセキュリティ対策を10年以上も啓蒙してきました。一般開発者が勘違いするのは仕方ないでしょう。大間違いのセキュリティ対策を啓蒙してきた、セキュリティ専門家とされる人達の責任は大きいです。
出力時のセキュリティ対策も必須です。妥当な出力データが出力先を誤作動させるデータであることも普通にあります。入力データバリデーションに不備がある場合もあります。”最低限の対策”として誤作動だけは防ぐ対策も、フェイルセーフ対策として必要です。
出力対策には大別して3つの方法があり、入力データバリデーションも「絶対に欠かせない」出力対策の1つ(出力対策の前提条件)と考えることが出来ます。
出力対策の3原則
- エスケープ
- エスケープをせずに出力できるAPI
- バリデーション(1も2も使えない場合に利用)
+1の原則(=出力対策の前提条件)
- 入力処理/ロジック処理のデータバリデーション
出力対策だけでセキュリティ対策が完結することはありません。出力対策だけで完璧なセキュリティ対策ができる、とする意見は全て間違いです。
※ 出鱈目な構造のソフトウェアなら出力対策だけでも対策可能ですが、gotoだらけのスパゲティコードのようになります。構造が出鱈目になるので間違いです。
結局の所、出力対策も可能な限りバリデーションで対策するのが最もセキュアなセキュリティ対策になります。
ファイルパスは解りやすい例です。エスケープしても、エスケープ無しに出力できるAPIを使っても、「おかしな出力(=ファイル名)」はおかしなままです。どの出力先でも同じです。
まとめ
- セキュリティ専門家でも出鱈目で危険なソフトウェアセキュリティ対策を啓蒙していた。例)IPA、おかしなセキュアコーディングの解説
- 出力時点でデータバリデーションを行っても、手遅れであるケースが大半である。
- 入力/ロジックでのデータバリデーションがないと十分に安全なコードには絶対にならない。
そもそも、コンピュータープログラムは”妥当なデータ”でしか正しく動作できないです。先ずデータの妥当性保証が必要です。これが無いと始まりません。
参考: