第一章:Go语言脚本木马的威胁全景与应急响应概览
Go语言因其静态编译、跨平台免依赖、高隐蔽性等特点,正被攻击者广泛用于构建无文件内存驻留型木马、横向移动载荷及C2通信模块。相较于传统Shell脚本或PowerShell恶意代码,Go编译生成的二进制体积小(常低于3MB)、无运行时依赖、可绕过基于签名与行为白名单的EDR检测,已成为APT组织与勒索软件团伙的新宠。
常见攻击载体与执行特征
- 通过钓鱼邮件附件投递伪装为PDF工具或系统补丁的Go编译二进制;
- 利用CI/CD流水线劫持植入恶意构建脚本,污染开源Go项目发布包;
- 以内存反射方式加载加密的Go字节码(如使用
github.com/elastic/go-seccomp-bpf混淆syscall调用链); - 进程名常伪装为
update_service、log_agent等合法服务名,且父进程多为bash、sh或systemd,规避进程树异常检测。
应急响应关键动作
发现可疑Go木马后,应立即隔离主机并提取内存镜像与磁盘二进制样本。使用strings -n8 <binary>快速提取硬编码C2地址、API密钥及解密密钥;运行以下命令识别Go运行时特征:
# 检查是否为Go编译二进制(含runtime·goexit符号)
readelf -s <malware_binary> | grep -q "runtime\.goexit" && echo "Likely Go binary" || echo "Not Go"
# 提取Go版本与构建信息(需安装go-dump工具)
go-dump -f <malware_binary> --sections=".gosymtab,.gopclntab" 2>/dev/null | head -10
典型IOC识别维度
| IOC类型 | 示例值 | 检测建议 |
|---|---|---|
| 文件哈希 | sha256:9a3f...e8c1(常见于Go 1.21+构建) |
对比VirusTotal与Go恶意样本库 |
| 网络连接模式 | HTTP POST至/api/v1/sync(无TLS证书验证) |
抓包分析User-Agent与Body熵值 |
| 内存特征 | .text段中存在runtime.mstart调用序列 |
使用Volatility3插件goheap |
Go木马常启用-ldflags="-s -w"剥离调试信息,增大逆向难度,因此内存取证与网络流量分析在初始响应阶段比静态反编译更具实效性。
第二章:Go编译型木马的深度逆向与行为解构
2.1 Go二进制文件特征识别与静态签名提取(理论+strings/die/objdump实战)
Go编译生成的二进制具有显著特征:无动态符号表、含.gopclntab节、字符串常量密集且含/go/src/路径痕迹。
Go二进制典型静态指纹
runtime.main入口符号(非_start).text段中高频出现CALL runtime.morestack_noctxt指令模式- 字符串区包含
buildID、GOROOT、GOEXPERIMENT等关键词
strings 提取关键线索
strings -n 8 ./sample | grep -E "(go\.|/src/|runtime\.|buildID)"
-n 8过滤短噪声;正则聚焦Go运行时标识。输出中若含/src/net/http/server.go:2134,即强Go编译证据。
工具对比表
| 工具 | 优势 | Go特化能力 |
|---|---|---|
strings |
轻量、快速定位文本线索 | ✅ 高效捕获源码路径 |
die |
可视化解析PE/ELF结构 | ✅ 识别.gopclntab节 |
objdump |
反汇编指令级分析 | ⚠️ 需配合 -d -j .text 手动查调用模式 |
graph TD
A[原始二进制] --> B{strings -n 8}
A --> C[die ./sample]
A --> D[objdump -d -j .text]
B --> E[匹配 /src/.*\.go]
C --> F[检测 .gopclntab 节]
D --> G[查找 CALL runtime.*stack]
E & F & G --> H[Go编译置信度 >95%]
2.2 CGO调用链与系统调用劫持路径分析(理论+strace/seccomp trace复现)
CGO 是 Go 程序调用 C 函数的桥梁,其底层依赖 syscall.Syscall 或 runtime.syscall,最终经由 int 0x80(x86)或 syscall 指令(x64)陷入内核——这一路径正是系统调用劫持的关键锚点。
典型 CGO 调用链
- Go 代码调用
C.open(...) - →
cgo生成 stub 进入_cgo_callers栈帧 - → 经
libgcc/libc封装为openat(AT_FDCWD, ...) - → 最终触发
sys_openat内核入口
strace 复现示例
# 编译含 CGO 的程序后追踪
strace -e trace=openat,read,write ./main 2>&1 | grep openat
输出中可见
openat(AT_FDCWD, "/etc/hosts", O_RDONLY)—— 验证 CGO 调用已落地为真实系统调用,且参数经 C ABI 标准化传递。
seccomp 规则匹配示意
| syscall | arch | args (0: path, 1: flags) | action |
|---|---|---|---|
| openat | native | path contains “/proc/” | ERRNO(EPERM) |
graph TD
A[Go: C.open] --> B[cgo stub]
B --> C[libc openat]
C --> D[syscall instruction]
D --> E[seccomp filter]
E -->|match| F[Kernel sys_openat]
E -->|deny| G[Return -EPERM]
2.3 Go runtime goroutine调度器注入机制(理论+gdb调试goroutine栈回溯)
Go runtime 通过 runtime.gogo 和 runtime.mcall 实现 goroutine 栈切换与调度器注入,核心在于保存/恢复 G 的 gobuf(含 SP、PC、G 指针)。
调度器注入关键路径
gosched_m→gopreempt_m→gosave(保存当前 G 栈上下文)schedule()选择新 G 后调用gogo(&g.sched)跳转至其gobuf.pc
gdb 调试栈回溯示例
(gdb) info registers rip rsp rbp
(gdb) x/5i $rip # 查看当前指令流
(gdb) p *(struct g*)$rax # 打印当前 goroutine 结构
$rax通常指向g结构体首地址(runtime·findrunnable返回值),g.sched.sp即该 G 下次恢复的栈顶。
goroutine 切换核心字段对照表
| 字段 | 类型 | 作用 |
|---|---|---|
g.sched.sp |
uintptr | 切换时恢复的栈指针 |
g.sched.pc |
uintptr | 切换后执行的第一条指令地址 |
g.sched.g |
*g | 指向自身,用于 mcall 中重绑定 |
// runtime/asm_amd64.s 中 gogo 的精简逻辑
TEXT runtime·gogo(SB), NOSPLIT, $0
MOVQ g_sched_g(CX), BX // BX = g
MOVQ g_sched_sp(CX), SP // 恢复栈
MOVQ g_sched_pc(CX), BP // BP ← PC(为 traceback 准备)
JMP g_sched_pc(CX) // 跳转执行
g_sched_pc 是 goroutine 暂停或启动时的入口点(如 runtime.goexit 或用户函数起始地址),SP 恢复后即重建完整调用栈帧,使 runtime.gentraceback 可正确遍历。
2.4 Go模块依赖混淆与隐式C2通信建模(理论+httptrace+tcpdump协议还原)
Go 模块依赖图中,replace 与 indirect 标记常被滥用以隐藏恶意依赖路径,导致 go list -m all 输出失真。攻击者可借 httptrace.ClientTrace 注入钩子,在 GotConn, DNSStart, WroteHeaders 等阶段劫持控制流,构造无显式 net/http.NewRequest 的隐蔽请求。
数据同步机制
func hijackTrace() *httptrace.ClientTrace {
return &httptrace.ClientTrace{
DNSStart: func(info httptrace.DNSStartInfo) {
// 域名解析阶段触发隐式C2:编码命令至子域名(如 cmd-abc123.mal.example.com)
},
GotConn: func(info httptrace.GotConnInfo) {
// 连接建立后立即发送预置TLS ALPN或SNI载荷,绕过常规HTTP日志
},
}
}
该 trace 钩子不发起新请求,仅利用标准库生命周期事件注入侧信道;DNSStart 参数含原始查询名,可提取Base32编码指令;GotConnInfo.Reused 字段揭示连接复用状态,辅助判断C2会话活跃性。
协议还原关键字段对照表
| tcpdump 过滤点 | 对应 httptrace 阶段 | 可还原字段 |
|---|---|---|
tcp[20:2] == 0x0101 |
GotConn | TLS ClientHello SNI |
udp port 53 and (ip[2:2] > 0) |
DNSStart | Query name (base32) |
tcp[((tcp[12:1] & 0xf0) >> 2):4] |
WroteHeaders | HTTP/2 HEADERS frame |
graph TD
A[go.mod replace github.com/x → ./mal] --> B[go build -ldflags=-s]
B --> C[运行时加载 httptrace 钩子]
C --> D[tcpdump捕获SNI/DNS]
D --> E[ALPN+DNS+Header三源协议还原]
2.5 K8s节点上下文逃逸路径:从kubelet.sock到containerd-shim提权(理论+kubectl debug+nsenter实操)
kubelet.sock 权限边界与攻击面
/var/run/kubelet.sock 是 kubelet 的本地 Unix 域套接字,默认权限为 srw-rw----,属组 kube。若容器以 securityContext.runAsGroup: kube 启动,即可直连调用 pods/{uid}/exec —— 这是逃逸的第一跳。
容器运行时提权链
# 通过 kubectl debug 挂载宿主机 PID 命名空间
kubectl debug node/<NODE> -it --image=ubuntu:22.04 \
--share-processes --copy-default-env=false
参数说明:
--share-processes强制共享宿主 PID namespace;--copy-default-env=false避免污染环境变量。此时ps aux可见全部宿主进程,包括containerd-shim。
nsenter 直接接管 shim 进程
# 查找 containerd-shim 进程并进入其命名空间
PID=$(pgrep -f "containerd-shim.*<pod-id>" | head -n1)
nsenter -t $PID -m -u -i -n -p /bin/bash
逻辑分析:
nsenter利用目标进程的/proc/$PID/ns/*文件复用其 mount、UTS、IPC、net、pid 命名空间;-p参数确保新 shell 具备完整进程视图,可直接读写/proc/1/root/下的宿主文件系统。
提权路径关键依赖表
| 组件 | 必需条件 | 攻击者视角风险等级 |
|---|---|---|
| kubelet.sock | 容器属组包含 kube 或 socket 权限宽松 |
⚠️⚠️⚠️ |
| containerd-shim | 进程未启用 --no-new-privileges |
⚠️⚠️ |
| kubectl debug | cluster-admin 或 nodes/debug RBAC |
⚠️⚠️⚠️⚠️ |
graph TD
A[容器内进程] -->|1. 调用 kubelet.sock exec| B[kubelet 创建临时 pod]
B -->|2. 启动 containerd-shim| C[shim 进程持有宿主 rootfs 挂载]
C -->|3. nsenter 复用其命名空间| D[获得宿主 root 权限上下文]
第三章:K8s集群中Go木马的横向定位与动态取证
3.1 基于eBPF的进程血缘图实时构建(理论+bpftrace+tracee-ebpf部署验证)
进程血缘图(Process Lineage Graph)是可观测性的核心抽象,通过捕获 fork/exec/exit 等系统调用链,构建带时序与父子关系的有向图。
核心原理
eBPF 程序在内核态挂载 tracepoint/syscalls/sys_enter_fork、sys_enter_execve 等钩子,提取 pid, ppid, comm, argv[0] 等字段,经 ringbuf 零拷贝推送至用户态。
快速验证:bpftrace 示例
# 捕获 execve 调用并输出进程谱系快照
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_execve {
printf("[%d → %d] %s %s\n", pid, ppid, comm, str(args->filename));
}'
逻辑分析:
tracepoint:syscalls:sys_enter_execve在 exec 系统调用入口触发;pid/ppid提供父子关系锚点;str(args->filename)安全解引用用户态路径字符串;输出格式直接支撑血缘边(ppid) → (pid)构建。
tracee-ebpf 部署对比
| 工具 | 启动方式 | 血缘粒度 | 输出格式 |
|---|---|---|---|
| bpftrace | 单行命令 | 进程级 | 控制台流式 |
| tracee-ebpf | docker run ... |
进程+文件+网络 | JSON with parent_pid, process_id, ancestors |
graph TD
A[execve syscall] --> B[eBPF tracepoint]
B --> C{Extract: pid/ppid/comm}
C --> D[Ringbuf]
D --> E[Userspace Builder]
E --> F[Directed Edge: ppid → pid]
3.2 Go程序内存镜像dump与pprof堆栈反向映射(理论+gcore+go tool pprof解析)
Go 程序运行时的内存镜像(core dump)是诊断崩溃、死锁与内存泄漏的关键原始数据。gcore 可生成符合 ELF 格式的完整进程快照,而 go tool pprof 能结合 Go 运行时符号信息,将采样地址反向映射为源码函数调用栈。
内存快照捕获流程
# 以 PID 12345 为例,生成带 Go 符号的 core 文件
gcore -o core.golang 12345
gcore通过/proc/PID/mem读取进程地址空间,生成core.golang.12345;需确保目标进程未启用prctl(PR_SET_DUMPABLE, 0),且用户有读取/proc/PID/权限。
pprof 反向映射核心命令
# 加载 core 文件并解析 goroutine 堆栈(需二进制含调试符号)
go tool pprof -symbolize=executable ./myapp core.golang.12345
-symbolize=executable强制使用可执行文件中的 DWARF 信息解符号;若缺失调试信息,需编译时添加-gcflags="all=-N -l"。
| 工具 | 输入依赖 | 输出能力 |
|---|---|---|
gcore |
运行中 Go 进程 PID | 原始内存镜像(ELF core) |
go tool pprof |
core + 带调试信息的 binary | 符号化 goroutine / heap / CPU 栈 |
graph TD
A[Go 进程运行] --> B[gcore 捕获内存镜像]
B --> C[pprof 加载 binary + core]
C --> D[地址→函数名→源码行号反向映射]
D --> E[定位阻塞点/泄漏根因]
3.3 容器运行时层异常syscall聚类检测(理论+runc events+auditd规则联动)
容器运行时层的 syscall 异常行为(如 mknod、ptrace、mount 非预期调用)是逃逸与提权的关键信号。需融合 runc 的 events 流式输出与内核 auditd 的细粒度 syscall 捕获,实现跨源聚类。
数据协同架构
graph TD
A[runc --events] -->|JSON event: pid, container_id, cmd| B[Syscall Collector]
C[auditd: -a always,exit -F arch=b64 -S mount,mknod,ptrace] -->|AUDIT_SYSCALL| B
B --> D[聚类引擎:DBSCAN on syscall+pid+container_id+time_window]
auditd 规则示例
# /etc/audit/rules.d/container-syscall.rules
-a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=4294967295 -k container_escape
-a always,exit -F arch=b64 -S ptrace -F pid=1 -F auid>=1000 -k runc_ptrace_abuse
-F auid>=1000过滤普通用户会话;-k标签便于ausearch -k快速归集;-F pid=1捕获对 init 进程的非法调试,典型 runc 沙箱逃逸特征。
聚类维度对照表
| 维度 | runc events 提供 | auditd 提供 | 联动价值 |
|---|---|---|---|
| 容器标识 | ✅ container_id | ❌ | 关联宿主机 syscall 到具体容器 |
| 系统调用类型 | ❌ | ✅ syscall name | 补全行为语义 |
| 时间戳精度 | ⚠️ ms | ✅ ns | 支持微秒级时序对齐聚类 |
第四章:自动化清除、加固与溯源闭环实践
4.1 一键终止恶意Go进程并冻结其cgroup资源(理论+kubectl exec+crictl stop+systemd-cgtop验证)
当集群中检测到异常高CPU占用的Go二进制进程(如挖矿木马),需快速隔离而非仅杀进程——因其常通过fork()再生或利用容器逃逸复现。
核心策略:容器层→运行时层→cgroup层三级阻断
kubectl exec -n <ns> <pod> -- pkill -f 'miner|golang':初步终止用户态进程(⚠️易被绕过)crictl stop $(crictl ps --name <malicious-pod> -q):强制停止容器,触发OCI运行时清理systemd-cgtop -P | grep golang+systemd-run --scope -p MemoryLimit=1M -p CPUQuota=1% -- bash -c 'sleep infinity':验证cgroup冻结效果
关键验证命令
# 冻结对应pod的kubepods.slice子cgroup(以pod UID为标识)
POD_UID=$(kubectl get pod <pod> -o jsonpath='{.metadata.uid}')
echo "FROZEN" > /sys/fs/cgroup/systemd/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod${POD_UID}.slice/freezer.state
此操作将
freezer.state设为FROZEN,使该cgroup下所有进程暂停调度且不可fork新线程。crictl stop已释放容器命名空间,但残留cgroup需手动冻结以防资源复用。
| 工具 | 作用层级 | 是否阻断cgroup资源 |
|---|---|---|
pkill |
进程级 | ❌ |
crictl stop |
容器运行时级 | ⚠️(延迟释放) |
freezer.state |
cgroup v2 控制组 | ✅(即时冻结) |
graph TD
A[发现恶意Go进程] --> B[kubectl exec pkill]
B --> C[crictl stop容器]
C --> D[定位pod对应cgroup路径]
D --> E[写入freezer.state=FROZEN]
E --> F[systemd-cgtop验证RSS/CPU=0]
4.2 自动化清理Go木马残留:临时目录、定时任务、systemd用户服务(理论+find+xargs+systemctl –user list-units实操)
Go编译的木马常驻内存后,惯用/tmp、/var/tmp存放动态载入模块,并通过crontab -e或systemd --user实现持久化。
识别可疑临时文件
# 查找72小时内创建、权限宽松且含Go特征的可执行文件
find /tmp /var/tmp -type f -mtime -3 -perm /u+x,g+x -name "*go*" -o -name "*.so" -print0 | \
xargs -0 file | grep -i "ELF.*Go"
-print0与xargs -0规避空格路径问题;file命令识别Go编译二进制(含Go build ID或runtime符号)。
枚举用户级持久化入口
systemctl --user list-units --type=service --state=running --no-pager
输出中重点关注非标准命名(如update-agent.service)、WantedBy=default.target且无上游包管理记录的服务。
| 清理目标 | 检查命令 | 风险特征 |
|---|---|---|
| 临时文件 | find /tmp -name "*go*" -delete |
创建时间异常新、无属主 |
| 用户定时任务 | crontab -u $USER -l |
调用/tmp/下未知二进制 |
| systemd用户服务 | systemctl --user status <unit> |
ExecStart=指向临时路径 |
graph TD
A[发现可疑进程] --> B{检查临时目录}
B --> C[find + xargs 定位Go载荷]
C --> D[systemctl --user list-units]
D --> E[比对服务路径与/tmp关联性]
E --> F[原子化清理:rm + systemctl --user stop/disable]
4.3 基于Go build ID与符号表的精准溯源命令(理论+readelf -n + go tool buildid + 集群镜像仓库比对)
Go 二进制文件在编译时自动生成唯一 build ID,嵌入 ELF 注释段(.note.go.buildid),成为不可篡改的指纹标识。
核心工具链协同机制
readelf -n <binary>:提取.note段原始 build ID 数据go tool buildid <binary>:解析并标准化输出(含主 ID 与辅助 ID)- 镜像仓库(如 Harbor/ECR)通过
oci manifest关联二进制层 digest 与 build ID 元数据
示例:从运行中 Pod 逆向定位镜像源
# 1. 从容器内提取二进制 build ID
readelf -n /app/server | grep -A2 'Build ID'
# 输出示例:
# Note section ['.note.go.buildid'] (576 bytes):
# Owner Data size
# Go 0x40
# 2. 标准化解析(更可靠)
go tool buildid /app/server
# → "sha1-8a3f...b9e2/5d2c...a1f7"(主/辅 ID 对)
readelf -n 直接读取 ELF 注释段原始字节,适用于无 Go 环境的调试;go tool buildid 自动校验格式并分离主辅 ID,是 CI/CD 流水线自动比对的首选。
集群级溯源流程
graph TD
A[Pod 中提取 binary] --> B{readelf -n 或 go tool buildid}
B --> C[获取 build ID 字符串]
C --> D[查询镜像仓库 buildID 索引表]
D --> E[定位构建流水线 & Git Commit]
| 工具 | 优势 | 适用场景 |
|---|---|---|
readelf -n |
无需 Go 运行时,跨平台通用 | 容器内最小化调试环境 |
go tool buildid |
自动校验、支持 -w 写入新 ID |
构建时注入可追踪元数据 |
| 仓库索引服务 | 支持 build ID ↔ image digest ↔ commit 多维关联 | SRE 故障归因与合规审计 |
4.4 溯源结果结构化输出与MITRE ATT&CK映射(理论+jq+attack-flow生成+可视化导入)
溯源分析产出的原始日志需统一转为STIX 2.1兼容的JSON结构,再映射至MITRE ATT&CK框架。核心流程包含三阶段:标准化→映射→可视化。
结构化转换(jq示例)
# 将Elasticsearch导出的扁平日志转为STIX observable对象
jq -n \
--arg ip "$SRC_IP" \
--arg technique "T1059.004" \
'{type: "ipv4-addr", spec_version: "2.1", value: $ip} |
{type: "attack-pattern", id: "attack-pattern--\($technique|gsub("\\."; "-"))",
external_references: [{source_name: "mitre-attack", external_id: $technique}]}'
逻辑说明:--arg注入动态变量;gsub将ATT&CK技术ID中的点号转为连字符以符合STIX ID规范;输出双对象便于后续组合成bundle。
MITRE映射关键字段对照
| STIX字段 | ATT&CK对应项 | 示例值 |
|---|---|---|
external_references[].external_id |
技术ID | T1059.004 |
labels |
tactic名称 | ["execution"] |
created_by_ref |
分析工具标识 | identity--a1b2c3... |
attack-flow生成与导入
graph TD
A[原始溯源JSON] --> B[jq结构化]
B --> C[attack-flow CLI生成.flow文件]
C --> D[导入ATT&CK Navigator或Mandiant ADVANTAGE]
第五章:防御体系演进与Go安全编码基线建议
防御纵深从边界走向运行时
传统WAF+防火墙的边界防御模型在微服务与容器化场景中持续失效。某金融客户在K8s集群中遭遇横向移动攻击,攻击者利用未鉴权的/debug/pprof端点获取内存快照,继而提取JWT密钥。该案例直接推动其将Go服务默认禁用所有pprof路由,并通过http.StripPrefix强制拦截/debug/路径——这已成为其CI流水线中的静态检查硬性规则(go vet -tags=security自定义检查器)。
内存安全边界强化实践
Go虽无传统指针算术漏洞,但unsafe.Pointer与reflect.Value滥用仍可绕过类型系统。2023年CVE-2023-24538即源于net/http中Header字段的不安全反射赋值。生产环境应全局禁止unsafe包导入,通过gosec扫描器配置如下规则:
gosec -exclude=G103,G104 ./...
其中G103禁用unsafe,G104强制错误检查——该策略使某电商核心订单服务的panic率下降76%。
依赖供应链可信链构建
使用go list -json -m all生成模块图谱后,需结合Sigstore的cosign验证关键依赖签名:
flowchart LR
A[go.mod] --> B[cosign verify --key cosign.pub github.com/gorilla/sessions]
B --> C{签名有效?}
C -->|是| D[注入构建环境变量 GO111MODULE=on]
C -->|否| E[阻断CI流水线]
敏感数据零残留机制
HTTP请求体中的密码、令牌必须在首行处理后立即清零。以下为经审计的合规写法:
func handleLogin(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
defer func() {
for i := range body { body[i] = 0 } // 显式覆写内存
}()
// 解析JSON后立即调用 runtime.KeepAlive(body)
}
运行时防护能力集成
在Kubernetes DaemonSet中部署eBPF探针,实时拦截execve调用中含/tmp/路径的恶意进程启动。某支付网关通过此方案捕获到利用os/exec.Command动态加载恶意SO库的0day利用链。
| 风险类型 | Go原生缓解方案 | 补充加固措施 |
|---|---|---|
| SQL注入 | database/sql参数化 |
OpenTelemetry SQL语句脱敏 |
| SSRF | http.DefaultClient禁用重定向 |
自定义http.RoundTripper白名单 |
| XML外部实体 | encoding/xml禁用Parse |
替换为xmlquery只读解析器 |
日志输出安全红线
所有日志语句必须通过log/slog的With方法显式标注敏感字段,且禁止拼接用户输入:
// ❌ 危险示例
log.Printf("user %s login from %s", username, r.RemoteAddr)
// ✅ 合规写法
slog.With("user_id", userID).Info("login_attempt", "ip", r.RemoteAddr)
该规范已嵌入团队代码模板引擎,新服务初始化即生成带敏感字段过滤的日志中间件。
容器镜像最小化实践
采用多阶段构建剥离调试工具链,最终镜像仅保留/app二进制与/etc/ssl/certs:
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o /app .
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /app /app
CMD ["/app"]
某区块链节点服务据此将镜像体积压缩至12MB,CVE暴露面减少89%。
