内容简介:作者:陈千CVE-2018-0296是思科ASA设备Web服务中存在的一个拒绝服务漏洞,远程未认证的攻击者利用该漏洞可造成设备崩溃重启。该漏洞最初由来自Securitum的安全研究人员Michal Bentkowski发现,其在
作者:陈千
漏洞简介
CVE-2018-0296是思科ASA设备Web服务中存在的一个拒绝服务漏洞,远程未认证的攻击者利用该漏洞可造成设备崩溃重启。该漏洞最初由来自Securitum的安全研究人员Michal Bentkowski发现,其在 博客 中提到该漏洞最初是一个认证绕过漏洞,上报给思科后,最终被归类为拒绝服务漏洞。据思科发布的 安全公告 显示:针对部分型号的设备,该漏洞可造成设备崩溃重启;而针对其他型号的设备,利用该漏洞可获取设备的敏感信息,造成信息泄露。
针对该漏洞,目前已有公开的PoC脚本,可用于获取设备的敏感信息如用户名,或造成设备崩溃重启。经过实际测试,在公开PoC中造成该漏洞的关键url如下。
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()函数中创建,其类似于下面的定义。
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等
// lua 数据类型 #define LUA_TNONE (-1) #define LUA_TNIL 0 // 空值 #define LUA_TBOOLEAN 1 #define LUA_TLIGHTUSERDATA 2 #define LUA_TNUMBER 3 #define LUA_TSTRING 4 #define LUA_TTABLE 5 #define LUA_TFUNCTION 6 #define LUA_TUSERDATA 7 #define LUA_TTHREAD 8
Lua 栈操作常用api
Lua中提供了一系列与栈操作相关的api,常用的api如下。
// 压入元素 void lua_pushnil (lua_State *L); void lua_pushboolean (lua_State *L, int bool); void lua_pushnumber (lua_State *L, double n); void lua_pushlstring (lua_State *L, const char *s, size_t length); void lua_pushstring (lua_State *L, const char *s); // 检查一个元素是否是一个指定的类型 int lua_is* (lua_State *L, int index); // *可以是任何类型 // 获取元素 int lua_toboolean (lua_State *L, int index); double lua_tonumber (lua_State *L, int index); const char * lua_tostring (lua_State *L, int index); size_t lua_strlen (lua_State *L, int index);
环境准备
调试环境搭建
由于该漏洞在不同型号设备上表现的行为不一致,这里分别选取了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()函数的参数。
Thread 2 received signal SIGSEGV, Segmentation fault. [Switching to Thread 1677] 0x00007ffff497699a in strlen () from ***/_asav962.qcow2.extracted/rootfs/lib64/libc.so.6 (gdb) x/10i $rip => 0x7ffff497699a <strlen+42>: movdqu xmm12,XMMWORD PTR [rax] 0x7ffff497699f <strlen+47>: pcmpeqb xmm12,xmm8 0x7ffff49769a4 <strlen+52>: pmovmskb edx,xmm12 0x7ffff49769a9 <strlen+57>: test edx,edx 0x7ffff49769ab <strlen+59>: je 0x7ffff49769b1 <strlen+65> 0x7ffff49769ad <strlen+61>: bsf eax,edx 0x7ffff49769b0 <strlen+64>: ret 0x7ffff49769b1 <strlen+65>: and rax,0xfffffffffffffff0 0x7ffff49769b5 <strlen+69>: pcmpeqb xmm9,XMMWORD PTR [rax+0x10] 0x7ffff49769bb <strlen+75>: pcmpeqb xmm10,XMMWORD PTR [rax+0x20] (gdb) i r $rax rax 0x13 19 (gdb) bt #0 0x00007ffff497699a in strlen () from ***/_asav962.qcow2.extracted/rootfs/lib64/libc.so.6 #1 0x0000555557ee51ce in lua_pushstring () #2 0x00005555583c87d2 in webvpn_file_name () #3 0x0000555557eec43b in luaD_precall () #4 0x0000555557efc258 in luaV_execute () #5 0x0000555557eeced0 in luaD_call () #6 0x0000555557eebeda in luaD_rawrunprotected () #7 0x0000555557eed323 in luaD_pcall () #8 0x0000555557ee5de6 in lua_pcall () #9 0x0000555557f00821 in lua_dofile () #10 0x000055555822053b in aware_run_lua_script_ns () #11 0x0000555557dc6e3d in ak47_new_stack_call () Backtrace stopped: previous frame inner to this frame (corrupt stack?)
根据栈回溯信息,查看函数lua_pushstring()和webvpn_file_name(),其部分伪代码片段如下。在函数webvpn_file_name()中,将v1 + 0x13这个指针作为参数传递给lua_pushstring(),最终传递给strlen()函数。崩溃点处访问的非法内存地址为0x13,说明v1=0,即在webvpn_file_name()中lua_touserdata()返回值为NULL(也就是0)。
_DWORD *__fastcall lua_pushstring(__int64 a1, const char *a2) { size_t v2; // r14 __int64 v3; // r13 _DWORD *result; // rax if ( a2 ) { v2 = _wrap_strlen(a2); // ... } signed __int64 __fastcall webvpn_file_name(_QWORD *a1) { signed __int64 v1; // rax v1 = lua_touserdata(a1, 1); lua_pushstring((__int64)a1, (const char *)(v1 + 0x13)); return 1LL; }
由前面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函数之间的对应关系,示例如下。
// Lua函数与C函数对应关系 OPEN_DIR() <---> webvpn_open_dir() READ_DIR() <---> webvpn_read_dir() FILE_NAME() <---> webvpn_file_name() FILE_IS_DIR() <---> webvpn_file_is_dir()
在查看对应的C函数时,在webvpn_read_dir()函数中,有一个对lua_pushnil()函数的调用。根据函数的调用顺序,猜测webvpn_file_name()函数中获取到的NULL值来自于这里。
动态分析
根据之前的猜测,尝试在调用lua_pushnil()处下断点,然后查看Lua栈上的数据,如下。
其中,rdi指向的数据结构的定义大致如下,这里主要关注其中的lua_stack_top_ptr和lua_stack_base_ptr两个指针,分别指向Lua栈的栈顶和栈底,栈中的元素就是前面提到的{类型,值}结构。
struct { uint64 xxx; uint64 xxx; uint64 lua_stack_top_ptr; // 指向栈顶 (空栈,即始终指向刚入栈元素的下一个位置) uint64 lua_stack_base_ptr; // 指向栈底 (栈地址由低向高增长) uint64 xxx; uint64 xxx; uint64 xxx; uint64 xxx; ... }
之后在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的方式来观察指定地址处的内容。
define hook-stop x/2gx <addr> end
通过设置断点并查看相应地址处的内容,最终定位到修改内容的地方位于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()函数中完成,其中增加了对./和../的处理逻辑,部分代码如下。
v16 = *v5; // v5 指向请求url v17 = v5; v18 = v5; LABEL_45: while ( v16 ) { if ( v16 == '.' ) { v20 = v18[1]; switch ( v20 ) { case '.': v9 = (unsigned __int8)v18[2]; if ( !(_BYTE)v9 ) goto LABEL_75; if ( (_BYTE)v9 == '/' ) { v20 = v18[3]; // 匹配到"../" v18 += 2; LABEL_75: ++v18; v16 = v20; goto LABEL_45; } break; case '/': v16 = v18[2]; // 匹配到"./" v18 += 2; goto LABEL_45; case '\0': ++v18; goto LABEL_60; } do { LABEL_48:
拒绝服务漏洞补丁分析
根据前面的分析可知,拒绝服务漏洞的触发位置在函数webvpn_file_name()中。在镜像asav9101.qcow2中,该函数内容如下,可以看到并没有对该函数进行更改。
webvpn_file_name proc near ; __unwind { push rbp mov esi, 1 mov rbp, rsp push rbx mov rbx, rdi sub rsp, 8 call lua_touserdata mov rdi, rbx lea rsi, [rax+13h] call lua_pushstring add rsp, 8 mov eax, 1 pop rbx pop rbp retn ; }
在字符串列表中查找/+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接口。
以上所述就是小编给大家介绍的《CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 漏洞分析:OpenSSH用户枚举漏洞(CVE-2018-15473)分析
- 【漏洞分析】CouchDB漏洞(CVE–2017–12635, CVE–2017–12636)分析
- 【漏洞分析】lighttpd域处理拒绝服务漏洞环境从复现到分析
- 漏洞分析:对CVE-2018-8587(Microsoft Outlook)漏洞的深入分析
- 路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析
- Weblogic IIOP反序列化漏洞(CVE-2020-2551) 漏洞分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。