ディレクトリ一覧による情報漏えい

ディレクトリ一覧によるファイルや情報の露出

説明

ディレクトリ一覧は、要求されたフォルダー内のファイルを表示する機能です。Goの http.FileServer は、ディレクトリに index.html がない場合に一覧を表示することがあります。別途アクセスを制御せず外部へ提供すると、ディレクトリ構造やファイル名が露出するおそれがあります。攻撃者が設定、バックアップ、ログ、スクリプトなどを発見し、アカウント侵害やコード解析、ほかの脆弱性の探索に使う可能性があります。

想定される影響

  • ディレクトリ構造、ファイル名、バックアップや設定の情報が公開されるおそれがあります。
  • リンクを隠すことはアクセス制御ではありません。発見したファイルに読み取り権限があれば、内容にもアクセスできる可能性があります。
  • 露出したソースコードや設定から、SQLインジェクションや認証回避などの弱点を探られるおそれがあります。

対処方法

  • http.FileServer にプロジェクトルートや必要以上に広いディレクトリを指定しないでください。
  • 公開するファイルだけを置く専用ディレクトリを、限定したパスに対応付けてください。機密ファイルや外部を指すシンボリックリンクがないことを確認し、信頼できないユーザーによるファイル・リンクの作成を防いでください。
  • 別の静的ファイルサーバーやプロキシを使う場合も、不要なディレクトリ一覧を無効にしてください。
  • 保護されたファイルはユーザーの権限を確認してから ServeContent、ServeFile などで提供してください。これらの関数は認可を代替しません。
  • デプロイ前に、静的ディレクトリ内の設定、.env、.git、バックアップ、ログを確認してください。

例

変更後の例は一覧表示の停止に重点を置いています。./public には公開用ファイルだけを置き、外部を指すシンボリックリンクを禁止する必要があります。一部の名前・拡張子の検査だけで、すべての機密ファイルは判別できません。

変更前

go
package main

import (
    "log"
    "net/http"
)

// 変更前: プロジェクトルートをそのまま公開
func main() {
    // 現在のディレクトリ(".")をFileServerで公開
    fs := http.FileServer(http.Dir("."))

    // サーバー上でhttp://localhost:8080/へアクセスすると
    // ディレクトリ一覧や内部のファイルが見える可能性
    http.Handle("/", fs)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

変更後

go
package main

import (
    "log"
    "net/http"
    "os"
    "path/filepath"
)

// 変更後: 
// 1) 静的ファイル専用ディレクトリだけを公開
// 2) 提供前に機密ファイル名を検査

// 公開用の静的ファイルだけを置くディレクトリ
const staticDir = "./public"

// 一部の機密ファイル名・拡張子を拒否する例
func isForbidden(path string) bool {
    base := filepath.Base(path)
    if base == ".env" || base == "config.yaml" || base == "config.yml" {
        return true
    }
    ext := filepath.Ext(path)
    switch ext {
    case ".log", ".bak", ".old", ".zip":
        return true
    }
    return false
}

func secureStaticHandler(w http.ResponseWriter, r *http.Request) {
    // 静的ファイルのパスを計算
    cleanPath := filepath.Clean(r.URL.Path)
    fullPath := filepath.Join(staticDir, cleanPath)

    if isForbidden(fullPath) {
        http.NotFound(w, r)
        return
    }

    // ファイルの存在を確認
    info, err := os.Stat(fullPath)
    if err != nil || info.IsDir() {
        // 一覧を表示せず、ディレクトリ要求を拒否
        http.NotFound(w, r)
        return
    }

    http.ServeFile(w, r, fullPath)
}

func main() {
    // /staticの下だけで静的ファイルを提供
    http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
        // /staticの接頭辞を除去して内部ハンドラーを呼ぶ
        r.URL.Path = r.URL.Path[len("/static"):]
        secureStaticHandler(w, r)
    })

    // ほかのパスには通常のハンドラーを使用
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("<h1>Home</h1>"))
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

解説:

  • 変更前: 現在のディレクトリを認証なしで提供します。インデックスがなければ一覧が表示され、読み取り可能な設定、ログ、バックアップなどの内容も露出する可能性があります。
  • 変更後: /static/ で専用ディレクトリのファイルを提供し、ディレクトリ要求には404を返します。isForbidden が拒否するのは一部のファイルなので、デプロイ内容と書き込み権限も管理する必要があります。

参考資料