「Go セキュアコーディングクイズ」in Go Conference 2025 解説

Go Conference 2025 に協賛し、ブースを出展させていただきました!

セキュリティエンジニアの xryuseix です。今回は先日開催された Go Conference 2025 の振り返りと、ブースで出題したセキュアコーディングクイズの解説をお届けしたいと思います!

弊社は Go Conference 2025 にプラチナ “Go”ld スポンサーとして協賛いたしました。ブースではクイズを掲載しており、ステッカーやアイマスクなどのノベルティをプレゼントしていました! 2 日間で延べ 300 人以上の方にご訪問いただき、様々な方と Go 言語についての知見やセキュア開発における課題などについて、有意義な議論を交わすことができました。

Go Conference 2025 には弊社から代表取締役 CEO の井手康貴(@niconegoto)、セキュリティエンジニアのfujitargzxryuseix、ソフトウェアエンジニアのOJI、その他にマーケ・DevRel メンバーを合わせて、合計 6 名が参加しました。CEO の井手は Day2 のスポンサーセッションにて「Go に育てられ開発者向けセキュリティ事業を立ち上げた僕が今向き合う、AI × セキュリティの最前線」という講演をさせていただきました!

「Go セキュアコーディングクイズ」の概要

「Go セキュアコーディングクイズ」は会場に展示していたパネルの他に、当日お配りしていた解説チラシの裏に掲載されている【宿題クイズ】の合わせて 2 問をご用意しました。会場でクイズに正解された方にはバウムクーヘンをプレゼントしていました!特に、午後の小腹が空いたタイミングではバウムクーヘン獲得を目指してたくさんの方に解いていただきました。

なお、今回の問題はfujitargzxryuseixOJIの 3 人で作問しました。「会社のお金で Go Conference に行きたい!!!」と思っていた正直すぎる 3 人が、会社からチケットを貰う代わりに業務中に作問しました。

問題文

以下の main.go は、簡易的な静的ファイル配信サーバーの実装です。

main.go

package main

import (
    "encoding/base64"
    "fmt"
    "net/http"
    "os"
    "strings"

    "github.com/sirupsen/logrus"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        filePath := r.URL.Query().Get("file")

        
        if strings.Contains(filePath, "..") || strings.HasPrefix(filePath, "/") {
            http.Error(w, "Invalid file path", http.StatusBadRequest)
            return
        }

        
        data, err := os.ReadFile(filePath)
        if err != nil {
            http.Error(w, "File not found", http.StatusNotFound)
            return
        }
        fmt.Fprint(w, base64.StdEncoding.EncodeToString(data))
    })
    logrus.Info("Usage: curl 'http://localhost:8000?file=files/flatt.png'")
    http.ListenAndServe(":8000", nil)
}

このプログラムをビルドした実行ファイルmainや、配信する静的ファイルflatt.pngなどは以下のようなディレクトリに配置されています。

/
├── app
│   ├── files/flatt.png
│   └── main
├── etc/passwd
├── secret_file.png
└── home/user/.ssh/authorized_keys

サーバーは以下の手順で起動します。

$ cd /app && ./main

起動したサーバーに対してcurl 'http://localhost:8000/?file=files/flatt.png'のようにリクエストを送信することでflatt.pngをダウンロードできます。

なお、問題を解く上で必ずしも利用する必要はありませんが、手元でもお試しいただく際は必要に応じて以下の go.mod と Dockerfile もご利用いただければと思います!

go.mod と Dockerfile の内容を展開するにはこちらをクリック

go.mod

module flatt.tech

go 1.24.3

require github.com/sirupsen/logrus v1.9.3

require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

Dockerfile

FROM golang:1.24-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY main.go ./
RUN go build -o main .

FROM alpine:latest

ARG USER_ID="10000"
ARG GROUP_ID="10001"
ARG USER_NAME="user"

RUN addgroup -S -g "${GROUP_ID}" "${USER_NAME}" && adduser -u "${USER_ID}" -G "${USER_NAME}" -D "${USER_NAME}"

RUN mkdir -p /home/${USER_NAME}/.ssh && touch /home/${USER_NAME}/.ssh/authorized_keys

WORKDIR /app

COPY --from=builder /app/main .
RUN echo "secret" > /secret_file.png
RUN mkdir /app/files && echo "flatt-png" > /app/files/flatt.png

EXPOSE 8000

CMD ["./main"]

問 1. このサーバーから、flatt.png以外でダウンロードできてしまうファイルはどれでしょう?

  1. /app/main
  2. /etc/passwd
  3. /secret_file.png
  4. ~/.ssh/authorized_keys

問 2. このサーバーから漏洩する情報はどれでしょう?

  1. ユーザーのログインパスワード
  2. 環境変数
  3. サーバーのアクセスログ
  4. 使用しているライブラリとそのバージョン

解説

ここから下は解説になるので、まだ問題文を読み終えていない方は注意です!









1 問目の解説

このサーバーは静的ファイル配信を想定した実装になっています。
取得するファイルはクエリパラメータで?file=files/flatt.pngのように指定できます。
なお、パストラバーサル対策として、指定されたファイルパスが..を含むか/から始まる場合エラーを返すようになっています。

この問題ではサーバー起動時の実行ディレクトリが問題になります。os.ReadFile()は実行ディレクトリを基準としてファイルを探索するため、このサーバーは起動時の実行ディレクトリである/appを基準としてファイルを探索します。したがって、?file=mainのようなリクエストによって/app/main、すなわち実行ファイル自身を配信してしまいます

