WebVulnerabilities

ファイルアップロードにおける拡張子チェックのバイパス手法について

概要

ファイルアップロード機能があるWebアプリケーションでは、通常、許可された拡張子のみを受け入れるように設計されています。しかし、攻撃者はこの拡張子チェックをバイパスして悪意のあるファイル(例:スクリプトファイル)をアップロードし、サーバーで実行させる攻撃を行うことがあります。拡張子バイパス攻撃は、拡張子の誤検出や、ファイル名に特殊な文字やシーケンスを含めることで発生します。

言語別の脆弱性を持つコードの例と攻撃手法

  1. PHP
    • 脆弱性を持つコードの例:
      $allowedExtensions = ['jpg', 'png', 'gif'];
      $fileExtension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
      if (in_array($fileExtension, $allowedExtensions)) {
          move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $_FILES['file']['name']);
      }
      
    • 攻撃手法: PHPでは、ファイル名に「null byte(\0)」を含めることで拡張子チェックをバイパスできる場合があります。例えば、malicious.php.jpg といったファイル名に \0 を挿入することで、サーバー側で実際に .php として扱われるケースがあります(ただし、最新のPHPバージョンではこのバイパス手法は無効化されています)。また、malicious.php.png という名前を使い、サーバーがファイルの内容を検証せずに拡張子だけで判定する場合も危険です。
  2. Python (Flask/Django)
    • 脆弱性を持つコードの例:
      ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
      def allowed_file(filename):
          return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
      
      if 'file' in request.files:
          file = request.files['file']
          if file and allowed_file(file.filename):
              filename = secure_filename(file.filename)
              file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
      
    • 攻撃手法: Pythonでも、filename に特殊文字を含めることでバイパスが可能です。例えば、malicious.py.png のような名前が使われることがあります。また、ファイル内容に悪意のあるスクリプトを含めた画像をアップロードし、処理されるケースもあります。
  3. Node.js (Express)
    • 脆弱性を持つコードの例:
      const multer = require('multer');
      const upload = multer({
          fileFilter: function (req, file, cb) {
              const filetypes = /jpeg|jpg|png/;
              const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
              if (extname) {
                  return cb(null, true);
              }
              cb('Error: File type not allowed!');
          }
      });
      
      app.post('/upload', upload.single('file'), (req, res) => {
          res.send('File uploaded!');
      });
      
    • 攻撃手法: Node.jsでも、拡張子が二重になったファイル名(例: malicious.js.jpg)が使われることがあります。この場合、ファイルの拡張子チェックが不十分だと、JavaScriptファイルが実行される可能性があります。
  4. Java (Spring)
    • 脆弱性を持つコードの例:
      @PostMapping("/upload")
      public String handleFileUpload(@RequestParam("file") MultipartFile file) {
          String[] allowedExtensions = {"jpg", "png", "gif"};
          String fileExtension = FilenameUtils.getExtension(file.getOriginalFilename());
          if (Arrays.asList(allowedExtensions).contains(fileExtension)) {
              file.transferTo(new File("uploads/" + file.getOriginalFilename()));
              return "File uploaded!";
          } else {
              return "Invalid file type!";
          }
      }
      
    • 攻撃手法: Javaでは、拡張子の検査が単純である場合、malicious.jsp.jpg のような名前を使うと、サーバー側で.jspとして実行されるリスクがあります。また、ファイル内容の検証が行われないと、任意のコードが実行される可能性もあります。

言語による差異

防止策

  1. サーバー側で拡張子とMIMEタイプの両方を検査する
    ファイルの拡張子だけでなく、MIMEタイプも確認し、不一致があれば拒否するようにします。

  2. ファイル内容の検証
    画像やドキュメントファイルとして受け取る場合は、実際にその形式であるかを検証します(例:画像であれば画像ライブラリを使って読み取れるか確認する)。

  3. サーバー側で実行可能な場所に保存しない
    アップロードされたファイルがスクリプトとして実行されないように、Webサーバーの公開ディレクトリ外に保存するか、実行権限を制限します。

  4. ファイル名の正規化と安全な保存
    secure_filename() や同様の方法で、ファイル名を正規化し、予期しない文字列やパターンを防ぎます。

拡張子チェックバイパスは多くの言語で発生しうるため、拡張子のチェックだけに頼らず、多層的な検証を行うことが重要です。