2022年末にC#でzipファイルを解凍する際に遭遇したバグ

2022年末にC#でzipファイルを解凍する際に遭遇したバグ

最近、アップロード機能を調査していたところ、クライアントからzipファイルがアップロードされ、サーバー側でC#のzipファイル解凍コードを使用してアップロードファイルを解凍し、許可されたファイルタイプかどうかを検証していました。また、ファイル拡張子の不正な変更やファイルヘッダーの検証なども行っていました。ところが、zipファイルを解凍する際に問題が発生しました。

最終更新 2022/12/23 9:39
江湖人士
読了目安 2 分
カテゴリ
.NET
タグ
.NET C#

本記事はユーザーからの寄稿です。

著者:江湖人士

原文タイトル:2022年末 C# でzipファイルを解凍する際に遭遇したバグ

原文リンク:https://jhrs.com/2022/46060.html

最近、アップロード機能を調査していたところ、クライアントがzipファイルをアップロードし、サーバー側でC#を使用してzipファイルを解凍し、アップロードが許可されたファイルタイプかどうかを検証する処理を行っていました。また、ファイル拡張子の不正な変更やファイルヘッダーなども確認する必要がありましたが、zipファイルを解凍する際に問題が発生しました。

C# で zip ファイルを解凍する

まず前提として、IIS上にファイルサービスサイトをデプロイしており、各種ファイルをアップロードできるようになっています。処理の流れとしては、まずサイトのルートディレクトリ内にランダムに作成した一時ディレクトリ(ここでは手抜き方法としてGUIDをディレクトリ名として使用)にアップロードし、ファイル検証を通過した後にコードで正式なアーカイブディレクトリにコピーまたは移動します。C#でのファイルのコピー・移動のコードは、江湖人士サイトの記事を参考にしてください。昨日の業務終了間際に、zipファイルのアップロード時にエラーが発生し、ファイルサービスのルートサイトに大量のGUIDで始まるディレクトリが作成されてしまいました。これは明らかに異常事態であり、コードにバグがあるに違いありません。

バグのある解凍コード

もうすぐ2022年末だというのに、このバグが発生しました。急いで模擬環境を構築して動作確認したところ、以下の元のコードに問題があることが判明しました。元のコードは次のとおりです。

/// <summary>
/// ファイルを解凍する
/// </summary>
/// <param name="saveDir">保存ディレクトリ</param>
/// <param name="stream"></param>
public static void UnZipFiles(string saveDir, Stream stream)
{
    using (ZipInputStream s = new ZipInputStream(stream))
    {
        ZipEntry theEntry;
        while ((theEntry = s.GetNextEntry()) != null)
        {
            string directoryName = $"{saveDir}{Path.GetDirectoryName(theEntry.Name)}\\";
            string fileName = Path.GetFileName(theEntry.Name);
            Directory.CreateDirectory(directoryName);
            using (FileStream streamWriter = File.Create(directoryName + fileName))
            {
                byte[] data = new byte[2048];
                while (true)
                {
                    int size = s.Read(data, 0, data.Length);
                    if (size > 0)
                    {
                        streamWriter.Write(data, 0, size);
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    }
}

ちなみに、これは古いプロジェクトであるため、圧縮・解凍には ICSharpCode.SharpZipLib.Zip コンポーネントを使用しています。

ソースコードを見ると、一目で問題点がわかりました。ロジックが不十分であり、解凍したファイルの保存ディレクトリを直接連結していることが原因です。

このバグを修正するには?

問題がわかれば修正は簡単で、Path.Combine メソッドを呼び出し、解凍時にディレクトリかファイルかを判断すればよいです。最終的な修正後のコードは以下のとおりです。

/// <summary>
/// ファイルを解凍する
/// </summary>
/// <param name="saveDir">保存ディレクトリ</param>
/// <param name="stream"></param>
public static void UnZipFiles(string saveDir, Stream stream)
{
    using (ZipInputStream s = new ZipInputStream(stream))
    {
        ZipEntry theEntry;
        string directoryName, file, fileName;
        while ((theEntry = s.GetNextEntry()) != null)
        {
            directoryName = Path.Combine(saveDir, Path.GetDirectoryName(theEntry.Name));
            fileName = Path.GetFileName(theEntry.Name);
            Directory.CreateDirectory(directoryName);
            file = Path.Combine(directoryName, fileName);

            if (theEntry.IsFile)
            {
                using (FileStream streamWriter = File.Create(file))
                {
                    byte[] data = new byte[2048];
                    while (true)
                    {
                        int size = s.Read(data, 0, data.Length);
                        if (size > 0)
                        {
                            streamWriter.Write(data, 0, size);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
        }
    }
}
さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む