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

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

このコミットは、Go言語の実験的なSSHパッケージ(exp/ssh)に、RFC 4251のセクション9.2で定義されている要件に従って文字列をサニタイズするためのsafeString関数を追加するものです。これにより、SSHプロトコルにおける制御文字の扱いが適切に行われ、セキュリティとプロトコル準拠が向上します。具体的には、タブ、キャリッジリターン、改行以外のすべての制御文字をスペース(0x20)に置換します。

コミット

  • コミットハッシュ: b57bb9282e0ba47bfecc3de8a2ab72754a2d8185
  • Author: Dave Cheney dave@cheney.net
  • Date: Mon Nov 28 12:29:19 2011 -0500

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

https://github.com/golang/go/commit/b57bb9282e0ba47bfecc3de8a2ab72754a2d8185

元コミット内容

exp/ssh: add safeString error sanitiser

R=huin, agl, gustav.paul, cw
CC=golang-dev
https://golang.org/cl/5399044

変更の背景

SSHプロトコルは、セキュアなリモートシェルアクセスやファイル転送などを提供するための重要なネットワークプロトコルです。プロトコルの仕様はRFCによって厳密に定義されており、特に文字列の扱いについてはセキュリティ上の脆弱性を防ぐために細心の注意が払われています。

RFC 4251(The Secure Shell (SSH) Protocol Architecture)のセクション9.2「Strings」では、SSHプロトコル内で使用される文字列のエンコーディングとサニタイズに関する要件が記述されています。このセクションでは、文字列はUTF-8でエンコードされるべきであり、特に制御文字(0x00-0x1F)の扱いについて規定しています。多くのプロトコルでは、制御文字が予期せぬ動作やセキュリティ上の問題を引き起こす可能性があるため、これらを適切に処理することが求められます。

このコミットの背景には、SSHプロトコルが要求する文字列のサニタイズ要件、特にRFC 4251, Section 9.2への準拠があります。SSHプロトコルでは、エラーメッセージやその他のテキストベースのデータに、ターミナルエミュレータやログ解析ツールに問題を引き起こす可能性のある制御文字が含まれないようにする必要があります。例えば、NULL文字(0x00)やベル文字(0x07)などがそのまま出力されると、ターミナルが誤動作したり、ログが読みにくくなったりする可能性があります。

safeString関数は、このような制御文字を無害なスペース文字(0x20)に置換することで、プロトコル準拠と堅牢性を確保し、SSHクライアントやサーバーが予期せぬ入力によって不安定になることを防ぐことを目的としています。

前提知識の解説

SSH (Secure Shell)

SSHは、ネットワークを介してコンピュータを安全に操作するためのプロトコルです。クライアントとサーバー間で暗号化された通信チャネルを確立し、リモートコマンドの実行、ファイル転送(SCP, SFTP)、ポートフォワーディングなどを可能にします。SSHは、公開鍵暗号方式やパスワード認証など、複数の認証メカニズムをサポートしており、データの機密性、完全性、認証性を提供します。

RFC (Request for Comments)

RFCは、インターネット技術の標準や仕様を定義する文書群です。IETF(Internet Engineering Task Force)によって発行され、インターネットプロトコル(TCP/IP、HTTP、SSHなど)の動作や実装に関する詳細な情報を提供します。SSHプロトコルも複数のRFCによって定義されており、このコミットで参照されているRFC 4251はその中でも基本的なアーキテクチャを定義するものです。

制御文字 (Control Characters)

制御文字は、ASCIIやUnicodeなどの文字コードセットにおいて、表示される文字ではなく、プリンタやターミナルなどのデバイスの動作を制御するために使用される特殊な文字です。例えば、改行(LF, 0x0A)、キャリッジリターン(CR, 0x0D)、タブ(HT, 0x09)などはよく知られた制御文字です。しかし、それ以外の多くの制御文字(例: ベル文字 0x07, エスケープ文字 0x1B)は、ターミナルに予期せぬ動作を引き起こしたり、セキュリティ上の脆弱性(例: ターミナルエミュレータのバッファオーバーフロー)につながる可能性があります。

文字列のサニタイズ (String Sanitization)

文字列のサニタイズとは、入力された文字列から、特定のコンテキストにおいて問題を引き起こす可能性のある文字やパターンを除去または置換する処理のことです。セキュリティの文脈では、SQLインジェクション、クロスサイトスクリプティング(XSS)、コマンドインジェクションなどの攻撃を防ぐために不可欠です。このコミットのケースでは、SSHプロトコルが期待する形式に合わない制御文字を無害化することが目的です。

技術的詳細

safeString関数は、RFC 4251, Section 9.2の要件を満たすために設計されています。このセクションでは、SSHプロトコル内で交換される文字列に含まれる制御文字の扱いについて具体的に指示しています。

RFC 4251, Section 9.2の関連部分を引用すると、以下のようになります(要約): 「文字列はUTF-8でエンコードされるべきである。制御文字(0x00-0x1F)は、タブ(0x09)、キャリッジリターン(0x0D)、改行(0x0A)を除いて、すべてスペース(0x20)に置換されるべきである。」

