热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux内核提权DirtyPipe(CVE20220847)漏洞分析

 作者:ghost461@知道创宇404实验室简介2022年2月23日, Linux内核发布漏洞补丁, 修复了内核5.8及之后版本存在的任意文件覆盖的漏洞(CVE-2022-0847), 该漏洞可导致

 

作者:ghost461@知道创宇404实验室

简介

2022年2月23日, Linux内核发布漏洞补丁, 修复了内核5.8及之后版本存在的任意文件覆盖的漏洞(CVE-2022-0847), 该漏洞可导致普通用户本地提权至root特权, 因为与之前出现的DirtyCow(CVE-2016-5195)漏洞原理类似, 该漏洞被命名为DirtyPipe。

在3月7日, 漏洞发现者Max Kellermann详细披露了该漏洞细节以及完整POC。Paper中不光解释了该漏洞的触发原因, 还说明了发现漏洞的故事, 以及形成该漏洞的内核代码演变过程, 非常适合深入研究学习。

漏洞影响版本: 5.8 <= Linux内核版本 <5.16.11 / 5.15.25 / 5.10.102

 

漏洞复现

在ubuntu-20.04-LTS的虚拟机中进行测试, 内核版本号5.10.0-1008-oem, 在POC执行后成功获取到root shell


从POC看漏洞利用流程

限于篇幅,这里截取POC的部分代码

static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
// 获取Pipe可使用的最大页面数量
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
// 任意数据填充
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
// 清空Pipe
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
}
int main(int argc, char **argv)
{
......
// 只读打开目标文件
const int fd = open(path, O_RDONLY); // yes, read-only! :-)
......
// 创建Pipe
int p[2];
prepare_pipe(p);
// splice()将文件1字节数据写入Pipe
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
......
// write()写入任意数据到Pipe
nbytes = write(p[1], data, data_size);
// 判断是否写入成功
if (nbytes <0) {
perror("write failed");
return EXIT_FAILURE;
}
if ((size_t)nbytes fprintf(stderr, "short write\n");
return EXIT_FAILURE;
}
printf("It worked!\n");
return EXIT_SUCCESS;
}


  1. 创建pipe;

  2. 使用任意数据填充管道(填满, 而且是填满Pipe的最大空间);

  3. 清空管道内数据;

  4. 使用splice()读取目标文件(只读)的1字节数据发送至pipe;

  5. write()将任意数据继续写入pipe, 此数据将会覆盖目标文件内容;

只要挑选合适的目标文件(必须要有可读权限), 利用漏洞Patch掉关键字段数据, 即可完成从普通用户到root用户的权限提升, POC使用的是/etc/passwd文件的利用方式。

仔细阅读POC可以发现, 该漏洞在覆盖数据时存在一些限制, 我们将在深入分析漏洞原理之后讨论它们。

 

复现原始Bug

在作者的paper中可以了解到, 发现该漏洞的起因不是专门的漏洞挖掘工作, 而是关于日志服务器多次出现的文件错误, 用户下载的包含日志的gzip文件多次出现CRC校验位错误, 排查后发现CRC校验位总是被一段ZIP头覆盖。

根据作者介绍, 可以生成ZIP文件的只有主服务器的一个负责HTTP连接的服务(为了兼容windows用户, 需要把gzip封包即时封包为ZIP文件), 而该服务没有写入gzip文件的权限。

即主服务器同时存在一个writer进程与一个splicer进程, 两个进程以不同的用户身份运行, splicer进程并没有写入writer进程目标文件的权限, 但存在splicer进程的数据写入文件的bug存在。


简化两个服务进程

根据描述, 简易还原出bug触发时最原本的样子, poc_p1与poc_p2两个程序:

编译运行poc_p1程序, tmpFile内容为全A

运行poc_p2程序, tmpFile文件时间戳未改变, 但文件内容中出现了B

仔细观察每次出现脏数据的间隔, 发现恰好为4096字节, 4kB, 也是系统中一个页面的大小

如果将进程可使用的全部Pipe大小进行一次写入/读出操作, tmpFile的内容发生了变化

同时可以注意到, tmpFile文件后续并不是全部被B覆盖, 而是在4096字节处保留了原本的内容

此时不执行任何操作, 重启系统后, tmpFile将变回全A的状态, 这说明, poc_p2程序对tmpFile文件的修改仅存在于系统的页面缓存(page cache)中。

以上便是漏洞出现的初始状态, 要分析其详细的原因, 就需要了解造成此状态的一些系统机制。


Pipe、splice()与零拷贝

