Nanobanana
763 字
4 分鐘
你的 Go Binary 正在洩漏 Secret:strings 指令的威力與防禦
一行指令,撈出所有 Secret
最近在研究一個用 Go 寫的容器化應用時,發現了一件有趣的事:所有編譯時注入的敏感資訊,用 strings 一行就能全部撈出來。
strings ./app | grep -E "secret|password|answer|token"不只是 secret 本身,連 build 指令都會洩漏:
build -ldflags="-s -w -X main.secret=my-super-secret-key"為什麼會這樣?
Go 常用 -ldflags "-X" 在編譯時注入變數值:
go build -ldflags="-X main.secret=my-super-secret-key -X main.version=v1.0.0" -o appvar secret stringvar 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 是 Go 的 binary 混淆工具,-literals flag 會把字串常量加密,runtime 才解密。
實測一下:
# 正常編譯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# 正常版:全部曝光$ strings app-normal | grep secretmy-super-secret-key-ldflags="-X main.secret=my-super-secret-key"
# garble 版:寫在 source code 的字串消失了,但...$ strings app-garble | grep secretmy-super-secret-key # 還是在!結果:
| 來源 | 正常 build | garble -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 注入:
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:
garble -literals build -ldflags="-s -w" -o app實測 strings 完全撈不到任何東西。
同場加映:Docker Image 也會洩漏
這不只是 binary 的問題。Docker image 也有類似的安全盲區:
docker history 洩漏 build 參數
$ docker history --no-trunc <image># ENV SECRET=xxx、ARG TOKEN=xxx 全部看得到COPY 再 rm 檔案仍在 layer 裡
COPY secret.txt /app/secret.txtRUN rm /app/secret.txt # 沒用,前一層還在用 docker save 匯出 image,解壓後就能從舊 layer 提取被「刪除」的檔案:
docker save <image> -o image.tartar 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 stageDocker registry image 可被 pull 私有 registry + 最小權限結論
strings 是逆向工程最基本的第一步,對未混淆的 binary 幾乎是萬能的。但很多開發者(包括之前的我)從來沒想過自己的 binary 裡藏了什麼。
一個簡單的檢查:對你的 production binary 跑一次 strings,看看會不會嚇一跳。
你的 Go Binary 正在洩漏 Secret:strings 指令的威力與防禦
https://geminixiang.xyz/posts/go-binary-secrets-leak/