safeString関数の実装は、この仕様に厳密に従っています。

  1. 入力のバイトスライス化: Go言語の文字列はイミュータブル(不変)であるため、変更を行うためにはまず文字列をバイトスライス([]byte)に変換します。これにより、個々のバイトにアクセスし、必要に応じて変更することができます。
    out := []byte(s)
    
  2. バイトごとのイテレーション: for i, c := range outループを使用して、バイトスライスoutの各バイト(c)とそのインデックス(i)を順に処理します。
  3. 制御文字のチェック: 各バイトcに対して、以下の条件で制御文字であるかをチェックします。
    • c < 0x20: これは、バイトがASCIIの制御文字範囲(0x00から0x1F)にあることを意味します。
    • c != 0xd: キャリッジリターン(CR)は除外します。
    • c != 0xa: 改行(LF)は除外します。
    • c != 0x9: タブ(HT)は除外します。 これらの条件を組み合わせることで、「タブ、キャリッジリターン、改行以外のすべての制御文字」を正確に識別します。
  4. 置換: 上記の条件に合致するバイトが見つかった場合、そのバイトをスペース文字(0x20)に置換します。
    out[i] = 0x20
    
  5. 文字列への再変換: すべての処理が完了した後、変更されたバイトスライスoutを再び文字列に変換して返します。
    return string(out)
    

この関数は、SSHプロトコルが処理する可能性のある任意の文字列(例えば、エラーメッセージ、ユーザー名、パスワードなど)に対して適用され、プロトコルレベルでの堅牢性とセキュリティを向上させます。

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

このコミットでは、以下の2つのファイルが変更されています。

  1. src/pkg/exp/ssh/common.go: safeString関数の実装が追加されました。
  2. src/pkg/exp/ssh/common_test.go: safeString関数のテストケースが追加されました。

src/pkg/exp/ssh/common.go の変更点

--- a/src/pkg/exp/ssh/common.go
+++ b/src/pkg/exp/ssh/common.go
@@ -224,3 +224,16 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK
 	r = marshalString(r, pubKey)
 	return ret
 }
+
+// safeString sanitises s according to RFC 4251, section 9.2. 
+// All control characters except tab, carriage return and newline are
+// replaced by 0x20.
+func safeString(s string) string {
+	out := []byte(s)
+	for i, c := range out {
+		if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
+			out[i] = 0x20
+		}
+	}
+	return string(out)
+}

src/pkg/exp/ssh/common_test.go の変更点

--- /dev/null
+++ b/src/pkg/exp/ssh/common_test.go
@@ -0,0 +1,26 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"testing"
+)
+
+var strings = map[string]string{
+	"\x20\x0d\x0a":  "\x20\x0d\x0a",
+	"flibble":       "flibble",
+	"new\x20line":   "new\x20line",
+	"123456\x07789": "123456 789",
+	"\t\t\x10\r\n":  "\t\t \r\n",
+}
+
+func TestSafeString(t *testing.T) {
+	for s, expected := range strings {
+		actual := safeString(s)
+		if expected != actual {
+			t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual))
+		}
+	}
+}

コアとなるコードの解説

safeString 関数 (src/pkg/exp/ssh/common.go)

この関数は、入力文字列sを受け取り、RFC 4251, Section 9.2の規定に従ってサニタイズされた文字列を返します。

// safeString sanitises s according to RFC 4251, section 9.2. 
// All control characters except tab, carriage return and newline are
// replaced by 0x20.
func safeString(s string) string {
	out := []byte(s) // 入力文字列をバイトスライスに変換
	for i, c := range out { // バイトスライスの各バイトをイテレート
		// 制御文字(0x00-0x1F)であり、かつタブ(0x09)、キャリッジリターン(0x0D)、改行(0x0A)ではない場合
		if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
			out[i] = 0x20 // スペース(0x20)に置換
		}
	}
	return string(out) // 変更されたバイトスライスを文字列に戻して返す
}

このコードは非常に効率的で、文字列を一度バイトスライスに変換し、インプレースで変更を行うことで、余分なメモリ割り当てを最小限に抑えています。

TestSafeString 関数 (src/pkg/exp/ssh/common_test.go)

このテストファイルは、safeString関数が期待通りに動作するかを検証するためのものです。

var strings = map[string]string{
	"\x20\x0d\x0a":  "\x20\x0d\x0a", // スペース、CR、LFは変更されない
	"flibble":       "flibble",      // 通常の文字列は変更されない
	"new\x20line":   "new\x20line",   // スペースを含む文字列も変更されない
	"123456\x07789": "123456 789",   // ベル文字(0x07)がスペースに置換される
	"\t\t\x10\r\n":  "\t\t \r\n",    // タブ、CR、LFは変更されず、DLE(0x10)がスペースに置換される
}

func TestSafeString(t *testing.T) {
	for s, expected := range strings { // テストケースをイテレート
		actual := safeString(s) // safeString関数を呼び出し
		if expected != actual { // 期待値と実際の結果を比較
			// 失敗した場合、バイト表現でエラーメッセージを出力し、視覚的に分かりやすくする
			t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual))
		}
	}
}

テストケースは、以下のシナリオをカバーしています。

  • 変更されない文字: スペース(\x20)、キャリッジリターン(\x0d)、改行(\x0a)、タブ(\t)が含まれる文字列が正しく変更されないことを確認します。
  • 通常の文字列: 制御文字を含まない通常の文字列が変更されないことを確認します。
  • 置換される制御文字: ベル文字(\x07)やデータリンクエスケープ(\x10)など、RFC 4251でスペースに置換されるべき制御文字が含まれる文字列が正しくサニタイズされることを確認します。

t.Errorfでバイト表現([]byte(expected), []byte(actual))を出力しているのは、制御文字を含む文字列の比較において、通常の文字列表示では違いが分かりにくい場合があるため、デバッグを容易にするためです。

関連リンク

参考にした情報源リンク