Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2022-0185 — CVE-2022-0185 POC、Docker 与分析报告 | Kitploit
工具/GitHubGitHub/chenaotian/cve-2022-0185
权限提升漏洞分析漏洞利用学习与教育容器逃逸二进制利用实验室与实践
GitHubchenaotian/cve-2022-0185

CVE-2022-0185

CVE-2022-0185 POC、Docker 与分析报告

查看仓库
3712184年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2022-0185 linux 内核提权(逃逸)

[toc]

漏洞简介

漏洞编号: CVE-2022-0185

漏洞评分:

漏洞产品: linux kernel - fsconfig syscall

影响范围: linux kernel 5.1-rc1 ~ 5.16.2

利用条件: linux 本地; 具有CAP_SYS_ADMIN cap权限(可以unshare 直接获得,等于无限制)

利用效果: 本地提权;容器逃逸

源码获取: git clone git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git -b Ubuntu-hwe-5.11-5.11.0-27.29_20.04.1 --depth 1

或 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/

环境搭建

调试环境

5.X 内核编译环境docker :chenaotian/kernelcompile

漏洞分析docker:chenaotian/cve-2022-0185

  • 准备了两个内核一个发行版,一个自己编译版

    • 一个下载的发行版内核5.11.0-44 用于验证调试分析exp(发行版内核不会崩溃)
    • 一个编译的带符号的5.13 用于有符号调试poc
  • 安装qemu、gdb、gdb-peda 等

  • 漏洞相关在 /root/cve-2022-0185

    • boot_exp.sh 用于启动exp 验证调试环境,发行版5.11.0-44无符号内核
    • boot_poc.sh 用于启动poc 验证环境,可以跑崩内核,但无法跑exp,自编译5.13 有符号内核
    • exp 目录,exp 源码(作者: BitsByWill),直接编译exploit_fuse即可。

qemu 环境:https://github.com/chenaotian/CVE-2022-0185/tree/main/qemuANDexp

ubuntu20.04 验证环境

ubuntu 20.04虚拟机exp 运行环境运行原作者exp

准备ubuntu20.04 虚拟机,然后更换内核:

apt-get install linux-image-5.11.0-44-generic

grep menuentry /boot/grub/grub.cfg
vim /etc/default/grub
#修改 GRUB_DEFAULT 选项为上面结果中想要启动内核的下标
update-grub
#如果不生效的话则直接进入/boot 目录将之前的内核相关文件(带之前内核编号的文件)全部删掉,然后启动时候报找不到内核,然后手动选择内核启动也可以

#编译exp
make fuse
./exploit

提权效果

image-20220302154151113

漏洞原理

漏洞发生的系统调用是fsconfig 中的 FSCONFIG_SET_STRING 操作选项,该系统调用用于对已经打开的文件系统上下文进行一些配置,需要的前提条件是具备CAP_SYS_ADMIN cap权限:

fsopen的主要目的就是创建一个文件系统上下文,然后把它和一个文件描述符挂钩,返回文件描述符。fsopen后面就是fsconfig,从字面意思应该可以猜到,我们上面通过fsopen创建了一个文件系统上下文,下面的fsconfig可能就是用来配置文件系统上下文里的内容的。事实上fsconfig确实主要是做这个配置工作的,除了文件系统上下文,同时它还支持其它的工作。

漏洞发生点

首先漏洞出现在 legacy_parse_param 函数中:

linux-5.11\fs\fs_context.c : 502 : legacy_parse_param

static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
	struct legacy_fs_context *ctx = fc->fs_private;
	unsigned int size = ctx->data_size;
	size_t len = 0;

	··· ···
	··· ···

	switch (param->type) {
	case fs_value_is_string:
		len = 1 + param->size;
		fallthrough;
	··· ···
	}

	if (len > PAGE_SIZE - 2 - size) //此处边界检查有问题
		return invalf(fc, "VFS: Legacy: Cumulative options too large");
	if (strchr(param->key, ',') ||
	    (param->type == fs_value_is_string &&
	     memchr(param->string, ',', param->size)))
		return invalf(fc, "VFS: Legacy: Option '%s' contained comma",
			      param->key);
	if (!ctx->legacy_data) {
		ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); //在第一次时会分配一页大小
		if (!ctx->legacy_data)
			return -ENOMEM;
	}

	ctx->legacy_data[size++] = ',';
	len = strlen(param->key);
	memcpy(ctx->legacy_data + size, param->key, len);
	size += len;
	if (param->type == fs_value_is_string) {
		ctx->legacy_data[size++] = '=';
		memcpy(ctx->legacy_data + size, param->string, param->size); //拷贝,可能越界
		size += param->size;
	}
	ctx->legacy_data[size] = '\0';
	ctx->data_size = size;
	ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS;
	return 0;
}

关键在于后面的memcpy,会将我们传入的param->string 拷贝到ctx->legacy_data 之中。而判断是否拷贝越界就在前面的(len > PAGE_SIZE - 2 - size) 判断,这里判断是有问题的,判断类型是size_t 也就是unsigned int,如果size > PAGE_SIZE - 2 则会发生整数溢出反转,造成len < PAGE_SIZE - 2 - size ,进而判断通过,后面拷贝的时候size 是大于 PAGE_SIZE - 2的,造成拷贝越界。

用到的一些数据结构:

