CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析
漏洞简介
CVE-2018-0296
是思科ASA
设备Web
服务中存在的一个拒绝服务漏洞,远程未认证的攻击者利用该漏洞可造成设备崩溃重启。该漏洞最初由来自Securitum
的安全研究人员Michal Bentkowski
发现,其在博客中提到该漏洞最初是一个认证绕过漏洞,上报给思科后,最终被归类为拒绝服务漏洞。据思科发布的安全公告显示:针对部分型号的设备,该漏洞可造成设备崩溃重启;而针对其他型号的设备,利用该漏洞可获取设备的敏感信息,造成信息泄露。
针对该漏洞,目前已有公开的PoC
脚本,可用于获取设备的敏感信息如用户名,或造成设备崩溃重启。经过实际测试,在公开PoC
中造成该漏洞的关键url
如下。
1 | https://<ip>:<port>/+CSCOU+/../+CSCOE+/files/file_list.json?path=/ |
下面利用思科ASA
设备和已有的PoC
脚本,对该漏洞的形成原因进行分析。
背景知识
在实际对漏洞进行分析的过程中,发现思科ASA
设备的lina
程序中,存在大量的Lua
脚本以及对Lua
api
的调用。为了便于理解,下面对Lua
脚本的相关知识进行简单介绍。
Lua
脚本和C/C++
交互
Lua
是一个小巧的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua脚本
可以很容易被C/C++
代码调用,也可以反过来调用C/C++
的函数,这使得Lua
在应用程序中可以被广泛使用。不仅可作为扩展脚本,也可以作为普通的配置文件,代替XML
、ini
等文件格式,并且更容易理解和维护。
Lua
和C/C++
通信的主要方式是一个虚拟栈,其特点是后进先出。在Lua
中,Lua
栈就是一个struct
,栈的索引可以是正数也可以是负数,其中正数索引1
永远表示栈底,负数索引-1
永远表示栈顶,如下图所示。
Lua
中的栈在stack_init()
函数中创建,其类似于下面的定义。
1 | TObject stack[BASIC_STACK_SIZE + EXTRA_STACK] |
在Lua
中,可以往栈上压入字符串、数值、表和闭包等类型,最后统一用Tobject
这种数据结构进行保存,如下。TObject
结构对应于Lua
中所有的数据类型,是一个{值,类型}
结构,它将值和类型绑在一起。其中用tt
表示value
的类型,value
是一个联合体,共有4个域,说明如下。
p
:可以保存一个指针,实际上指向Lua
中的light userdata
结构n
:保存数值,包括int
、float
等类型b
:保存布尔值gc
:保存需要内存管理垃圾回收的类型如string
、table
、closure
等
1 | // lua 数据类型 |
Lua
栈操作常用api
Lua
中提供了一系列与栈操作相关的api
,常用的api
如下。
1 | // 压入元素 |
环境准备
调试环境搭建
由于该漏洞在不同型号设备上表现的行为不一致,这里分别选取了32位的设备和64位的设备,相关信息如下。其中,前面2个设备用于漏洞分析,设备asav9101
用于补丁分析。
- 真实设备
ASA 5505
,镜像为asa924-k8.bin
,32bit
GNS3
仿真设备,镜像为asav962.qcow2
,64bit
GNS3
仿真设备,镜像为asav9101.qcow2
,64bit
ASA
设备中内置了gdbsever
,但默认不启动。为了对设备进行调试,需要修改镜像文件以启动gdbserver
。同时,由于ASA
设备会对镜像文件进行完整性校验,所以修改后的镜像文件无法直接通过tftp
或ASDM
工具传入设备。ASA
使用CF卡作为存储设备,可以通过用CF卡读卡器直接将镜像写入CF卡中的方式绕过校验,因为ASA
没有对CF中的镜像进行校验。
详细的调试环境搭建和镜像修改等内容可以参考nccgroup
的系列博客.
设备配置
思科ASA设备会在443
端口提供Web服务。笔者在进行测试时,对设备的WebVPN
功能(Clientless SSL VPN
)进行了配置,使得可以访问Web服务,进而触发该漏洞。详细的配置操作可参考思科相关文档。
漏洞分析
环境搭建好后,运行已有的PoC
脚本,针对asa924
设备,会造成敏感信息泄露,而针对asav962
设备,会造成设备崩溃重启。下面基于asav962
设备,重点对拒绝服务漏洞进行分析。
崩溃分析
运行PoC
脚本,在gdb
中捕获到如下错误。可以看到,崩溃点在libc.so.6
库中的strlen()
函数里,由于在0x7ffff497699a
处尝试访问一个非法的内存地址0x13
,故产生Segmentation fault
错误,而rax
的值来源于strlen()
函数的参数。
1 | Thread 2 received signal SIGSEGV, Segmentation fault. |
根据栈回溯信息,查看函数lua_pushstring()
和webvpn_file_name()
,其部分伪代码片段如下。在函数webvpn_file_name()
中,将v1 + 0x13
这个指针作为参数传递给lua_pushstring()
,最终传递给strlen()
函数。崩溃点处访问的非法内存地址为0x13
,说明v1=0
,即在webvpn_file_name()
中lua_touserdata()
返回值为NULL
(也就是0)。
1 | _DWORD *__fastcall lua_pushstring(__int64 a1, const char *a2) |
由前面lua
的相关知识可知,函数lua_touserdata()
用于获取栈底数据。因此,很自然的想法就是分析这个NULL
值是从哪里来的,即在什么地方通过调用lua_pushnil()
往栈上压入了NULL
值。
静态分析
通过查找字符串/+CSCOE+/files/file_list.json
的交叉引用定位到aware_webvpn_content()
函数。在该函数中可以看到有很多请求url
的字符串,同时还包含很多lua
脚本的名称,猜测该函数应该是负责对这些请求进行处理,根据不同的请求url
执行对应的lua
脚本。示例如下。
查看files_list_json_lua
脚本的内容,其主要功能是列出当前路径下的目录或文件,依次调用了Lua
中的OPEN_DIR()
、READ_DIR()
、FILE_NAME()
、FILE_IS_DIR()
等函数。而在aware_addlib_netfs()
函数中,建立了Lua
函数和C
函数之间的对应关系,示例如下。
1 | // Lua函数与C函数对应关系 |
在查看对应的C
函数时,在webvpn_read_dir()
函数中,有一个对lua_pushnil()
函数的调用。根据函数的调用顺序,猜测webvpn_file_name()
函数中获取到的NULL
值来自于这里。
动态分析
根据之前的猜测,尝试在调用lua_pushnil()
处下断点,然后查看Lua
栈上的数据,如下。
其中,rdi
指向的数据结构的定义大致如下,这里主要关注其中的lua_stack_top_ptr
和lua_stack_base_ptr
两个指针,分别指向Lua
栈的栈顶和栈底,栈中的元素就是前面提到的{类型,值}
结构。
1 | struct { |
之后在webvpn_file_name()
中调用lua_touserdata()
函数前下断点,查看此时Lua
栈上的内容,如下。此时,lua_touserdata()
函数的第2个参数为1,即获取Lua
栈底的数据,而此时栈底的数据为NULL
。
继续单步执行程序,查看函数lua_touserdata()
的返回值。可以看到,其返回值确实为NULL
,之后将一个非法内存地址0x13
作为参数传入了lua_pushstring()
,最终导致Segmentation fault
错误。
但是,这里的NULL
值并不是来自之前lua_pushnil()
压入的nil
值,而是位于其下面的栈元素。在下断点调试的过程中,发现设置的2个断点均只命中一次就触发了问题,极大地缩小了调试的范围。同时,在2个断点处Lua
栈的地址是一样的,因此可以在第1个断点命中后,对相应的Lua
栈地址设置硬件断点,看在哪个地方对其值进行了修改。
在gdb
中设置硬件断点后,继续执行时提示如下错误。网上查找相应的解决方案,建议使用set can-use-hw-watchpoints 0
,但实际测试时貌似也存在问题。最后采用hook-stop
的方式来观察指定地址处的内容。
1 | define hook-stop |
通过设置断点并查看相应地址处的内容,最终定位到修改内容的地方位于luaV_execute()
中。对照lua-5.0
源码,luaV_execute()
函数是Lua VM
执行字节码的入口,修改内容的地方位于OP_GETGLOBAL
操作码的处理流程中。
asav962与asa924执行流程对比
前面的分析定位到了luaV_execute()
函数中,而该函数属于Lua VM
的一部分,难道是因为files_list_json_lua
脚本存在问题,而导致Lua VM
执行字节码时出现错误?由于该拒绝服务漏洞对型号为asa924
的设备没有影响,下面对asa924
设备上对应的执行流程进行分析。
根据前面的分析思路,在webvpn_file_name()
中设置断点,发现其流程与asav962
类似,lua_touserdata()
函数的返回值同样会为NULL
,而asa924
设备却不会发生崩溃。2个webvpn_file_name()
的对比如下。
通过调试可知,针对32位程序(asa924
),lua_touserdata()
函数的返回值为指向字符串的指针。当该指针为空时,其直接作为参数传入lua_pushstring()
,而在lua_pushstring()
中会对参数是否为空进行判断。而针对64位程序(asav962
),lua_touserdata()
函数的返回值为指向某个结构体的指针。当该指针为空时,传入lua_pushstring()
的参数为0x13
,从而”绕过“了lua_pushstring()
中的校验,最终造成非法内存地址访问。
至此,分析清楚了该拒绝服务漏洞产生的原因,主要是由于32位程序和64位程序中lua_touserdata()
函数的返回值代表的结构不一致造成的。
补丁分析
在镜像asav9101.qcow2
中该漏洞被修复了。基于前面对漏洞形成原因的分析,下面以asav9101.qcow2
镜像为例,对漏洞的修复情况进行简单分析。
目录遍历漏洞补丁分析
通过动态调试分析,对请求url
的解析在UrlSniff_cb()
函数中完成,其中增加了对./
和../
的处理逻辑,部分代码如下。
1 | v16 = *v5; // v5 指向请求url |
拒绝服务漏洞补丁分析
根据前面的分析可知,拒绝服务漏洞的触发位置在函数webvpn_file_name()
中。在镜像asav9101.qcow2
中,该函数内容如下,可以看到并没有对该函数进行更改。
1 | webvpn_file_name proc near |
在字符串列表中查找/+CSCOE+/files/file_list.json
显示没有结果,表明在该镜像中将这个接口去掉了。同时根据之前files_list_json_lua
脚本的内容进行查找,在该镜像中仍然可以找到对应的lua
脚本内容,但是找不到对该脚本的交叉引用,进一步证实该接口/+CSCOE+/files/file_list.json
被去掉了。
小结
- 利用
CVE-2018-0296
漏洞,远程未认证的攻击者可以对目标设备实施拒绝服务攻击,或从设备获取敏感信息。 - 拒绝服务漏洞的形成原因是由于32位程序和64位程序中
lua_touserdata()
函数的返回值代表的结构不一致造成。 - 在镜像
asav9101.qcow2
中已经修复了该漏洞,其中拒绝服务漏洞的修复方式是去掉了触发了该漏洞的请求url
接口。
相关链接
- Cisco Adaptive Security Appliance Web Services Denial of Service Vulnerability
- Error description CVE-2018-0296 - bypassing authentication in the Cisco ASA web interface
- Cisco Adaptive Security Appliance - Path Traversal
- Test CVE-2018-0296 and extract usernames
- Lua和C++交互详细总结
- 网络设备漏洞分析技术研究
- Cisco ASA series part one: Intro to the Cisco ASA
本文首发于安全客,文章链接:https://www.anquanke.com/post/id/171916