チートシートシリーズの 今回は、 Go開発者の ための 8つの セキュリティベストプラクティスを 紹介します。 Go言語には、 メモリの ガベージコレクションや 型安全性の 高い ポインターなど、 Cのような 古くて 低レベルな 言語と 比べて、 より 安全な 開発を 促す 多くの 機能が 組み込まれています。
こうした 機能に より、 メモリを 自分で 管理する 必要が なくなり、 脆弱性に つながる バグを 避けやすくなります。 それでも、 プログラマーが 把握しておくべき セキュリティの ベストプラクティスは 存在します。 Eric Smalling、 Gerred Dillonが 執筆し、 Snykの シニアソフトウェアエンジニアである Dan Enmanの 協力を 得て 作成した この チートシートでは、 よく ある トピックを いくつか 取り上げます。
チートシートを ダウンロード !
Go Modulesを 使う
依存関係を スキャンして CVEを 検出する
Go標準の 暗号パッケージを 使う
html/templateを 使って XSS攻撃を 防ぐ
サブシェルの 使用
unsafeと cgoを 避ける
リフレクションの 使用は 最小限に する
コンテナの 攻撃対象領域を 最小化する
1. Go Modulesを使う Go Modules は、 v1.11以降の 公式依存関係 管理システムです。 従来の Vendor と Dep は 非推奨に なっています。 Go Modulesでは、 推移的な モジュールを 含めて 依存関係の バージョンを 固定できます。 また、 go.sumチェックサムデータベースに より、 予期しない モジュールの 変更を 防ぐことができます。
まず、 最上位の ディレクトリで go mod init [namespace/project-name]を 実行し、 プロジェクトを 初期化します。
これに より、 現在の ディレクトリに go.modと いう ファイルが 作成され、 プロジェクト名と 現在 使用している Goの バージョンが 記録されます。 ソースコードに パッケージの インポートが ある 場合、 go build(または test、installなど)を 実行するだけで、 使用中の モジュールと その バージョンが go.modに 反映されます。 go getを 使って 依存関係を 更新する こともできます。 特定の バージョンへの 更新も 可能で、 その 内容も go.modに 反映されます。
go.modファイルの 例:
go.sumと いう ファイルも 作成されていることに 注目してください。 この ファイルには、 使用する 各モジュールの ハッシュが 記録されており、 Goは これを 利用して、 すべての ビルドで 同じ バイナリが 使われる ことを 検証します。 go.modと go.sumの 両方を、 アプリケーションコードとともに ソース管理に チェックインしてください。
Go 公式ブログの チュートリアル Using Go Modules は、 Go Modulesに ついて さらに 学ぶのに 役立つ 優れた 資料です。 推移的な 依存関係の バージョン固定や、 使われていない 依存関係の 整理などを 解説しています。
2. 依存関係をスキャンしてCVEを検出する 多くの プロジェクトでは、 アプリケーション自体よりも、 依存先の モジュールに 含まれる コードの ほうが 多くを 占めます。 こうした 外部依存関係は、 セキュリティの 脆弱性が 入り込む 一般的な 経路です。 広範な 脆弱性データベース を 活用する Snykなどの ツールを 使えば、 依存関係の グラフを スキャンして 既知の 脆弱性を 検出し、 修正に 役立つアップグレードを 提案できます。 さらに、 今後新たに 発見される 脆弱性に ついて、 プロジェクトを 継続的に 監視する ことも 可能です。
たとえば、 Goアプリケーションで snyk testを 実行するだけで、 モジュールが 解析され、 既知の CVE と、 アップグレード先と なる 修正版の 情報が 報告されます。 また、 Snykの Webベースの ツールでは、 GitHubリポジトリを 直接 継続的に 監視できます。 コードを 変更していない 場合や CIビルドを 実行していない 場合でも、 今後発見された 脆弱性に ついて 通知を 受け取れます。
GoアプリケーションでのSnykテストの例
Snykのウェブベースツールの例
3. サードパーティ製ではなくGo標準の暗号パッケージを使う Go標準ライブラリの cryptoパッケージ は、 セキュリティ研究者に よる 十分な 監査を 受けています。 しかし、 すべての 機能を 網羅しているわけではないため、 サードパーティ製の パッケージを 使いたくなることもあるでしょう。
暗号アルゴリズムを 自作しないのと 同様に、 サードパーティ製の 暗号ライブラリにも 十分 注意してください。 同じ レベルの 厳密な 監査を 受けているとは 限りません。 提供元を 確認しましょう。
4. html/templateを使ってXSS攻撃を防ぐ io.WriteString()または text/templateパッケージを 使って、 フィルタリングされていない 文字列を Webクライアントに 返すと、 ユーザーが クロスサイトスクリプティング (XSS) 攻撃 に さらされる 可能性が あります。 返される 文字列内の HTMLタグが エンコードされずに 出力される うえ、 明示的に 設定しないと Content-Type: plain/textレスポンスヘッダーが 誤って 設定される 可能性が ある ためです。
html/templateパッケージを 使えば、 アプリケーションの ロジックで 手作業に よる エンコードを 徹底する 代わりに、 返すコンテンツを 自動的に Web向けに エンコードできます。 この トピックに ついて、 詳しい 解説と 例を 掲載した OWASP/GO-SCP の ドキュメントも 参考に なります。
5. サブシェルの使用 Goでの subshellは、 基本的に システムへの 直接的な シェルアクセスを 可能に する もので、 通常は コマンドラインツール型の アプリケーションでのみ 使われます。 可能な 限り、 適切な モジュールを 使い、 Goコードで ネイティブに 実装する 方法を 優先してください。
サブシェルを 使う 必要が ある 場合は、 外部から 取得した データを 渡す前に 適切に サニタイズしてください。 また、 返される データも サニタイズし、 アプリケーションが 基盤システムの 不要な 情報を 漏らさないようにしましょう。 これは、 レンダリングされた テンプレートへの 攻撃 (上記の #4を 参照) や SQLインジェクション への 対策と 同様です。 アプリケーションの リクエスト処理中に 外部プロセスを 実行すると、 Goコードから 制御できない 副作用が 起きる 可能性も 考慮してください。 たとえば、 ファイルシステムの 変更、 外部依存先への 呼び出し、 コンテナの 実行制限や AppArmor、 SELinuxなどの ツールに よる セキュリティ環境の 変更や、 呼び出しの ブロックなどが 挙げられます。
6. unsafeとcgoの使用には注意する C言語と 同様、 Goでも ポインター型の 変数を 使えますが、 意図しない、あるいは 悪意の ある 副作用から 開発者を 守る ため、 厳格な 型安全性が 設けられています。 Cでは 型が 割り当てられていない void*ポインターを 定義できます。 Goで 同様の ことを するには、 型安全性の 制約を 破る unsafe標準パッケージを 使います。 unsafeは メモリに 直接アクセスできる ため、 一般に Goの ドキュメントでも 使用が 推奨されていません。 ユーザーデータと 組み合わせると、 攻撃者が Goの メモリ安全性を 破る 可能性が あります。
同様に 注意が 必要なのが cgoです。 Goアプリケーションに 任意の Cライブラリを 統合できる 強力な コマンドですが、 強力な ツールと 同様、 cgoは 細心の 注意を 払って 使う 必要が あります。 安全でない 言語で 書かれた 外部依存先が、 すべてを 正しく 処理していると 信頼する ことになる ためです。 外部コードに バグや 悪意ある 処理が 潜んでいても、 Goの メモリ安全機能では 保護できません。 cgoは、 ビルド時に CGO_ENABLED=0を 設定するだけで 無効に できます。 明示的に 必要でない 場合は、 通常 これが 安全な 選択肢です。 最近の Goライブラリの 多くは 純粋な Goコードで 書かれています。
7. リフレクション Goは 静的型付けの 言語であり、 変数の 型は 重要です。 実行時に、 変数の 型や 値に 関する 情報を コード内で 参照する 必要が 生じることもあります。 Goには、 任意の 型を 持つ 変数の 型や値を 調べたり 操作したりできる `reflect`パッケージが あります。 たとえば、 変数が 特定の 型かどうか、または 特定の プロパティや 関数を 持つかどうかを 確認できます。
リフレクションは 便利な 一方で、 Goコードの 実行時型エラーの リスクを 高めます。 リフレクションで 取得した 変数を、 許可されていない 方法で 変更しようとすると (たとえば、 構造体の 設定できない 値を 設定しようとすると)、 コードは パニックを 起こします。 また、 コードの 流れや、 参照される さまざまな 型の 種類、 値の 種類を 把握するのが 難しくなる 場合が あります。 さらに、 リフレクションで 取得した 型や 値を 扱う 際には、 型アサーションが 必要に なることがあります。 コードが 分かりにくくなり、 実行時エラーに つながる 可能性も あります。
リフレクションは 強力な ツールですが、 Goの 型システムと インターフェースの 仕組みを 踏まえると、 予期しない 問題を 簡単に 引き起こす 可能性が ある ため、 使用は 最小限に と どめるべきです。
8. コンテナの攻撃対象領域を最小化する Goアプリケーションの 多くは 外部依存関係を 持たず、 コンテナで 実行されるよう 設計されています。 その ため、 いくつかの イメージ構築手法を 使い、 利用可能な ファイルシステムを 最小限に しましょう。 簡単な 方法の 一つは、 マルチステージ Dockerfileを 使って ビルドステージで アプリケーションを ビルドし、 デプロイ用イメージには scratchベースイメージを 使う ことです。
次の Dockerfileの 例を 見てみましょう。
Dockerfileに 馴染みが ない 方の ために 説明すると、 Dockerfileは ほぼ すべての OCIイメージビルドで イメージを 構築する ために 使える 手順 書です。 詳しくは こちら を ご覧ください。 この 例は マルチステージDockerfileで、 ビルドステージと 最終的な ランタイムイメージステージの 2つに 分かれています。
ステージ1、1~12行目:ビルドステージ 公式の golang:1.15ベースイメージから 開始し、 この ステージで 環境変数を 設定して Goアプリケーションを ビルドします。 この ステージが 完了すると、 buildと いう ラベルの 付いた 一時イメージが キャッシュされ、 後で 参照できるようになります。
ビルドに 渡している 環境変数や 引数の 意味が 気に なるかもしれません。
GOPATH=””: この 変数 (
golang:1.15ベースイメージで 設定済み)を クリアします。 Go Modulesを 使う 場合は 不要です。
CGO_ENABLED=0:cgoを 無効に します (上記セクション6を 参照)。
GOOS=linux:Linuxオペレーティングシステム向けに ビルドする ことを 明示します。
GOARCH=amd66:amd64(Intel) アーキテクチャ向けに ビルドする ことを 明示します。
-trimpath:バイナリから ファイルシステムの パス情報を 削除します。
ldflag -s:シンボルテーブルと デバッグ情報を 省略します。
ldflag -w:DWARFシンボルテーブルを 省略します。
これらの 設定を 組み合わせると、 ほぼ 最小限の バイナリファイルを ビルドできます。 ただし、 アプリケーションに よっては 適さない 設定も ある ため、 必要に 応じて 選択してください。
ステージ2、13~10行目:ランタイムイメージステージ この ステージでは、 静的バイナリと /etc/passwdファイルを buildステージから 空の scratchファイルシステム に コピーし、 適切な 所有権と コンテナ 起動時に 実行する コマンドを 指定します。
この イメージを ビルドするには、 Dockerfileと 同じ ディレクトリで 次を 実行します。
注: ビルド行の 最後に ある . は 重要です。 ビルドシステムに、 Dockerfileと 参照される その 他の ファイルの 場所を 伝えます。
生成される イメージの ファイルシステムには、 アプリケーションと mobyユーザーを 記載した passwdファイルの 2つだけが 含まれます (ユーザー名は 重要ではありません。 コンテナ内で rootと して 実行したくないためです)。 攻撃者に 悪用される 可能性の ある sh、psなどの ファイルは 含まれません。 もちろん、 アプリケーションの 動作に 他の ファイルが 必要な 場合 は、 それらを 含めるか、 実行時に マウントしてください。
Goセキュリティチートシートを ダウンロード !
Capture the Flagを 始めよう オンデマンドの バーチャル入門ワークショップを 見て、 Capture the Flagの 課題の 解き方を 学びましょう。