はじめに
「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」23日目の記事は、バックエンド開発で絶対に避けて通れないセキュリティについてです。
開発を始めたばかりの頃は、「とにかく動くものを作りたい!」という気持ちでいっぱいなんですが、デプロイを意識すると、情報漏洩など起こらないようにしないとな、と一気に身が引き締まります。
この記事では、GoでWebアプリケーションを開発する際に、最低限押さえておきたい基本的なセキュリティ対策を5つピックアップし、それぞれの脆弱性の概要と具体的な対策を解説します。
- GoでAPI開発を始めたが、セキュリティについて何をすれば良いか分からない方
- SQLインジェクションやXSSといった言葉は知っているが、Goでどう対策するのか知りたい方
- より安全なWebアプリケーションの構築に関心のあるすべての方
この記事で、代表的なWebアプリケーションの脆弱性を理解し、Goの標準ライブラリやエコシステムを活用して、それらに対する基本的な防御策を実装できるように頑張りましょう!
SQLインジェクション対策
ユーザーからの入力をそのままSQLクエリに連結してしまうと、不正なSQL文を注入(インジェクション)され、データベースを不正に操作される危険があります。
GoにおけるSQLインジェクション対策には、プレースホルダの徹底が有効です。
悪いコード例:
// 絶対にやってはいけない例!
name := r.URL.Query().Get("name")
// ユーザー入力 `name` を直接文字列連結している
rows, err := db.Query("SELECT * FROM users WHERE name = '" + name + "'")
// もし name が "'; DROP TABLE users; --" だったら…?
対策: database/sqlパッケージでクエリを実行する際は、必ずプレースホルダ(?や$1など、ドライバによって異なる)を使い、実際の値は関数の引数として渡します。
良いコード例:
import "database/sql"
// ...
func getUser(db *sql.DB, name string) (*User, error) {
var user User
// POINT: PostgreSQLの場合は$1, $2... というプレースホルダを使う
// MySQLの場合は ? を使う
row := db.QueryRow("SELECT id, name, email FROM users WHERE name = $1", name)
// Scanが内部で安全に処理してくれる
if err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {
return nil, err
}
return &user, nil
}
プレースホルダを使うことで、データベースドライバがユーザー入力を単なる「値」として安全に扱い、SQL文の一部として解釈されるのを防いでくれます。Goでは、SQLを組み立てる際はプレースホルダを使うことを常に徹底しましょう。
XSS対策
クロスサイトスクリプティング(XSS)は、ユーザーの入力に悪意のあるスクリプト(例: <script>alert('XSS')</script>)を埋め込み、それを表示した別のユーザーのブラウザ上で実行させる攻撃です。
GoにおけるXSS対策は、html/templateによる自動エスケープが有効です。
対策: Goの標準ライブラリhtml/templateは、HTMLコンテキストを認識し、埋め込む文字列を自動で安全にエスケープ(無害化)してくれます。
コード例:
package main
import (
"html/template"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// ユーザー入力を取得
userInput := r.URL.Query().Get("comment")
if userInput == "" {
userInput = "<script>alert('XSS Attack!');</script>"
}
// POINT: html/template を使う
tmpl, err := template.New("t").Parse(`
<h1>ユーザーコメント</h1>
<p>{{.}}</p>
`)
if err != nil {
http.Error(
w,
err.Error(),
http.StatusInternalServerError
)
return
}
// POINT: テンプレートを実行してレスポンスを書き出す
// ここで自動的にエスケープされる!
tmpl.Execute(w, userInput)
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
このサーバーにアクセスすると、ブラウザには<script>タグがそのまま文字列として表示され、スクリプトは実行されません。
ちなみに、text/templateパッケージはHTMLのエスケープを行わないため、ユーザー入力をHTMLに出力する際はhtml/templateを使うことが有効です。
CSRF対策
クロスサイトリクエストフォージェリ(CSRF)は、ログイン済みのユーザーが、悪意のあるサイトに仕掛けられたリンクをクリックするなどして、意図しないリクエスト(例: 退会処理)を強制的に送信させられる攻撃です。
GoにおけるCSRF対策は、gorilla/csrfミドルウェアの導入が有効です。
対策: サーバーは、正規のフォームにのみ秘密のトークン(CSRFトークン)を埋め込みます。POSTリクエストなどを受け取った際に、その正規のトークンが一緒に送信されてくるかを確認することで、意図しないリクエストを防ぎます。
実装: Goではgorilla/csrfというデファクトスタンダードなライブラリを使うと、ミドルウェアとして簡単にCSRF対策を導入できます。
go get github.com/gorilla/csrf
コード例:
package main
import (
"html/template"
"log"
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// GETリクエスト用のハンドラ(フォームを表示)
r.HandleFunc("/signup", func(w http.ResponseWriter, r *http.Request) {
tmpl, _ := template.New("t").Parse(`
<form action="/signup" method="POST">
<!-- csrf.TemplateFieldがCSRFトークンを含むhidden inputを生成 -->
{{ .csrfField }}
<input type="text" name="name">
<button type="submit">登録</button>
</form>
`)
tmpl.Execute(w, map[string]interface{}{
csrf.TemplateTag: csrf.TemplateField(r),
})
}).Methods("GET")
// POSTリクエスト用のハンドラ(フォーム処理)
r.HandleFunc("/signup", func(w http.ResponseWriter, r *http.Request) {
// このハンドラが呼ばれる時点で、ミドルウェアがトークンを検証済み
w.Write([]byte("登録成功!"))
}).Methods("POST")
// CSRF保護ミドルウェアを設定
// 32バイトの認証キーは本番では環境変数などから読み込むこと
csrfMiddleware := csrf.Protect(
[]byte("32-byte-long-auth-key-should-be-secret"),
csrf.Secure(false), // 開発用にHTTPでも動作させる(本番ではtrueに)
)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", csrfMiddleware(r)))
}
安全なHTTPヘッダーの設定
ブラウザのセキュリティ機能を有効にするため、レスポンスに特定のHTTPヘッダーを含めることが推奨されています。
これらはミドルウェアで一元的に設定するのが便利です。
例えば以下のようなものが挙げられます。
-
X-Frame-Options: クリックジャッキング攻撃を防ぐため、<iframe>内での表示を制御します。DENY(常に拒否)が最も安全です。 -
X-Content-Type-Options:nosniffを設定し、ブラウザがContent-Typeを誤解釈してスクリプトを実行するのを防ぎます。 -
Content-Security-Policy: 読み込み可能なリソース(スクリプト、画像など)をホワイトリスト形式で厳密に制御し、XSSなどのリスクを大幅に低減します。
コード例(ミドルウェア):
func securityHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
// CSPは要件に合わせて細かく設定する必要がある
// w.Header().Set("Content-Security-Policy", "default-src 'self'")
next.ServeHTTP(w, r)
})
}
// main関数内
// http.ListenAndServe(":8080", securityHeadersMiddleware(mux))
サーバーのタイムアウト設定
Goのhttp.ListenAndServeをそのまま使うと、各種タイムアウトが設定されません。
これにより、接続を長時間維持してサーバーのリソースを枯渇させる攻撃に対して脆弱になります。
対策: http.Server構造体を明示的に作成し、タイムアウトを設定します。
コード例:
import (
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Secure World!"))
})
// POINT: タイムアウトを設定
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second, // リクエストヘッダの読み取りタイムアウト
WriteTimeout: 10 * time.Second, // レスポンス書き込みのタイムアウト
IdleTimeout: 120 * time.Second, // Keep-Alive時の次のリクエスト待ちタイムアウト
}
log.Println("Server starting on :8080")
log.Fatal(srv.ListenAndServe())
}
これにより、不正なリクエストがサーバーリソースを占有し続けるのを防ぐことができます。
おわりに
この記事では、基本的な脆弱性とそのセキュリティ対策について具体例を交えて解説しました。
セキュリティ対策って聞くと、なんだかすごく難しいイメージがついてまわります。
でも、実際に学んでみると、一つ一つの地道な設定の積み重ねが、私たちのアプリケーションを守ってくれるんだと実感しました。
Goは、html/templateのように標準でセキュリティを意識した機能を提供してくれる部分がある一方で、タイムアウト設定のように開発者自身が意識しなければいけない、という部分も残されています。
言語やフレームワークに「お任せ!」にするのではなく、開発者自身が「なぜこの設定が必要なのか?」という脆弱性の背景を理解することが本当に大切なんだと、改めて痛感しました。
セキュリテに関しては、パスワードのハッシュ化などさらに学ぶべきことがたくさんあるので、これからもリサーチしていきたいと感じました。
最後までお読みいただき、ありがとうございました!
この「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」では、GoやJavaを使い、APIの作り方、データベースとの接続、テストやDockerといった気になったバックエンド技術の基本を振り返った学びを共有しています。
ぜひ他の記事もチェックして、筆者がこのひとりアドカレを完遂することができるか、確認してみてください(^^)
学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025
それでは、明日の「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」の記事もお楽しみに!
参考文献