2012年01月13日

ファイル名・パスの取り扱い

 今書いているスクリプトのファイルロック機構を作る上で、ファイル名・パスを一意で取得する必要があったためサブルーチンを作り始めたら、ちょっと横道に逸れたけど役に立ったのでメモ。
 個人的にスクリプト内部ではファイルをopenしたりする際には絶対パスを指定するのがよいと思います。ここでいう絶対パスというのは、"/"で始まり、"./"や"../"を含まないパスのこと。正規化というらしい。

 で、ユーザが入力した値(ユーザIDとかファイルナンバーとか)に基づいてファイルを扱う際に気をつけなければいけないのが、ディレクトリ・トラバーサル(cf.Wikipedia)です。ユーザから入力された値をチェックするのはまあ当然ですが、ファイルを取り扱う直前にもチェックを入れておきたいものです。



 ところで、ファイルパスを作るのに便利なモジュールがあります。File::Specです。(5.00405からコアモジュール)
相対パス←→絶対パスの相互変換、パスの分解などを行えるのですが、Windows,Unix環境等で共通のインターフェースを使えます。
 で、これだけ便利なモジュールでコアモジュールにもなっているんだから、絶対パスにしたときに正規化もしれくれるのかな・・・と思ったらだめだった。

 パスの正規化によるディレクトリ・トラバーサル対策については、IPAが具体的なコードを挙げて紹介しています。(cf.Unixパス名の安全対策
 IPAがコード書いているのを見ると、ひょっとしてコアモジュールで対策はできないのかな?と思ったりして、テストコード書いてみました。

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use URI::Escape;
#use File::Spec;

#これより上に辿ってはいけないパス
my $base_path = '/home/cgibin/public_html';

#スクリプトの実行パス
my $script_path = '/home/cgibin/public_html/dirtr';

#データの記録パス
my $data_path = './datadir/csv';

#正当なファイル名
my $file_true = 'data.csv';


#不当なファイル名
my $file_false = '../../../../../../etc/passwd';


##正当なファイルをテスト
print Encode::encode('utf8',
"【true】:"
. make_fullpath([$data_path,$file_true],$script_path,$base_path)
. "\n"
);

##不当なファイルをテスト
print Encode::encode('utf8',
"【false】:"
. make_fullpath([$data_path,$file_false],$script_path,$base_path)
. "\n"
);

exit;

## パスとファイル名を受け取って絶対パスを返す
## ついでにディレクトリ・トラバーサル対策
## make_fullpath(パスと配列のリファレンス,基準パス,遡上禁止パス)
sub make_fullpath{
my($r_path,$base,$home) = @_;

#URLエンコード対策
$r_path->[$_] = uri_unescape($r_path->[$_]) foreach(0 .. $#{$r_path});

#パスとファイル名をつなげる
# my $filename = File::Spec->catfile(@$r_path);
my $filename = join('/',@$r_path);

#相対パスを絶対化
# my $fullpath = File::Spec->rel2abs($filename,$base);
$base =~ s/\/$//;
my $fullpath = $base . '/' . $filename;

#パスの正規化
my @directries = (); # 正規化パス名中間データを初期化
foreach (split('/', $fullpath)){
next if($_ eq ""); # // を無視
next if($_ eq "."); # /./ も無視
if($_ eq ".."){ # /../ なら
pop(@directries); # 1 つ前の構成要素も無視
next;
}
push(@directries,$_); # 構成要素を追加
}

#バラしたパスをもう一度つなげる
# $fullpath = File::Spec->catfile('/',@directries);
$fullpath = '/' . join('/',(@directries));

die("sub make_fullpath need 2nd parameter 'Basename'") unless($home);
if($fullpath !~ /^$home/){
return('不当なパスです');
}else{
return($fullpath);
}
}

一応、File::Specを使った場合の記述も先頭をコメントアウトして残しておきました。
正規化の過程はほぼIPAの丸写しです。
 実行結果はこちら
【true】:/home/cgibin/public_html/dirtr/datadir/csv/data.csv
【false】:不当なパスです

falseの方は、辿っていくと"/etc/passwd"を指します。

実際には、不正パスを検知した際にはdieしてもいいでしょうし、make_fullpath()に渡す第2引数はCwd::realpath()でも使用すればよろしいかと。

で、ディレクトリ区切りに/を使用しているのでUNIX系専用です。
後でWindowsにも対応させたいな。でももう寝る。
 ついでに、Windowsでのテスト環境がない
posted by 鯖缶 at 01:30 | Comment(0) | TrackBack(0) | ただいま開発中 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバック
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。