脏牛漏洞提权原理
1. 什么是写时复制(CoW)?
在 Linux 系统中,当一个进程需要复制一个文件或共享内存区域时,内核并不会立刻为新进程分配独立的内存空间并复制数据。相反,它会让两个进程共享同一块物理内存
只有当其中一个进程尝试修改这块内存中的数据时,内核才会触发“写时复制”机制:
- 内核会为这个正在尝试写入的进程创建一个新的物理内存副本
- 这样,原始进程的数据保持不变,而新进程可以在自己的私有内存副本上进行修改,而互不影响
这个机制极大地节省了内存和时间,提高了系统的效率
2. 漏洞是如何产生的?
“脏牛”漏洞的本质就在于写时复制(CoW)机制的一个缺陷
当一个非特权用户尝试访问一个只读文件(例如 /etc/passwd
)时,内核会映射这个文件的内存页。按照 CoW 机制,用户无法修改它
然而,内核在处理以下两个操作时,存在一个竞争条件:
- 操作一: 一个线程使用
madvise(MADV_DONTNEED)
系统调用来丢弃一个内存页。这个调用告诉内核:我不需要这个页了,你可以把它从内存中释放掉 - 操作二: 另一个线程尝试执行写入操作,触发 CoW 机制,申请一个新的私有内存页
正常的流程应该是:如果一个线程在尝试写入,内核会先为其分配新的内存,然后再进行写入。但是,这个漏洞的巧妙之处在于,通过精巧地控制这两个操作的执行时机,可以制造出一个“时间窗口”
攻击者利用这个时间窗口,在内核准备为写入操作分配新内存之前,通过 madvise()
使得内核错误地取消了 CoW 的正常流程。结果是,内核没有为写入操作创建一个私有的内存副本,而是直接在原始的只读内存页上进行了写入
3. 如何利用这个漏洞?
攻击者利用这个漏洞的流程通常如下:
- 选择目标文件: 攻击者选择一个具有 root 权限的只读文件,例如
/etc/passwd
,该文件包含了系统用户的账户信息 - 多线程并发: 攻击者启动两个线程,一个线程不断地尝试对
/etc/passwd
进行写入操作(例如,写入一个新的 root 用户账户),另一个线程则持续调用madvise()
来触发竞争条件 - 成功写入: 在竞争条件被触发的瞬间,写入操作会绕过 CoW 机制,直接修改
/etc/passwd
的内存内容 - 获取权限: 攻击者随后会利用修改后的文件,通过新的 root 用户账户成功登录系统,从而获得 root 权限