763 字
4 分鐘
你的 Go Binary 正在洩漏 Secret:strings 指令的威力與防禦
2026-04-09
Ying Xiang Zhao
Ying Xiang Zhao

一行指令,撈出所有 Secret#

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

Terminal window
strings ./app | grep -E "secret|password|answer|token"

不只是 secret 本身,連 build 指令都會洩漏:

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

為什麼會這樣?#

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

Terminal window
go build -ldflags="-X main.secret=my-super-secret-key -X main.version=v1.0.0" -o app
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 sectionstrings 指令會掃描 binary 中所有可列印的字串序列,直接就能看到。

garble 能解決嗎?#

garble 是 Go 的 binary 混淆工具,-literals flag 會把字串常量加密,runtime 才解密。

實測一下:

Terminal window
# 正常編譯
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
Terminal window
# 正常版:全部曝光
$ 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 # 還是在!

結果:

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

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

正確做法#

Secret:不進 binary,runtime 注入#

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

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

Terminal window
SECRET=my-super-secret-key ./app

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

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

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

搭配 garble:

Terminal window
garble -literals build -ldflags="-s -w" -o app

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

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

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

docker history 洩漏 build 參數#

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

COPY 再 rm 檔案仍在 layer 裡#

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

docker save 匯出 image,解壓後就能從舊 layer 提取被「刪除」的檔案:

Terminal window
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,看看會不會嚇一跳。

你的 Go Binary 正在洩漏 Secret:strings 指令的威力與防禦
https://geminixiang.xyz/posts/go-binary-secrets-leak/
作者
Ying Xiang Zhao
發佈於
2026-04-09
許可協議
CC BY-NC-SA 4.0