CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

栏目: Lua · 发布时间: 5年前

内容简介:作者:陈千CVE-2018-0296是思科ASA设备Web服务中存在的一个拒绝服务漏洞,远程未认证的攻击者利用该漏洞可造成设备崩溃重启。该漏洞最初由来自Securitum的安全研究人员Michal Bentkowski发现,其在

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

作者:陈千

漏洞简介

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永远表示栈顶,如下图所示。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

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等

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

// 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脚本。示例如下。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

查看files_list_json_lua脚本的内容,其主要功能是列出当前路径下的目录或文件,依次调用了Lua中的OPEN_DIR()、READ_DIR()、FILE_NAME()、FILE_IS_DIR()等函数。而在aware_addlib_netfs()函数中,建立了Lua函数和C函数之间的对应关系,示例如下。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

// 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值来自于这里。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

动态分析

根据之前的猜测,尝试在调用lua_pushnil()处下断点,然后查看Lua栈上的数据,如下。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

其中,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。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

继续单步执行程序,查看函数lua_touserdata()的返回值。可以看到,其返回值确实为NULL,之后将一个非法内存地址0x13作为参数传入了lua_pushstring(),最终导致Segmentation fault错误。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

但是,这里的NULL值并不是来自之前lua_pushnil()压入的nil值,而是位于其下面的栈元素。在下断点调试的过程中,发现设置的2个断点均只命中一次就触发了问题,极大地缩小了调试的范围。同时,在2个断点处Lua栈的地址是一样的,因此可以在第1个断点命中后,对相应的Lua栈地址设置硬件断点,看在哪个地方对其值进行了修改。

在gdb中设置硬件断点后,继续执行时提示如下错误。网上查找相应的解决方案,建议使用set can-use-hw-watchpoints 0,但实际测试时貌似也存在问题。最后采用hook-stop的方式来观察指定地址处的内容。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

define hook-stop
    x/2gx <addr>
end

通过设置断点并查看相应地址处的内容,最终定位到修改内容的地方位于luaV_execute()中。对照lua-5.0源码,luaV_execute()函数是Lua VM执行字节码的入口,修改内容的地方位于OP_GETGLOBAL操作码的处理流程中。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

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()的对比如下。

CVE-2018-0296 Cisco ASA 拒绝服务漏洞分析

通过调试可知,针对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 拒绝服务漏洞分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning JavaScript

Learning JavaScript

Shelley Powers / Oreilly & Associates Inc / 2006-10-17 / $29.99

As web browsers have become more capable and standards compliant, JavaScript has grown in prominence. JavaScript lets designers add sparkle and life to web pages, while more complex JavaScript has led......一起来看看 《Learning JavaScript》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具