前言
CVE-2024-41592是Draytek公司核心网关设备中的一个栈溢出漏洞,通过精心构造的报文,可以实现0click RCE。
环境搭建
此漏洞影响Draytek Vigor3910等型号,这些系列的设备上都运行了一个Linux系统,但是他们的主要业务都是位于用qemu启动的sohod64.bin中。具体解包及模拟方法我参考了Catalpa师傅的博客,里面记录的非常详细。我这里的环境就是Catalpa师傅博客里的那个版本。
漏洞分析
CVE公告如下
1 | DrayTek Vigor3910 devices through 4.3.2.6 have a stack-based overflow when processing query string parameters because GetCGI mishandles extraneous ampersand characters and long key-value pairs. |
里面很明确地提到了是GetCGI的时候出现了问题。根据以往经验,一般是指的是在CGI(Common Gateway Interface)环境中处理请求内容的一种方法。一般会通过环境变量去获取到GET请求的QUERY_STRING,通过CONTENT-LENGTH确定POST请求的body,然后把各个键值对分别进行记录。
定位到这个函数也很简单,直接发包来获得一些GET请求的参数,如aa,然后全局搜索相关字符串。大部分情况下这些字符串会被获取变量的函数调用,如GetCGIbyFieldName(buf,”aa”),那么前面与这个buf有关的函数有很大可能就是我们这里的GetCGI。(这里的GetCGIbyFieldName和GetCGI都是后来重命名的)
我们可以很快定位到这个函数,发现他处理GET请求的逻辑非常简单。直接获取环境变量QUERY_STRING。如果QUERY_STRING存在,那么就会以&作为分隔符去分割QUERY_STRING,为每一个键值重新分配内存,并且把键的地址存放到*(_DWORD *)(a2 + 8 * idx),再对字符串进行urldeocde,最后把值的地址存放到*(_DWORD *)(a2 + 8 * idx + 4LL)。
单看这个函数就有一个很明显的问题,没有对a2的大小进行判断,很可能会存在越界写。实际也正是这样,在调用这个函数前也没有对大小进行判断,所有就是这里导致的缓冲区溢出。
漏洞利用
在找到漏洞点之后我们就该考虑如何利用了。这里确实存在栈上的越界写,但是写的内容都是一些堆地址,也就是说内容无法被我们控制,那么就无法进行常规ROP,那又该如何利用呢?由于这个binary是通过qemu启动的,所以没有NX保护,并且虽然我们没有办法控制越界写的内容,但是我们可以控制堆地址里的内容(通过GET请求的键值对控制)。最后发现这里竟然会存在一个天然的ret2shellcode的利用方式。所以我们要做的就是精准控制好覆盖返回地址的那个堆地址里存放了我们想要执行的shellcode即可。
curl发送如下请求看一下栈结构
1 | curl "http://192.168.1.1/cgi-bin/wlogin.cgi?key1111=111&key222=222&key33333333=333&key444444444444=444" |
还需注意的一点是在GetCGI的上层函数(A)返回之前,会释放每个键的指针,并且置零。而AARCH64我们能覆盖到的是上上层函数及函数A的上层函数的返回地址。这就导致我们覆盖的返回地址将变成非法地址。
如何解决这个问题?我们看一下相关函数的实现,它是通过判断*(_DWORD *)(unsigned int)(ptr + 8 * idx)是否为空来结束循环的。如果我们想要在释放覆盖返回地址的堆地址之前使它跳出这个循环,那么就只能想办法找到某个函数,在正常流程中有对栈上变量进行置零的操作。并且需要正好是对某个键的存放位置进行置零,那么就可以保护我们的返回地址不被释放。
因为原作者文章中并没有指出是哪一个cgi可实现这个效果,我们在经过一段时间分析之后找到了一个可用的cgi。这里我也并不准备公开exp,感兴趣的师傅们可以自行尝试。
参考链接
https://wzt.ac.cn/2024/02/19/vigor_3910/
https://www.forescout.com/resources/draybreak-draytek-research/