This page looks best with JavaScript enabled

hertz path-traversal vulnerability; echo open redirect vulnerability

 ·  ☕ 5 min read · 👀... views

漏洞及修复

先上链接

https://github.com/cloudwego/hertz/issues/228
https://github.com/cloudwego/hertz/pull/229

起因是昨晚睡前看到hertz issue列表有一个默认通过XFF获取ClientIP的漏洞,这很容易被伪造。Issue中提及Gin社区的相似讨论(那叫一个混乱与精彩)。然后我想着看看会被会有啥别的漏洞。

由于之前就看过多个http框架的源码,并且之前2022Bytecamp的项目就是hertz添加QUIC和HTTP3支持,所以我对hertz源码可以说是十分了解了。

http框架常规的漏洞审计思路就是文件、模板、路由这几个点,我这次就是上来直奔文件去的。

在fs的handleRequest方法中注释可以看到ctx.Path()获取的path是经过normalize的,找了一下有个normalizePath函数,里面对path多了多步处理可以说十分完善。我觉得代码写的有一定借鉴意义,所以摘取如下

 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
59
func normalizePath(dst, src []byte) []byte {
	dst = dst[:0]
	dst = addLeadingSlash(dst, src)
	dst = decodeArgAppendNoPlus(dst, src)

	// remove duplicate slashes
	b := dst
	bSize := len(b)
	for {
		n := bytes.Index(b, bytestr.StrSlashSlash)
		if n < 0 {
			break
		}
		b = b[n:]
		copy(b, b[1:])
		b = b[:len(b)-1]
		bSize--
	}
	dst = dst[:bSize]

	// remove /./ parts
	b = dst
	for {
		n := bytes.Index(b, bytestr.StrSlashDotSlash)
		if n < 0 {
			break
		}
		nn := n + len(bytestr.StrSlashDotSlash) - 1
		copy(b[n:], b[nn:])
		b = b[:len(b)-nn+n]
	}

	// remove /foo/../ parts
	for {
		n := bytes.Index(b, bytestr.StrSlashDotDotSlash)
		if n < 0 {
			break
		}
		nn := bytes.LastIndexByte(b[:n], '/')
		if nn < 0 {
			nn = 0
		}
		n += len(bytestr.StrSlashDotDotSlash) - 1
		copy(b[nn:], b[n:])
		b = b[:len(b)-n+nn]
	}

	// remove trailing /foo/..
	n := bytes.LastIndex(b, bytestr.StrSlashDotDot)
	if n >= 0 && n+len(bytestr.StrSlashDotDot) == len(b) {
		nn := bytes.LastIndexByte(b[:n], '/')
		if nn < 0 {
			return bytestr.StrSlash
		}
		b = b[:nn+1]
	}

	return b
}

本来是感觉十分完善不可能有漏洞,最关键的一步是打下断点debug启动 console输出path并且burp repeater不断尝试

/../会被过滤/./会被删除 但是在测试中 \..\还是正常显示,然后就自然想到windows fs可以\..\访问 便找到了漏洞。

hertz-path_traversal

最终的修复方案也十分简单 就是把所有反斜杠替换成正斜杠即可。

第一版修改如下 但是会存在已修改数据重复判断问题,而且函数对原有src进行修改存在争议,此外还有攻击面127.0.0.0:8888/..%5c..%5cgo.sum 这个攻击可以成功是因为src的反斜杠urlencode了没有替换掉 后续decodeArgAppendNoPlus会urldecode然后交由open函数/..\..\go.sum 所以任然攻击成功

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func normalizePath(dst, src []byte) []byte {
	// replace all backslashes with forward slashes
	for {
		n := bytes.Index(src, bytestr.StrBackSlash)
		if n < 0 {
			break
		}
		src[n] = '/'
	}

	dst = dst[:0]
	dst = addLeadingSlash(dst, src)
	dst = decodeArgAppendNoPlus(dst, src)
    ...
}

第二版修改如下,避免上文说到存在的问题。教训是过滤要尽量靠近函数return处或者危险函数点(此处是return后交由fs的open函数)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func normalizePath(dst, src []byte) []byte {
	dst = dst[:0]
	dst = addLeadingSlash(dst, src)
	dst = decodeArgAppendNoPlus(dst, src)

	// replace all backslashes with forward slashes
	if filepath.Separator == '\\' {
		for i := range dst {
			if dst[i] == '\\' {
				dst[i] = '/'
			}
		}
	}
    ...
}

单测如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func TestParsePath(t *testing.T) {
	t.Parallel()

	testParsePath(t, "/../../../../../foo", "/foo")
	testParsePath(t, "/..\\..\\..\\..\\..\\foo", "/foo")
	testParsePath(t, "/..%5c..%5cfoo", "/foo")
}

