---
title: "你的 Go Binary 正在洩漏 Secret：strings 指令的威力與防禦"
url: https://geminixiang.com/posts/go-binary-secrets-leak/
description: "用 strings 一行指令就能從 Go binary 中撈出所有敏感資訊。從 ldflags -X 的明文注入問題，到 garble 混淆的實測與限制，完整解析 Go binary 的安全盲區。"
published: 2026-04-09
category: "DevOps"
tags: ["Go", "Docker", "資安", "逆向工程"]
---

# 你的 Go Binary 正在洩漏 Secret：strings 指令的威力與防禦

## 一行指令，撈出所有 Secret

最近在研究一個用 Go 寫的容器化應用時，發現了一件有趣的事：所有編譯時注入的敏感資訊，用 `strings` 一行就能全部撈出來。

```bash
strings ./app | grep -E "secret|password|answer|token"
```

不只是 secret 本身，連 build 指令都會洩漏：

```
build  -ldflags="-s -w -X main.secret=my-super-secret-key"
```

## 為什麼會這樣？

Go 常用 `-ldflags "-X"` 在編譯時注入變數值：

```bash
go build -ldflags="-X main.secret=my-super-secret-key -X main.version=v1.0.0" -o app
```

```go
var secret string
var version string

func main() {
    fmt.Println(secret)  // my-super-secret-key
    fmt.Println(version) // v1.0.0
}
```

這在注入版本號、commit hash 時很常見，CI/CD pipeline 幾乎都這樣用。但問題是：**`-X` 注入的值會以明文存在 binary 的 data section**，`strings` 指令會掃描 binary 中所有可列印的字串序列，直接就能看到。

## garble 能解決嗎？

[garble](https://github.com/burrowers/garble) 是 Go 的 binary 混淆工具，`-literals` flag 會把字串常量加密，runtime 才解密。

實測一下：

```bash
# 正常編譯
go build -ldflags="-X main.secret=my-super-secret-key" -o app-normal

# garble 編譯
garble -literals build -ldflags="-X main.secret=my-super-secret-key" -o app-garble
```

```bash
# 正常版：全部曝光
$ strings app-normal | grep secret
my-super-secret-key
-ldflags="-X main.secret=my-super-secret-key"

# garble 版：寫在 source code 的字串消失了，但...
$ strings app-garble | grep secret
my-super-secret-key    # 還是在！
```

結果：

| 來源 | 正常 build | garble -literals |
|------|-----------|-----------------|
| 寫在 source code 的字串 | 明文曝光 | 成功隱藏 |
| `-ldflags -X` 注入的值 | 明文曝光 | **仍然曝光** |

**garble 在 compiler 階段混淆字串，但 `-X` 是 linker 階段直接寫入的，garble 介入不了。**

## 正確做法

### Secret：不進 binary，runtime 注入

```go
func main() {
    // 從環境變數取得，binary 裡完全沒有 secret
    secret := os.Getenv("SECRET")
    if secret == "" {
        log.Fatal("SECRET not set")
    }
}
```

部署時透過環境變數 / K8s Secret / Vault 注入：

```bash
SECRET=my-super-secret-key ./app
```

### 版本號等非敏感值：byte array + garble

如果連版本號都不想被 `strings` 撈到，避免用 `-X` 注入或直接字串常量，改用 byte array 讓 garble 可以混淆：

```go
func getVersion() string {
    return string([]byte{'v', '1', '.', '0', '.', '0'})
}
```

搭配 garble：

```bash
garble -literals build -ldflags="-s -w" -o app
```

實測 `strings` 完全撈不到任何東西。

## 同場加映：Docker Image 也會洩漏

這不只是 binary 的問題。Docker image 也有類似的安全盲區：

### docker history 洩漏 build 參數

```bash
$ docker history --no-trunc <image>
# ENV SECRET=xxx、ARG TOKEN=xxx 全部看得到
```

### COPY 再 rm 檔案仍在 layer 裡

```dockerfile
COPY secret.txt /app/secret.txt
RUN rm /app/secret.txt    # 沒用，前一層還在
```

用 `docker save` 匯出 image，解壓後就能從舊 layer 提取被「刪除」的檔案：

```bash
docker save <image> -o image.tar
tar xf image.tar -C layers
# 逐層搜尋，一定找得到
```

## 完整防禦清單

```
層級              問題                    正確做法
─────────────────────────────────────────────────────────
Go binary        -X ldflags 明文注入      環境變數 / Vault runtime 注入
Go binary        字串常量明文              garble -literals 混淆
Dockerfile       ENV / ARG 洩漏           Multi-stage build，不在最終 image 留痕
Docker layer     COPY + rm 假刪除         Multi-stage build，敏感檔只存在 builder stage
Docker registry  image 可被 pull          私有 registry + 最小權限
```

## 結論

`strings` 是逆向工程最基本的第一步，對未混淆的 binary 幾乎是萬能的。但很多開發者（包括之前的我）從來沒想過自己的 binary 裡藏了什麼。

**一個簡單的檢查：對你的 production binary 跑一次 `strings`，看看會不會嚇一跳。**

---

## About the author

**Ying Xiang Zhao** (geminixiang) — DevOps Engineer, Taipei, Taiwan.

Ying Xiang Zhao（geminixiang）是住在台北的 DevOps 工程師，工作橫跨雲原生基礎設施、CI/CD、ML 模型部署與全端開發。這個部落格記錄 infra、資安與 AI coding agent 的實戰筆記；作品包含開源專案 mikan（把 AI coding agent 帶進 Slack / Telegram / Discord）與 tiny-agent（用四種語言實作的極簡 coding agent），瀏覽器內的影片編輯器 Studio，並經營旅遊分帳工具 SentriPay。

**Open to work** — DevOps Engineer / Platform Engineer / Site Reliability Engineer / AI Infrastructure Engineer · Taipei, Taiwan / Remote. Résumé: https://geminixiang.com/about/ (JSON: https://geminixiang.com/resume.json)

Topics: DevOps, Cloud Native, Kubernetes, Docker, Terraform, Google Cloud, CI/CD, Security, AI coding agents, ML model deployment, TypeScript, Python

Projects:
- [mikan](https://github.com/geminixiang/mikan) — Self-hosted AI coding agent for Slack, Telegram, Discord and GitHub with per-conversation sandboxes
- [tiny-agent](https://github.com/geminixiang/tiny-agent) — Minimal AI coding agents in TypeScript, Go, Python and Rust, plus a book on how they work
- [SentriPay](https://sentripay.com) — Travel expense splitter with multi-currency conversion and shareable, live-synced trip ledgers
- [Studio](https://studio.geminixiang.com) — Browser-based video editor built on WebCodecs

[About](https://geminixiang.com/about/) · [Résumé](https://geminixiang.com/about/) · [LinkedIn](https://linkedin.com/in/geminixiang/) · [GitHub](https://github.com/geminixiang) · [Twitter](https://x.com/geminixiang)