限于篇幅, 这里简要介绍一下该漏洞相关的系统机制


  • CPU管理的最小内存单位是一个页面(Page), 一个页面通常为4kB大小, linux内存管理的最底层的一切都是关于页面的, 文件IO也是如此, 如果程序从文件中读取数据, 内核将先把它从磁盘读取到专属于内核的页面缓存(Page Cache)中, 后续再把它从内核区域复制到用户程序的内存空间中;

  • 如果每一次都把文件数据从内核空间拷贝到用户空间, 将会拖慢系统的运行速度, 也会额外消耗很多内存空间, 所以出现了splice()系统调用, 它的任务是从文件中获取数据并写入管道中, 期间一个特殊的实现方式便是: 目标文件的页面缓存数据不会直接复制到Pipe的环形缓冲区内, 而是以索引的方式(即 内存页框地址、偏移量、长度 所表示的一块内存区域)复制到了pipe_buffer的结构体中, 如此就避免了从内核空间向用户空间的数据拷贝过程, 所以被称为”零拷贝”;

  • 管道(Pipe)是一种经典的进程间通信方式, 它包含一个输入端和一个输出端, 程序将数据从一段输入, 从另一端读出; 在内核中, 为了实现这种数据通信, 需要以页面(Page)为单位维护一个环形缓冲区(被称为pipe_buffer), 它通常最多包含16个页面, 且可以被循环利用;

  • 当一个程序使用管道写入数据时, pipe_write()调用会处理数据写入工作, 默认情况下, 多次写入操作是要写入环形缓冲区的一个新的页面的, 但是如果单次写入操作没有写满一个页面大小, 就会造成内存空间的浪费, 所以pipe_buffer中的每一个页面都包含一个can_merge属性, 该属性可以在下一次pipe_write()操作执行时, 指示内核继续向同一个页面继续写入数据, 而不是获取一个新的页面进行写入。



描述漏洞原理

splice()系统调用将包含文件的页面缓存(page cache), 链接到pipe的环形缓冲区(pipe_buffer)时, 在copy_page_to_iter_pipe 和 push_pipe函数中未能正确清除页面的"PIPE_BUF_FLAG_CAN_MERGE"属性, 导致后续进行pipe_write()操作时错误的判定"write操作可合并(merge)", 从而将非法数据写入文件页面缓存, 导致任意文件覆盖漏洞。

这也就解释了之前原始bug造成的一些问题:


  • 由于pipe buffer页面未清空, 所以第一次poc_p2测试时, tmpFile从4096字节才开始被覆盖数据;

  • splice()调用至少需要将文件页面缓存的第一个字节写入pipe, 才可以完成将page_cache索引到pipe_buffer, 所以第二次poc_p2测试时, tmpFile并没有全部被覆盖为”B”, 而是每隔4096字节重新出现原始的”A”;

  • 每一次poc_p2写入的数据都是在tmpFile的页面缓存中, 所以如果没有其他可写权限的程序进行write操作, 该页面并不会被内核标记为“dirty”, 也就不会进行页面缓存写会磁盘的操作, 此时其他进程读文件会命中页面缓存, 从而读取到篡改后到文件数据, 但重启后文件会变回原来的状态;

  • 也正是因为poc_p2写入的是tmpFile文件的页面缓存, 所以无限的循环会因文件到尾而写入失败, 跳出循环。

 

阅读相关源码

要了解漏洞形成的细节, 以及漏洞为什么不是从splice()引入之初就存在, 还是要从内核源码了解Pipe buffer的can_merge属性如何迭代发展至今,

Linux 2.6, 引入了splice()系统调用;

Linux 4.9, 添加了iov_iter对Pipe的支持, 其中copy_page_to_iter_pipe()push_pipe()函数实现中缺少对pipe buffer中flag的初始化操作, 但在当时并无大碍, 因为此时的can_merge标识还在opspipe_buf_operations结构体中。 如图, 此时的buf->ops = &page_cache_pipe_buf_ops操作会使can_merge属性为0, 此时并不会触发漏洞, 但为之后的代码迭代留下了隐患;

Linux 5.1, 由于在众多类型的pipe_buffer中, 只有anon_pipe_buf_ops这一种情况的can_merge属性是为1的(can_merge字段在结构体中占一个int大小的空间), 所以, 将pipe_buf_operations结构体中的can_merge属性删除, 并且把merge操作时的判断改为指针判断, 合情合理。正是如此, copy_page_to_iter_pipe()中对buf->ops的初始化操作已经不包含can_merge属性初始化的功能了, 只是push_write()中merge操作的判断依然正常, 所以依然不会触发漏洞;

page_cache_pipe_buf_ops类型也在此时被修改

然后是新的判断can_merge的操作, 直接判断是不是anon_pipe_buf_ops类型即可

