Revel框架的一处DoS问题
0x00 前言
Revel 是一个基于Golang
的灵活的Web框架,大量应用了Golang
的反射特性,使得其可以类似于Django
那样快速地建立一个网站。前段时间在翻阅Revel
框架的文档时,发现其某个特性可能存在DoS问题,故对该特性的相关源码进行了审计,发现了一处非常容易利用的DoS问题,利用单个请求即可打挂Revel
的服务器。
好在该DoS的利用是有条件的,当且仅当网站使用了Revel
框架获取slice
类型的参数时才会触发。
0x01 分析
Revel
框架为开发者提供了许多有用的特性,其中一个特性允许开发者直接获取数组类型的数据。如当用户访问http://example.com/?keys[]=1&keys[]=2
时,开发者可直接将keys
参数视为slice
类型(Golang
中对数组的封装)。
该特性在Revel
中是通过反射+Binder实现的,其中专门用于处理slice
类型的函数如下。
func bindSlice(params *Params, name string, typ reflect.Type) reflect.Value {
// Collect an array of slice elements with their indexes (and the max index).
maxIndex := -1
numNoIndex := 0
sliceValues := []sliceValue{}
// Factor out the common slice logic (between form values and files).
processElement := func(key string, vals []string, files []*multipart.FileHeader) {
// ...
// 省略相关用于处理单个slice元素的内容
}
for key, vals := range params.Values {
processElement(key, vals, nil)
}
for key, fileHeaders := range params.Files {
processElement(key, nil, fileHeaders)
}
resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex)
for _, sv := range sliceValues {
if sv.index != -1 {
resultArray.Index(sv.index).Set(sv.value)
} else {
resultArray = reflect.Append(resultArray, sv.value)
}
}
return resultArray
}
该函数的的作用主要用于解析数组类型的参数,进行类型转换并确定slice
的最大下标maxIndex
,最终申请一个足够大的slice
来容纳这些内容。
其中,maxIndex
是由最大数组下标决定的,且没有大小限制。也就是说此处只要maxIndex
足够大,那么Revel
自动调用的reflect.MakeSlice()
所申请的内存空间可能会超过实体内存,触发OOM Killer
或内存换页。
由于processElement()
函数中会自动解析传入参数的下标信息,maxIndex
的值也变得容易控制,只需访问类似于下面的网址,即可制造一个足够大的maxIndex
。
https://example.com/?keys[1234567890]=1
访问上述网址后,服务器CPU和内存使用率将会迅速飙升,如图所示,最终导致Revel
进程结束。
0x02 利用条件
一般情况下,bindSlice
不会被自动调用,当且仅当controller
的代码中获取了slice
类型的参数时,该漏洞才能利用,如下面的两种情况。
func (c App) Test1(name []string) revel.Result {
return c.Render(name)
}
func (c App) Test2() revel.Result {
var name []string
c.Params.Bind(&name, "name")
return c.Render(name)
}
0x03 总结
当开发者利用Revel
框架获取slice
类型的数据时,攻击者只需发起一次请求,即可使相关Web服务宕机,危害不大不小。因此,如果网站中使用了Revel
框架,又恰好用到了数组类型的参数,应当重新审视自己的业务,是否非得使用数组类型的参数。