func testParsePath(t *testing.T, path, expectedPath string) {
	var u URI
	u.Parse(nil, []byte(path))
	parsedPath := u.Path()
	if string(parsedPath) != expectedPath {
		t.Fatalf("Unexpected Path: %q. Expected %q", parsedPath, expectedPath)
	}
}

怎么说呢 有时候写代码真的得多想一点并且搭配一定的测试,上文说IndexByte会存在重复判断的问题,但是也存在SIMD的优化,最终测试下来在有反斜杠的情况下IndexByte确实性能不如for-range,但是没有反斜杠的情况下因为第一次IndexByte就会break所以性能会更加,详情见pr comment。

还有就是UT在本地跑通过但是没有在github action跑通,因为linux不会对反斜杠进行替换。最终版本UT如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func TestParsePathWindows(t *testing.T) {
	t.Parallel()

	testParsePathWindows(t, "/../../../../../foo", "/foo")
	testParsePathWindows(t, "/..\\..\\..\\..\\..\\foo", "/foo")
	testParsePathWindows(t, "/..%5c..%5cfoo", "/foo")
}

func testParsePathWindows(t *testing.T, path, expectedPath string) {
	var u URI
	u.Parse(nil, []byte(path))
	parsedPath := u.Path()
	if filepath.Separator == '\\' && string(parsedPath) != expectedPath {
		t.Fatalf("Unexpected Path: %q. Expected %q", parsedPath, expectedPath)
	}
}

番外

笑死 当我想看看别的框架有没有这个洞的时候 发现fasthttp六个月前修复了

https://github.com/valyala/fasthttp/issues/1226

https://github.com/valyala/fasthttp/commit/6b5bc7bb304975147b4af68df54ac214ed2554c1

深夜有感写了个小作文笑死。

They just repeat the logic, there is no need to write duplicate code.
The point is that backslash also work in windows. So, essentially, we just need to replace the backslash with forward slash, by which we, to some extent, limit the separator.
In my limited point of view, my fix is better.

好家伙又看了一眼echo 又一个cve

https://github.com/labstack/echo/issues/2259

第二天早上醒来发现echo一小时就改好并release了新版本 反应是真的快

https://github.com/labstack/echo/pull/2260/files

看修复学习安全代码实践,主要是sanitizeURI函数删掉了URI前置的//\\等,同echo/pull/1775

1
2
3
4
5
6
7
8
func sanitizeURI(uri string) string {
	// double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri
	// we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash
	if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') {
		uri = "/" + strings.TrimLeft(uri, `/\`)
	}
	return uri
}

并添加了MustSubFS函数提供安全保证

filepath.ToSlash函数,这个逻辑跟我的上面hertz的漏洞fix逻辑是一样的。

1
2
3
4
5
6
func ToSlash(path string) string {
	if Separator == '/' {
		return path
	}
	return strings.ReplaceAll(path, string(Separator), "/")
}

CVE编号申请

相信能看到本文的大部分人都看过下面这篇文章,影响力可以说非常大了,自从这个文章发布后文中的github仓库已经全都是安全issue了 都被拿来刷CVE

https://www.freebuf.com/168362.html

文中可以说是讲的非常详细了,这里只做一点总结:如果你的洞是CNA的项目,那么根据CNA要求做;你过你的洞是github开源项目,那么直接到https://cveform.mitre.org/填写表格即可。

表格填写都大差不差 按照模板来就行,(PS:忽略我表格中的英语语法错误😀)

hertz-path_traversal-cvereq

统计了一下国内CNA只有下面几个 主要是操作系统厂商和硬件厂商

Partner Scope Program Role Organization Type Country*
Alibaba, Inc. Projects listed on its Alibaba GitHub website (https://github.com/alibaba) only CNA Vendors and Projects China
Dahua Technologies Dahua issues only CNA Vendors and Projects China
Hangzhou Hikvision Digital Technology Co., Ltd. All Hikvision Internet of Things (IoT) products (http://www.hikvision.com/en/product_1.html) including cameras and digital video recorders (DVRs) CNA Vendors and Projects China
Huawei Technologies Huawei issues only CNA Vendors and Projects China
OpenAnolis OpenAnolis issues only CNA Vendors and Projects China
openEuler openEuler issues only CNA Vendors and Projects China
openGauss Community openGauss issues only CNA Vendors and Projects China
OpenHarmony openHarmony issues only CNA Vendors and Projects China
OPPO Mobile Telecommunication Corp., Ltd. OPPO devices only CNA Vendors and Projects China
Unisoc (Shanghai) Technologies Co., Ltd. Unisoc issues only CNA Vendors and Projects China
Vivo Mobile Communication Co., Ltd. Vivo issues only CNA Vendors and Projects China
Xiaomi Technology Co., Ltd. Xiaomi issues only CNA Vendors and Projects China
ZTE Corporation ZTE products (https://www.zte.com.cn/global/products) only CNA Vendors and Projects China
Share on

ruokeqx
WRITTEN BY
ruokeqx