Linux 5.8中, 把各种类型的pipe_buf_operations结构体进行合并, 正式把can_merge标记改为PIPE_BUF_FLAG_CAN_MERGE合并进入flag属性中, 知道此时, 4.9补丁中没有flag字段初始化的隐患才真正生效

合并后的anon_pipe_buf_ops不能再与can_merge强关联

再次修改了merge操作的判断方式

添加新的PIPE_BUF_FLAG_CAN_MERGE定义, 合并进入pipe buffer的flag字段

内核漏洞补丁, 在copy_page_to_iter_pipe()push_pipe()调用中专门添加了对buffer中flag的初始化。

 

拓展与总结

关于该漏洞的一些限制:


  • 显而易见的, 被覆写的目标文件必须拥有可读权限, 否则splice()无法进行;

  • 由于是在pipe_buffer中覆写页面缓存的数据, 又需要splice()读取至少1字节的数据进入管道, 所以覆盖时, 每个页面的第一个字节是不可修改的, 同样的原因, 单次写入的数据量也不能大于4kB;

  • 由于需要写入的页面都是内核通过文件IO读取的page cache, 所以任意写入文件只能是单纯的“覆写”, 不能调整文件的大小;

该漏洞之所以被命名为DirtyPipe, 对比CVE-2016-5195(DirtyCOW), 是因为两个漏洞触发的点都在于linux内核对文件读写操作的优化(写时拷贝/零拷贝); 而DirtyPipe的利用方式要比DirtyCOW的更加简单, 是因为DirtyCOW的漏洞触发需要进行条件竞争, 而DirtyPipe可以通过操作顺序直接触发;

值得注意的是, 该内核漏洞不仅影响了linux各个发行版, Android或其他使用linux内核的IoT系统同样会受到影响; 另外, 该漏洞任意覆盖数据不只是影响用户或系统文件, 块设备、只读挂在的镜像等数据一样会受到影响, 基于此, 实现容器穿透也是有可能的。

一点个人总结, 想想自己刚开始做漏洞复现的时候, 第一个复现的内核提权就是大名鼎鼎的DirtyCOW, 所以看到DirtyPipe就不由得深入研究一下。这个漏洞的发现经历也非常有趣, 作者居然是从软件bug分析一路走到了内核漏洞披露, 相当佩服作者这种求索精神, 可以想象一个人在代码堆中翻阅各种实现细节时的辛酸, 也感谢作者如此详细的披露与分享。

 

参考链接



  • Max Kellermann的paper

  • Linux内核补丁

  • android补丁

  • 漏洞POC



推荐阅读
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
  • FIN7后门工具伪装成白帽工具进行传播
    fin7,后门,工具,伪装,成,白, ... [详细]
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 31.项目部署
    目录1一些概念1.1项目部署1.2WSGI1.3uWSGI1.4Nginx2安装环境与迁移项目2.1项目内容2.2项目配置2.2.1DEBUG2.2.2STAT ... [详细]
  • 2016 linux发行版排行_灵越7590 安装 linux (manjarognome)
    RT之前做了一次灵越7590黑苹果炒作业的文章,希望能够分享给更多不想折腾的人。kawauso:教你如何给灵越7590黑苹果抄作业​zhuanlan.z ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • 目录浏览漏洞与目录遍历漏洞的危害及修复方法
    本文讨论了目录浏览漏洞与目录遍历漏洞的危害,包括网站结构暴露、隐秘文件访问等。同时介绍了检测方法,如使用漏洞扫描器和搜索关键词。最后提供了针对常见中间件的修复方式,包括关闭目录浏览功能。对于保护网站安全具有一定的参考价值。 ... [详细]
  • 本文探讨了容器技术在安全方面面临的挑战,并提出了相应的解决方案。多租户保护、用户访问控制、中毒的镜像、验证和加密、容器守护以及容器监控都是容器技术中需要关注的安全问题。通过在虚拟机中运行容器、限制特权升级、使用受信任的镜像库、进行验证和加密、限制容器守护进程的访问以及监控容器栈,可以提高容器技术的安全性。未来,随着容器技术的发展,还需解决诸如硬件支持、软件定义基础设施集成等挑战。 ... [详细]
  • 动手动脑,无法自拔(3)课时作业6
    1.动手动脑(五子棋棋盘排布)(1)源程序(2)实验截图2.动手动脑(数字转换成汉字)(1)源程序(2)实验截图3.动手动脑(大数计算)(1)源程序 ... [详细]
  • 必须先赞下国人npm库作品:node-images(https:github.comzhangyuanweinode-images),封装了跨平台的C++逻辑,形成nodejsAP ... [详细]
author-avatar
陈俊铭冠萍育维
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有