struct fs_context {
	const struct fs_context_operations *ops;
	struct mutex		uapi_mutex;	/* Userspace access mutex */
	struct file_system_type	*fs_type;
	void			*fs_private;	/* The filesystem's context */
	void			*sget_key;
	struct dentry		*root;		/* The root and superblock */
	struct user_namespace	*user_ns;	/* The user namespace for this mount */
	struct net		*net_ns;	/* The network namespace for this mount */
	const struct cred	*cred;		/* The mounter's credentials */
	struct p_log		log;		/* Logging buffer */
	const char		*source;	/* The source name (eg. dev path) */
	void			*security;	/* Linux S&M options */
	void			*s_fs_info;	/* Proposed s_fs_info */
	unsigned int		sb_flags;	/* Proposed superblock flags (SB_*) */
	unsigned int		sb_flags_mask;	/* Superblock flags that were changed */
	unsigned int		s_iflags;	/* OR'd with sb->s_iflags */
	unsigned int		lsm_flags;	/* Information flags from the fs to the LSM */
	enum fs_context_purpose	purpose:8;
	enum fs_context_phase	phase:8;	/* The phase the context is in */
	bool			need_free:1;	/* Need to call ops->free() */
	bool			global:1;	/* Goes into &init_user_ns */
	bool			oldapi:1;	/* Coming from mount(2) */
};

struct legacy_fs_context {
	char			*legacy_data;	/* Data page for legacy filesystems */
	size_t			data_size;
	enum legacy_fs_param	param_type;
};

struct fs_parameter {
	const char		*key;		/* Parameter name */
	enum fs_value_type	type:8;		/* The type of value here */
	union {
		char		*string;
		void		*blob;
		struct filename	*name;
		struct file	*file;
	};
	size_t	size;
	int	dirfd;
};

调用路径

下面分析一下函数调用栈,首先入口肯定是 fsconfig 系统调用:

linux-5.11\fs\fsopen.c : 314 : SYSCALL_DEFINE5(fsconfig,...

SYSCALL_DEFINE5(fsconfig,
		int, fd,
		unsigned int, cmd,
		const char __user *, _key,
		const void __user *, _value,
		int, aux)
{
	struct fs_context *fc;
	struct fd f;
	int ret;
	int lookup_flags = 0;

	struct fs_parameter param = {
		.type	= fs_value_is_undefined,
	};

	··· ···
	f = fdget(fd);
	if (!f.file)
		return -EBADF;
	ret = -EINVAL;
	if (f.file->f_op != &fscontext_fops)
		goto out_f;

	fc = f.file->private_data; //设置fc
    
	··· ···
	switch (cmd) {
	··· ···
	case FSCONFIG_SET_STRING:
		param.type = fs_value_is_string;
        //初始化结构体中的联合体中的string成员为用户传入的字符串
		param.string = strndup_user(_value, 256); 
		if (IS_ERR(param.string)) {
			ret = PTR_ERR(param.string);
			goto out_key;
		}
		param.size = strlen(param.string);//设置size
		break;
	··· ···
    ··· ···
	}

	ret = mutex_lock_interruptible(&fc->uapi_mutex);
	if (ret == 0) {
		ret = vfs_fsconfig_locked(fc, cmd, &param);
		mutex_unlock(&fc->uapi_mutex);
	}

	··· ···
    ··· ···
}

在fsconfig 系统调用的入口中,先根据文件描述符fd 初始化文件系统上下文结构体fc,然后根据用户传入的参数设置param 结构体,该结构体变量就是后面在漏洞发生函数legacy_parse_param 中使用的param。接下来进入vfs_fsconfig_locked函数:

linux-5.11\fs\fsopen.c : 216 : vfs_fsconfig_locked

static int vfs_fsconfig_locked(struct fs_context *fc, int cmd,
			       struct fs_parameter *param)
{
	struct super_block *sb;
	int ret;

	ret = finish_clean_context(fc);
	if (ret)
		return ret;
	switch (cmd) {
	··· ···
	default:
		if (fc->phase != FS_CONTEXT_CREATE_PARAMS &&
		    fc->phase != FS_CONTEXT_RECONF_PARAMS)
			return -EBUSY;

		return vfs_parse_fs_param(fc, param);
	}
	fc->phase = FS_CONTEXT_FAILED;
	return ret;
}

首先调用finish_clean_context函数,这里会调用legacy_init_fs_context 函数来注册回调函数表,该回调函数表中就包括漏洞所在函数legacy_parse_param。

linux-5.11\fs\fs_context.c

int finish_clean_context(struct fs_context *fc)
{
    ··· ···
		error = legacy_init_fs_context(fc);
	··· ···
}

static int legacy_init_fs_context(struct fs_context *fc)
{
	fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL);
	if (!fc->fs_private)
		return -ENOMEM;
	fc->ops = &legacy_fs_context_ops; //注册回调函数表
	return 0;
}

const struct fs_context_operations legacy_fs_context_ops = {
	.free			= legacy_fs_context_free,
	.dup			= legacy_fs_context_dup,
	.parse_param		= legacy_parse_param, //漏洞函数
	.parse_monolithic	= legacy_parse_monolithic,
	.get_tree		= legacy_get_tree,
	.reconfigure		= legacy_reconfigure,
};

注册结束之后,进入vfs_parse_fs_param 函数处理参数,这里会调用刚注册的回调函数,也就是漏洞函数。

linux-5.11\fs\fs_context.c : 98 : vfs_parse_fs_param

int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param)
{
	··· ···
下载工具