以上より、この実装ではmainが取得できるため正答は 1になります。

その他のファイルについては、絶対パス記法はパストラバーサル対策により防がれ、相対パス記法の場合も全て先頭に../を使用する必要があるため同様にパストラバーサル対策で防がれます。
なお、4 の~/.ssh/authorized_keysについては~を利用してパスを記述することで../が不要になるためパストラバーサル対策を迂回可能ですが、その場合~が展開されないため取得できません。Go で~と同等の値を取得するにはos.UserHomeDir()を利用する必要があります。

2 問目の解説

問 1. でmainが取得できることが分かりました。mainは Go の実行ファイルなので、プログラム中で利用されているライブラリとそのバージョン情報を含んでいます*1
下記のコマンドのように実行ファイルをstringsコマンドで文字列として解釈しても良いですし、Go Toolchains を使ってgo version -m ./mainとしても確認できます。

$ curl 'http://localhost:8000?file=main' | base64 -d | strings | grep github.com/sirupsen/logrus@v1.9.3

よって正答は 4になります。

サーバーで利用されているライブラリとそのバージョンについての情報は一見漏洩しても無害なように見えますが、攻撃者にとっては攻撃の手がかりとなりうる重要な情報です。
攻撃者は漏洩した情報をもとに、当該ライブラリで過去に報告された脆弱性があるか確認できます。そして当該バージョンが影響を受ける脆弱性が報告されていた場合、サーバーに対して当該脆弱性を利用した攻撃が実行できる可能性があります。

対策

ファイル配信を行う場合は、秘密情報が含まれるファイルを含むディレクトリからファイルを配信しないようにしてください。

この実装においては、ファイル配信用のディレクトリである /app/filesからファイルを配信するべきです。例えば、ファイル配信向けのハンドラーであるhttp.FileServer()はファイル配信時の基準となるディレクトリを引数で明示的に指定できるため、このような実装を利用しましょう。

セキュリティ診断 AI エージェントの Takumi にもこの問題を解かせてみました!

ちなみに、このクイズは Takumi にも解かせてみました!今回 Takumi は 2 問とも正しい答えを回答してくれています!

以下の Markdown が Takumi が出力した回答です。

# セキュリティ脆弱性分析レポート

## クイズの回答

**問 1 の答え: 1. `/app/main`**

サーバーは `/app` ディレクトリから起動するため、同じディレクトリ内の `main` 実行ファイルに `?file=main` でアクセス可能です。パストラバーサル対策で `..``/` から始まるパスは blocked されているため、他のファイル(`/etc/passwd``/secret_file.png``~/.ssh/authorized_keys`)には相対パスでアクセスできません。

**問 2 の答え: 4. 使用しているライブラリとそのバージョン**

Go の実行ファイルには使用したライブラリの情報が含まれており、`main` ファイルがダウンロードできることで、コンパイル時に使用された `github.com/sirupsen/logrus` などのライブラリとそのバージョン情報が漏洩します。

## 脆弱性の概要

この Go アプリケーションは**不完全なパストラバーサル対策**により、意図しないファイルアクセスが可能な脆弱性を持っています。

## 技術的分析

### 脆弱性のメカニズム

```go
if strings.Contains(filePath, "..") || strings.HasPrefix(filePath, "/") {
    http.Error(w, "Invalid file path", http.StatusBadRequest)
    return
}
```

この対策は以下の理由で不十分です:

- **相対パスの制御不備**: サーバーが `/app` から起動するため、同一ディレクトリ内のファイルには制限なくアクセス可能
- **実行ファイルの露出**: `main` 実行ファイルがダウンロード可能となり、バイナリ解析による情報収集が可能

### アクセス可能なファイル

1. **`files/flatt.png`** - 意図された正常なファイル
2. **`main`** - 実行ファイル(脆弱性により露出)

### 情報漏洩リスク

実行ファイル `main` から以下の情報が取得可能:

- **ライブラリ依存関係**: `github.com/sirupsen/logrus` 等の使用ライブラリ
- **バージョン情報**: コンパイル時の Go バージョンとライブラリバージョン
- **ビルド情報**: コンパイル環境の詳細

## リスク評価

**重要度**: 中程度

- **影響**: システム構成情報の漏洩
- **攻撃難易度**: 低(単純な HTTP リクエストで実行可能)

この情報漏洩により、攻撃者は既知の脆弱性を持つライブラリバージョンを特定し、標的型攻撃を実行する可能性があります。

終わりに

今回 Go Conference 2025で弊社セッション / ブースに足をお運びいただいた皆様も、このブログでクイズに挑戦してくださった皆様もありがとうございました。

本記事でも登場したように、GMO Flatt Securityはセキュリティ診断 AI エージェント「Takumi byGMO」を提供しています。

今回のクイズに完璧に回答したように AI による脆弱性検知能力の向上は目覚ましいものがありますが、Takumi はセキュリティ特化のエージェントととて開発されており、例えば Claude Code と比較しても大きなセキュリティ能力の差が生まれています。

性能比較のベンチマークレポートをダウンロード

月額わずか7万円で DevSecOps を実現するパートナーとして、ぜひご活用ください。OSS支援という形で、OSS開発者向けの無料提供も行っています。

また、世界トップクラスの技術力を有するセキュリティエンジニアによる脆弱性診断・ペネトレーションテストも提供しています。

より高度・網羅的な第三者検証が必要な際にぜひご利用ください。

それでは、ここまでお読みいただきありがとうございました!




Source link

関連記事

コメント

この記事へのコメントはありません。