In this article
Golangにおけるサーバーサイドテンプレートインジェクションを理解する
開発者の間では比較的まれなセキュリティ脆弱性として、サーバーサイドテンプレートインジェクション(SSTI)があります。しかし、ファイルインクルージョンやクロスサイトスクリプティング(XSS)、さらにはコードインジェクション攻撃など、深刻化するセキュリティリスクにつながる可能性があります。
Golangでバックエンドとフロントエンドの両方を扱うフルスタックWeb開発を行う場合、安全でないコードを書いてしまい、SSTIにつながるミスを犯すリスクが高まります。
この記事では、いくつかのセキュリティ脆弱性と安全でないコードパターンを含む、Golangの簡単なWebアプリケーションを紹介します。ぜひ、どこに問題があるか見つけてみてください。続いてSSTIの具体例を詳しく見ながら、攻撃者がこの脆弱性をどのように悪用するのか、またGolangに組み込まれているhtml/templateライブラリがどのように使われているのかを解説します。
GolangでWebアプリケーションを構築する
まずは、Golangで小さなWebアプリケーションを構成する要素を、順を追って作成していきましょう。次のコンポーネントを使用します。
Golang用のGin Webフレームワークでは、オープンソースのGoライブラリ
github.com/gin-gonic/ginを使用します。SQLite 3データベースでは、オープンソースのGoライブラリ
database/sqlとSQLiteデータベースドライバー「github.com/mattn/go-sqlite3」を使用します。宣言したテンプレートを使ってHTMLレスポンスを生成する、Go組み込みの
html/templateライブラリ。
Golangプロジェクトの依存関係を宣言する
まず、app.goで表されるWebアプリケーションで、プロジェクトにインポートする依存関係を宣言します。
package main
import (
"database/sql"
"fmt"
"html/template"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)Golangの構文に馴染みがない場合、これらは依存関係をインポートするための名前やURLです。具体的には、SQLite3ドライバーではアンダースコア(_)構文を使います。これは、インポートしたパッケージをコード内で明示的に使用しないものの、アプリケーションのコード内に存在することを前提とする間接的なコードで必要となることを示します。
GolangのWebルーターと関数ハンドラー
次に、このシンプルなWebアプリケーションのコード構成を大まかに見てみましょう。全体像を把握するため、使用する関数を以下に示します。
package main
import (...)
func search(c *gin.Context) {...}
func main() {
r := gin.Default()
r.GET("/search", search)
r.Run(":8080")
}上記のコードスニペットでは、Golangプログラムのエントリーポイント関数main()を宣言しています。Gin Webフレームワークを使って新しいルーターを定義し、続いて/searchにHTTP GETエンドポイントを設定して、定義したsearch関数を呼び出します。最後に、Webアプリケーションを起動し、ポート8080でリクエストを処理します。
データベースの検索結果を表示する
ここでは、URLからクエリ文字列を取り出してユーザーの検索クエリを解析し、それを使ってデータベースのレコードを検索・取得します。取得したデータをHTMLテンプレートに整形し、レスポンスとして返します。
注:
以下のコードスニペットには多数のセキュリティ脆弱性が含まれており、教育目的でのみ使用してください。学習目的の演習以外でコピー&ペーストしたり、使用したりしないでください。
search関数ハンドラーの全コードは次のとおりです。
func search(c *gin.Context) {
query := c.Query("q")
db, err := sql.Open("sqlite3", "posts.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query(fmt.Sprintf("SELECT * FROM posts WHERE title LIKE '%%%s%%'", query))
if err != nil {
c.String(http.StatusInternalServerError, "Error executing query")
return
}
defer rows.Close()
tmpl, err := template.New("search").Parse(fmt.Sprintf(`
<h2>Results for query "%s":</h2>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
`, query))
if err != nil {
c.String(http.StatusInternalServerError, "Error creating template")
return
}
var results []struct {
ID int
Title string
Content string
}
for rows.Next() {
var id int
var title string
var content string
err := rows.Scan(&id, &title, &content)
if err != nil {
log.Fatal(err)
}
results = append(results, struct {
ID int
Title string
Content string
}{id, title, content})
}
err = tmpl.Execute(c.Writer, results)
if err != nil {
c.String(http.StatusInternalServerError, "Error executing template")
return
}
}GolangのWebアプリケーションにおけるセキュリティ脆弱性
データベース検索を行う上記のコードには、本番環境のアプリケーションに含めるべきではない、安全でないコードがあります。
特に、どのようなセキュリティ脆弱性に気づきましたか?
Snyk IDE extension(無料です!)をインストールしていれば、ファイル保存のショートカットを押した直後に、厄介なセキュリティ脆弱性を数秒で検出できます。

検索ハンドラーに潜むSQLインジェクション
検索ハンドラーのfunc search()関数にある該当コードを簡単に見てみましょう。query変数には、URLのクエリ文字列から取得したユーザー入力が読み込まれていることがわかります。
query := c.Query("q")その後、サニタイズされず、安全でない方法でデータベースクエリに渡されています。プレースホルダーを使ったパラメーター化クエリのバインドも行われていません。
func search(c *gin.Context) {
query := c.Query("q")
db, err := sql.Open("sqlite3", "posts.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query(fmt.Sprintf("SELECT * FROM posts WHERE title LIKE '%%%s%%'", query))
if err != nil {
c.String(http.StatusInternalServerError, "Error executing query")
return
}
defer rows.Close()
// ... rest of the program code ...
}この脆弱なコードは、攻撃者にSQLインジェクション攻撃の機会を与えます。
検索ハンドラーに潜むSSTI
先ほどのfunc search()関数ハンドラーの続きでは、次のコードでHTMLテンプレートを作成しています。
tmpl, err := template.New("search").Parse(fmt.Sprintf(`
<h2>Results for query "%s":</h2>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
`, query))query変数を%sプレースホルダー経由でhtml/templateライブラリに渡す方法には、少なくとも次の2つの明らかなセキュリティリスクがあります。
クロスサイトスクリプティング(XSS)- 攻撃者がHTML要素をそのまま指定し、安全にエンコードされずにHTMLテンプレートへ挿入できてしまいます。
サーバーサイドテンプレートインジェクション(SSTI)- 攻撃者が
{{ .Title | unsafeHTML }}のようなテンプレート構文をそのまま入力できます。この構文はhtml/templateでサポートされており、評価されてしまいます。
Golangのテンプレートライブラリにおけるコードインジェクションについて
Golangに組み込まれているhtml/templateライブラリには、os.system()など、危険性のあるユーザーランド関数へのアクセスを制限するセキュリティ対策が組み込まれています。
ただし、このパッケージでは、ユーザー入力を受け取り、結果を返すカスタム関数を定義できます。そのため、開発者は独自のロジックを定義し、Golang標準ライブラリやGolangエコシステムのサードパーティーパッケージに含まれる機密性の高いAPIやユーザーランド関数を使用できます。
次のカスタムテンプレート関数を仮定してみましょう。
func dangerousFunction(input string) string {
output, err := exec.Command("bash", "-c", input).Output()
if err != nil {
return "Error executing command"
}
return string(output)
}
tmpl, err := template.New("search").Funcs(template.FuncMap{"dangerous": dangerousFunction}).Parse( /* ... */ )注:
上記のカスタム関数は危険なため、学習目的以外では使用しないでください。
先ほどの例に戻り、URLのqクエリ文字列からのユーザー入力がquery変数に入り、テンプレートに渡されることを考えます。クエリパラメーターの値が次のような場合、その入力は有効なhtml/templateの特殊構文として展開されます。{{ dangerous "rm -rf /tmp" }}。
Golangアプリケーションのセキュリティを強化する
Golangのセキュリティ脆弱性について見てきた内容をまとめましょう。GoにはWebアプリケーションの構築に役立つhtml/templateパッケージのような強力なツールが用意されていますが、セキュリティへの配慮を忘れてはいけません。
SQLインジェクション、SSTI、XSSなどの潜在的な脆弱性を理解し、対策することで、コードのセキュリティをさらに高め、安全なGolangアプリケーションの実現に近づけます。
さらに、VS CodeやIntelliJなどの開発ツールでSnyk IDE extensionを使うと、コードを書きながらセキュリティ脆弱性を検出し、すばやく修正できるため、より安全なアプリケーションコードの作成に役立ちます。
Golangで安全に開発するための簡単なヒント
SQLインジェクション:SQLインジェクションの脆弱性を防ぐため、必ずパラメーター化クエリを使用してください。
SSTI:特にカスタムテンプレート関数やサードパーティーライブラリを使用する場合は、SSTIのリスクに注意してください。変数を安全に展開するようにしてください。
データのサニタイズ:テンプレート、SQLクエリ、その他アプリケーションのあらゆる箇所で使用する前に、すべてのユーザー入力をサニタイズしてください。ユーザー入力をそのまま、または安全でない方法で連結しないようにしましょう。
html/templateのセキュリティ:html/templateライブラリのセキュリティ機能を活用してください。template.HTML型などは慎重に、必要な場合に限って使用しましょう。そうしないと、XSSの脆弱性につながる恐れがあります。
定期的なセキュリティ監査:Snykを使ってアプリケーションのセキュリティ監査を定期的に実施し、Golangコード、Goモジュール、サードパーティーおよびオープンソースの依存関係に潜む潜在的なセキュリティ脆弱性を特定して対処しましょう。