游客发表
某天深夜,调洞安全群里有人甩出一张截图:某大厂的试信公开API返回了一段goroutine堆栈,里面赫然写着/usr/local/go/src/net/http/server.go:1234。息泄群里瞬间炸锅——这不是露漏内部IP,也不是调洞测试环境,是试信线上对外服务的真实响应。Go的息泄pprof调试接口,就这么裸奔在公网上。露漏
这不是调洞孤例。我随手用Shodan搜了一下content-type: text/plain; charset=utf-8加goroutine关键字,试信能匹配到上万台暴露的息泄Go服务。其中不少还开着/debug/pprof/heap、露漏/debug/pprof/profile这些敏感端点。调洞攻击者只要curl一下,试信就能拿到你服务的息泄完整内存快照、CPU采样数据、甚至所有活跃协程的调用栈。
问题出在哪?Go标准库的net/http/pprof默认注册了/debug/pprof/*路由,但很多开发者只记得在main.go里import _ "net/http/pprof",却忘了给这个mux加上鉴权。更隐蔽的是,有些框架(比如gin)封装了pprof,但文档没强调默认不校验来源IP。于是生产环境里,pprof端口和业务端口共用同一个Listener,谁都能访问。
泄露的后果不是“看到几行日志”那么简单。heap profile会暴露你内存里所有的字符串、指针结构,包括数据库连接串、用户token、密钥片段。我见过一个案例,某团队把Redis密码存在全局变量里,结果heap dump里明文可见。CPU profile则能暴露你的业务逻辑热点——攻击者据此判断哪些函数值得逆向,甚至通过协程栈还原出完整的调用链,摸清你的微服务拓扑。
更讽刺的是,Go官方文档明确写了“pprof端点不应暴露在公网”,但现实是很多云厂商的默认安全组规则只封了22、3306这类常见端口,对8080、9090这种高段位端口完全放行。开发者以为“没人知道这个端口”,可扫描器早就把全网IP扫了个遍。
修复方案不复杂。最直接的是在pprof路由外面包一层中间件,校验来源IP或者加Basic Auth。如果你用的是标准库,可以手动注册路由而不是用空导入:
mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", authMiddleware(pprof.Index)) mux.HandleFunc("/debug/pprof/cmdline", authMiddleware(pprof.Cmdline)) // ... 其他端点同理 但更推荐的做法是干脆别在生产环境启用pprof。非要监控性能,用独立的metrics服务(比如Prometheus),别把调试接口和业务接口混在一起。如果图省事,至少给pprof单独开一个端口,只绑定内网IP——这样公网扫描器根本摸不到。
还有个细节很多人忽略:即使你禁用了/debug/pprof,如果框架内部用了expvar或者自定义的调试路由,也可能泄露变量。我见过一个项目,开发者把pprof关了,却忘了/debug/vars还开着,照样把内存统计和自定义业务计数器全吐了出来。
最后说个真实教训。某创业公司被脱库,溯源发现攻击者就是从pprof的heap dump里提取到了数据库密码。事后复盘,他们的代码评审清单里压根没有“检查调试端点是否暴露”这一项。安全这东西,往往毁在最不起眼的默认配置上。你写代码时图方便加的那行import,可能就是你系统最薄弱的突破口。
友情链接