WebVulnerabilities

ディレクトリトラバーサル攻撃について

概要

ディレクトリトラバーサル攻撃は、Webアプリケーションがユーザー入力をもとにファイルやディレクトリにアクセスする際に、攻撃者が相対パスを利用して本来アクセスするべきでないシステムファイルや他の機密情報にアクセスする攻撃手法です。攻撃者は../(親ディレクトリへの移動)などの相対パスを使い、アプリケーションが意図していないディレクトリやファイルを閲覧したり、場合によっては書き換えたりすることができます。


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

PHP

脆弱なコード例:

<?php
$filename = $_GET['file'];
$content = file_get_contents('/var/www/html/files/' . $filename);
echo $content;
?>

攻撃手法: 攻撃者は以下のようなURLを使って、/etc/passwdのような機密ファイルにアクセスします。

http://example.com/view.php?file=../../../../etc/passwd

このコードは、ユーザーが提供したパスを検証せずに直接ファイルパスに使用しているため、ディレクトリトラバーサル攻撃が成立します。

Python(Flask)

脆弱なコード例:

from flask import Flask, request, send_file

app = Flask(__name__)

@app.route('/download')
def download_file():
    filename = request.args.get('file')
    return send_file('/var/www/html/files/' + filename)

if __name__ == '__main__':
    app.run()

攻撃手法: 攻撃者は以下のようなURLを使って、システムファイルにアクセスできます。

http://localhost:5000/download?file=../../../../etc/passwd

Node.js(Express)

脆弱なコード例:

const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

app.get('/view', (req, res) => {
    const filename = req.query.file;
    const filepath = path.join(__dirname, 'files', filename);
    fs.readFile(filepath, 'utf8', (err, data) => {
        if (err) {
            return res.status(404).send('File not found');
        }
        res.send(data);
    });
});

app.listen(3000);

攻撃手法: 攻撃者は以下のようなURLを使って、システムファイルにアクセスできます。

http://localhost:3000/view?file=../../../../etc/passwd

Java(Spring)

脆弱なコード例:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.stereotype.Controller;
import java.nio.file.Files;
import java.nio.file.Paths;

@Controller
public class FileController {

    @GetMapping("/view")
    public String viewFile(@RequestParam String file) throws Exception {
        String filepath = "/var/www/html/files/" + file;
        return new String(Files.readAllBytes(Paths.get(filepath)));
    }
}

攻撃手法: 攻撃者は以下のようなURLを使って、システムファイルにアクセスできます。

http://localhost:8080/view?file=../../../../etc/passwd

防止策

  1. 入力の正規化と検証:
    • ユーザーからの入力を正規化し、相対パス(../など)が含まれていないかをチェックします。ファイル名として許可されるのは、特定のディレクトリ内に存在する安全なファイルのみであるべきです。

    PHPの例:

    $filename = basename($_GET['file']);
    $filepath = '/var/www/html/files/' . $filename;
    if (file_exists($filepath)) {
        $content = file_get_contents($filepath);
        echo $content;
    } else {
        echo "File not found.";
    }
    

    Pythonの例(Flask):

    import os
    from flask import Flask, request, send_file, abort
    
    app = Flask(__name__)
    
    @app.route('/download')
    def download_file():
        filename = request.args.get('file')
        if '..' in filename or filename.startswith('/'):
            abort(400)
        safe_path = os.path.join('/var/www/html/files', filename)
        if os.path.exists(safe_path):
            return send_file(safe_path)
        else:
            abort(404)
    
  2. ファイルパスのホワイトリスト:
    • 許可されたファイル名のリストを用意し、そのリストに含まれているファイルのみアクセスを許可します。

    Node.jsの例(Express):

    const express = require('express');
    const fs = require('fs');
    const path = require('path');
    const app = express();
    
    const allowedFiles = ['file1.txt', 'file2.txt'];
    
    app.get('/view', (req, res) => {
        const filename = req.query.file;
        if (!allowedFiles.includes(filename)) {
            return res.status(400).send('Invalid file');
        }
        const filepath = path.join(__dirname, 'files', filename);
        fs.readFile(filepath, 'utf8', (err, data) => {
            if (err) {
                return res.status(404).send('File not found');
            }
            res.send(data);
        });
    });
    
    app.listen(3000);
    
  3. 絶対パスの使用:
    • 入力されたファイル名を絶対パスに変換し、それが許可されたディレクトリ内にあるかどうかをチェックします。

    Javaの例(Spring):

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.stereotype.Controller;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    @Controller
    public class FileController {
    
        @GetMapping("/view")
        public String viewFile(@RequestParam String file) throws Exception {
            Path basePath = Paths.get("/var/www/html/files").toRealPath();
            Path requestedPath = basePath.resolve(file).normalize().toRealPath();
    
            if (!requestedPath.startsWith(basePath)) {
                return "Invalid file path.";
            }
    
            return new String(Files.readAllBytes(requestedPath));
        }
    }
    
  4. ユーザー入力のエスケープ:
    • ファイル名に使用するユーザー入力はエスケープ処理を行い、危険な文字やパスが含まれていないことを確認します。

まとめ: ディレクトリトラバーサル攻撃は、ユーザー入力が適切に検証されない場合に発生する脆弱性です。相対パスの除去、ホワイトリストによるファイル名の制限、絶対パスの使用、ユーザー入力のエスケープなどの防止策を講じることで、この攻撃を防ぐことができます。各言語に応じた適切な防止策を実装することが推奨されます。