Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13527] ファイルの概要

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の FileServer のセキュリティ脆弱性に対処するものです。具体的には、ファイルパスにヌルバイト (\x00) が含まれている場合に発生する可能性のあるディレクトリトラバーサル攻撃を防ぐための変更が加えられています。

変更されたファイルは以下の2つです。

  • src/pkg/net/http/fs.go: FileServer のファイルパス処理ロジックが修正されています。
  • src/pkg/net/http/fs_test.go: ヌルバイトを含むパスに対する新しいテストケースが追加されています。

コミット

commit 538b2122f1d535e5a4ad11a69fddb1da3c10f9de
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Jul 30 13:57:30 2012 +1000

    net/http: don't allow zero byte in FileServer paths
    
    Should probably be fixed in the syscall package, either
    additional or instead of this CL.
    
    Fixes #3842
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6442061

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/538b2122f1d535e5a4ad11a69fddb1da3c10f9de

元コミット内容

net/http: don't allow zero byte in FileServer paths

Should probably be fixed in the syscall package, either
additional or instead of this CL.

Fixes #3842

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6442061

変更の背景

この変更は、Goの net/http パッケージの FileServer が持つセキュリティ脆弱性、具体的には Issue 3842「net/http: parent directory listing vulnerability」を修正するために導入されました。この脆弱性は、特別に細工されたURL(例: http://goserver/..%00/http://goserver/.%00/)を使用することで、攻撃者がWebサーバーのルートディレクトリ外のファイルやディレクトリのリストを取得できる可能性がありました。

この種の攻撃は「ヌルバイトインジェクション」として知られており、ファイルシステム操作を行う関数がヌルバイトを文字列の終端として解釈するOSの特性を悪用します。例えば、FileServer/path/to/file\x00../secret のようなパスを受け取った場合、OSによっては \x00 以降を無視して /path/to/file として処理してしまうことがあります。これにより、攻撃者は本来アクセスできないはずの親ディレクトリや他の場所にアクセスできてしまう危険性がありました。

このコミットは、FileServer がファイルパスにヌルバイトが含まれていることを検出し、そのようなリクエストを拒否することで、この脆弱性を緩和することを目的としています。コミットメッセージには、この問題が syscall パッケージで根本的に修正されるべきであるという言及がありますが、このコミットは net/http レベルでの即時的な対策として導入されました。

前提知識の解説

net/http パッケージと FileServer

Go言語の net/http パッケージは、HTTPクライアントとサーバーの実装を提供します。http.FileServer は、指定されたディレクトリのファイルを提供するHTTPハンドラを生成する関数です。これは、静的ファイル(HTML、CSS、JavaScript、画像など)をWebサーバーとして公開する際によく使用されます。

例:

http.Handle("/", http.FileServer(http.Dir("/var/www/html")))
http.ListenAndServe(":8080", nil)

このコードは、/var/www/html ディレクトリの内容をポート8080で提供するWebサーバーを起動します。

filepath.Separator

filepath.Separator は、オペレーティングシステムがファイルパスのディレクトリ区切り文字として使用する文字を表す定数です。Unix系システムでは '/'、Windowsでは '\\' です。FileServer は、クロスプラットフォームで動作するために、この区切り文字を考慮する必要があります。

strings.Contains()strings.IndexRune()

  • strings.Contains(s, substr string) bool: 文字列 ssubstr を部分文字列として含む場合に true を返します。
  • strings.IndexRune(s string, r rune) int: 文字列 s 内でルーン r が最初に出現するインデックスを返します。見つからない場合は -1 を返します。

これらの関数は、文字列操作、特にパスの検証において重要な役割を果たします。

ヌルバイト (\x00) とヌルバイトインジェクション

ヌルバイト (\x00 または NULL 文字) は、ASCIIコードで0x00に相当する特殊な文字です。C言語などの一部のプログラミング言語や、多くのオペレーティングシステムのファイルシステムAPIでは、文字列の終端を示すマーカーとしてヌルバイトが使用されます。

「ヌルバイトインジェクション」とは、この特性を悪用したセキュリティ攻撃手法です。攻撃者は、ファイルパスやその他の入力データにヌルバイトを挿入します。アプリケーションがこの入力データをファイルシステムAPIに渡す際、APIはヌルバイトを文字列の終端と解釈し、ヌルバイト以降の文字を無視してしまいます。これにより、攻撃者は意図しないファイルやディレクトリにアクセスしたり、ファイル名を変更したりするなどの不正な操作を行うことが可能になります。

例えば、Webサーバーが /var/www/html/index.html を提供する際に、ユーザーからのリクエストパスが /../secret\x00.html のように細工された場合、サーバー側の処理でヌルバイト以降が切り捨てられ、結果的に /../secret にアクセスしてしまう可能性があります。これは、本来アクセスが許可されていないディレクトリへのアクセスを許してしまう「ディレクトリトラバーサル」の一種です。

ディレクトリトラバーサル (Path Traversal)

ディレクトリトラバーサルは、Webアプリケーションの脆弱性の一種で、攻撃者がWebサーバーのファイルシステム上の任意のファイルやディレクトリにアクセスすることを可能にします。これは、ユーザーが提供する入力(通常はファイル名やパス)が適切に検証またはサニタイズされていない場合に発生します。攻撃者は ../ (親ディレクトリへの移動) などの特殊なシーケンスを使用して、Webサーバーのルートディレクトリ外のファイルにアクセスしようとします。ヌルバイトインジェクションは、このディレクトリトラバーサル攻撃を成功させるための一つの手段となり得ます。

技術的詳細

このコミットは、net/http パッケージの FileServer がファイルパスを処理する際に、ヌルバイト (\x00) が含まれていないかを厳密にチェックすることで、ヌルバイトインジェクションによるディレクトリトラバーサル攻撃を防ぎます。

従来の FileServerDir 型の Open メソッドでは、filepath.Separator'/' ではない(主にWindows環境を想定)場合に、パスに filepath.Separator が含まれていないかを確認していました。これは、FileServer がURLパスをファイルパスに変換する際に、意図しないディレクトリ区切り文字が挿入されるのを防ぐためのものでした。

しかし、このチェックだけでは、ヌルバイトがパスに挿入された場合の脆弱性には対処できませんでした。ヌルバイトは、多くのファイルシステムAPIで文字列の終端として扱われるため、FileServername 引数として受け取ったパスにヌルバイトが含まれていると、そのヌルバイト以降の文字列が無視されてしまい、攻撃者が意図しないファイルやディレクトリにアクセスできてしまう可能性がありました。

このコミットでは、既存のパス区切り文字のチェックに加えて、strings.Contains(name, "\x00") という条件が追加されました。これにより、name 文字列にヌルバイトが含まれている場合、即座に errors.New("http: invalid character in file path") エラーを返してファイルオープンを拒否するようになります。

この変更により、FileServer はヌルバイトを含む不正なパスを安全に処理し、攻撃者がファイルシステム上の機密情報にアクセスしたり、システムを操作したりするのを防ぐことができます。

また、この修正の有効性を確認するために、fs_test.goTestFileServerZeroByte という新しいテストケースが追加されました。このテストは、ヌルバイトを含むパス (/..\x00) を FileServer にリクエストし、期待通りにエラー(HTTPステータスコード200以外)が返されることを検証します。これにより、将来の変更でこのセキュリティ対策が誤って削除されたり、機能しなくなったりするのを防ぐことができます。

コアとなるコードの変更箇所

src/pkg/net/http/fs.go

--- a/src/pkg/net/http/fs.go
+++ b/src/pkg/net/http/fs.go
@@ -28,7 +28,8 @@ import (
 type Dir string
 
 func (d Dir) Open(name string) (File, error) {
-	if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
+	if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
+		strings.Contains(name, "\x00") {
 		return nil, errors.New("http: invalid character in file path")
 	}
 	dir := string(d)

src/pkg/net/http/fs_test.go

--- a/src/pkg/net/http/fs_test.go
+++ b/src/pkg/net/http/fs_test.go
@@ -389,6 +389,23 @@ func TestServeIndexHtml(t *testing.T) {
 	}
 }
 
+func TestFileServerZeroByte(t *testing.T) {
+	ts := httptest.NewServer(FileServer(Dir(".")))
+	defer ts.Close()
+
+	res, err := Get(ts.URL + "/..\x00")
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		t.Fatal("reading Body:", err)
+	}
+	if res.StatusCode == 200 {
+		t.Errorf("got status 200; want an error. Body is:\n%s", string(b))
+	}
+}
+
 type fakeFileInfo struct {
 	dir      bool
 	basename string

コアとなるコードの解説

src/pkg/net/http/fs.go の変更

Dir 型の Open メソッドは、FileServer がリクエストされたパスに対応するファイルを開く際に呼び出されます。このメソッドの冒頭で、不正なパスを検出するためのバリデーションが行われています。

変更前:

if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
    return nil, errors.New("http: invalid character in file path")
}

この行は、主にWindows環境において、URLパスにUnix形式の / 以外のディレクトリ区切り文字(例: \)が誤って含まれていないかをチェックしていました。もし含まれていれば、不正なパスとしてエラーを返していました。

変更後:

if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
    strings.Contains(name, "\x00") {
    return nil, errors.New("http: invalid character in file path")
}

この変更では、既存の条件に || strings.Contains(name, "\x00") が追加されました。

  • || は論理OR演算子です。
  • strings.Contains(name, "\x00") は、name という文字列の中にヌルバイト (\x00) が含まれているかどうかをチェックします。

これにより、以下のいずれかの条件が真になった場合に、"http: invalid character in file path" というエラーが返され、ファイルオープンが拒否されます。

  1. OSのパス区切り文字が '/' ではない環境で、パスにそのOSのパス区切り文字が含まれている場合。
  2. パスにヌルバイト (\x00) が含まれている場合。

このヌルバイトのチェックが追加されたことで、ヌルバイトインジェクションによるディレクトリトラバーサル攻撃が効果的に防止されます。

src/pkg/net/http/fs_test.go の追加テスト

TestFileServerZeroByte という新しいテスト関数が追加されました。

  1. ts := httptest.NewServer(FileServer(Dir("."))): 現在のディレクトリ (.) をルートとする FileServer を持つテストHTTPサーバーを起動します。
  2. defer ts.Close(): テスト終了時にサーバーをクリーンアップします。
  3. res, err := Get(ts.URL + "/..\x00"): テストサーバーに対して、ヌルバイト (\x00) を含む不正なパス /..\x00 を含むHTTP GETリクエストを送信します。
    • .. は親ディレクトリへの移動を意味し、ヌルバイトと組み合わせることでディレクトリトラバーサルを試みる典型的な攻撃パターンです。
  4. エラーハンドリング: リクエスト送信中にエラーが発生しないことを確認します。
  5. b, err := ioutil.ReadAll(res.Body): サーバーからのレスポンスボディを読み込みます。
  6. if res.StatusCode == 200: ここがテストの核心です。もしサーバーがHTTPステータスコード 200 OK を返した場合、それは不正なパスを許可してしまったことを意味します。この場合、テストは t.Errorf を呼び出して失敗します。
    • 期待される動作は、ヌルバイトを含むパスが拒否され、200 OK 以外のエラーを示すステータスコード(例: 400 Bad Request404 Not Found など)が返されることです。

このテストは、fs.go で行われたヌルバイトチェックが正しく機能していることを検証し、将来のリグレッションを防ぐための重要な役割を果たします。

関連リンク

参考にした情報源リンク