C#でzipを展開するときに気をつけること
C#でzipを展開するときに気をつけること
C#では、.NET Framework 4.5から、外部ライブラリを使用することなく、zipファイルを展開可能になりました。
その展開機能を使うときに、ファイル単位で展開する場合には、気をつけるべきポイントがあります。
まとめ
- ExtractToFile関数とPath.Combine関数を組み合わせると危険
- ExtractToDirectory関数を利用する(むやみにExtractToFile関数を呼ばない)
- どうしても使う場合には、フルパスの一致を確認する
Zip Slipとは
細工されたzipファイルのエントリから、ファイルパスの結合を利用して「ディレクトリトラバーサル」(パストラバーサル)を行い、「指定されたフォルダ」の外のファイルを書き換える攻撃です。
zip形式以外にも、いくつかの圧縮形式で、同様の現象が起こせるようです(参考サイトを参照)。
手法
- folder/a.txt
- folder/b.jpg
- c.html
- ../malicious_file.txt
上のようなファイルを格納するzipファイル(攻撃ファイル)が与えられたとします。
これを以下のコードで展開すると、「指定されたフォルダ」の外にファイルを生成してしまいます。
string currentDirectory = Directory.GetCurrentDirectory();
using (var zip = ZipFile.OpenRead("malicious.zip"))
{
foreach (var entry in zip.Entries)
{
string destPath = Path.Combine(currentDirectory, entry.FullName);
entry.ExtractToFile(destPath, true); // ファイルを上書きする
}
}
カレントディレクトリがC:/test
の場合、以下のファイルが作成されます。
- C:/test/folder/a.txt
- C:/test/folder/b.jpg
- C:/test/c.html
- C:/malicious_file.txt
malicious_file.txt
は、「指定されたフォルダ」の外に作成されてしまいました。
参考サイト[1]では、サーバ側の問題について触れられていますが、クライアント側でも発生する可能性があります。
対策
string currentDirectory = Directory.GetCurrentDirectory();
using (var zip = ZipFile.OpenRead("malicious.zip"))
{
foreach (var entry in zip.Entries)
{
// 以下の行を修正
string destPath = Path.GetFullPath(Path.Combine(currentDirectory, entry.FullName));
// 以下の4行を追加
if (!destPath.StartsWith(currentDirectory))
{
throw new Exception("Malicious entry has detected.");
}
entry.ExtractToFile(destPath, true); // ファイルを上書きする
}
}
Path.GetFullPath()
関数で、結合したファイルパスを検証し、「指定されたフォルダ」の外であれば、エラーとします。[2]
今回は例外を投げるようにしました。
また一般的には、zip内のすべてのファイルを展開することが多いと思いますので、ライブラリで対策されているZipFile.ExtractToDirectory()
関数等を使うほうが安全です。
entry.ExtractToFile()
関数は、極力使用を避けたほうがよいでしょう。
zipファイルフォーマット
zipファイルの末尾の構造体(セントラルディレクトリ)に、ファイル名を格納するフィールドがあります。
詳細は、参考サイト[3]を参照。
ファイル名について、含めてはならない文字列に関する規定は、今回探した限りでは見つかりませんでした。
-
単純に、
../
または..\
が含まれないことを確認する対策では不十分です。ext4等のファイルシステムではシンボリックリンク、NTFSでもジャンクションがあり、対策を迂回できてしまいます。 ↩︎
Discussion