chroot牢獄から脱獄
Rev.11を表示中。最新版はこちら。
仙石浩明さんの日記で面白い記事を見つけました。chroot されたディレクトリから脱出してみるというものです。例えば/aaa/bbb/ccc/dddでchroot cccとすると、aaa,bbbにはアクセスできなくなります。chroot牢獄というらしいのですが、なんとその牢獄から脱獄してaaa,bbbに行けちゃうっていうことです。実際さってみると、いとも簡単に脱獄できました。これを実装からみてみようと思います。外れているかもしれません。タスク構造他にはルートディレクトリ情報のrootとカレントディレクトリ情報のpwdのメンバーを有しています。例えば/aaa/bbb/ccc/dddでchroot cccとしたといたします。fs->rootはccc、fs->pwdはcccとなっています。(fs->pwdはchrootコマンドが設定していると思いますが・・・)
struct task_struct {
・・・・
struct fs_struct *fs;
・・・・
}
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct path root, pwd;
};
パスのルックアップにおいて、絶対パスでしてされれば、ルートをfs->rootで設定されているパスを起点に、相対パスで指定されればfs->pwdを基点にして探査していきます。たとえばcd ..とすると、fs->pwdから親へ辿ってそれを返すことになります。通常はfs->root配下にfs->pwdは存在することになりますが、chrootでfs->root配下以外にfs->pwdを設定することで、chroot牢獄をエスケープしようというわけです。
カーネルのchrootシステムコールはfs->rootを書き換えるだけです。従ってCでchrootシステムコールをコールしてcd..とする一連のプログラムを作成すればいいわけです。そのようなプロセスをchrootされているルート(ccc)から起動すると、子プロセスはfs->pwdを引き継ぎます。そうすると、fs->rootがddd、fs->pwdがcccとなるプロセスを作成することが可能となります。
SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
・・・・
set_fs_root(current->fs, &path);
・・・・
return error;
}
void set_fs_root(struct fs_struct *fs, struct path *path)
{
struct path old_root;
write_lock(&fs->lock);
old_root = fs->root;
fs->root = *path;
path_get(path);
write_unlock(&fs->lock);
if (old_root.dentry)
path_put(&old_root);
}
あとは順にcd ..をしていきます。パス探索ではcd ..毎に、それがfs->rootでないかチェックします。もしfs->rootであればそこでお終いです。しかしfs->pwdがfs->rootの上だとcd ..としてfs->rootにマッチすることはありません。従って最終的に本来のルートにたどり着いたのちbashを起動することで、まさにchroot脱獄です。ただし、fs->rootはdddのままですので、このシェル上で絶対パスを指定すると、牢獄に逆戻り。ってことになってしまいま・・・す?p/s
fs->pwdがコンポーネントした保持していないのに、なぜフルパスで表示されるのだろうか?と思って調べると、どうやら__d_pathをコールすることでfs->pwdから親へと遡って表示させているようでした。またそれゆえにchroot脱獄を可能としているようです。(たぶん)
char *__d_path(const struct path *path, struct path *root,
char *buffer, int buflen)
{
・・・・
for (;;) {
struct dentry * parent;
if (dentry == root->dentry && vfsmnt == root->mnt)
break;
マウントルートまたはシステムルートのチェック
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
マウントルートの親が自分自身ってことはシステムルートってこと
if (vfsmnt->mnt_parent == vfsmnt) {
goto global_root;
}
マウントポイントポイントを次に探査対象とする
dentry = vfsmnt->mnt_mountpoint;
vfsmnt = vfsmnt->mnt_parent;
continue;
}
探査対象の親を取得
parent = dentry->d_parent;
prefetch(parent);
if ((prepend_name(&end, &buflen, &dentry->d_name) != 0) ||
(prepend(&end, &buflen, "/", 1) != 0))
goto Elong;
retval = end;
親のdentryで再探索
dentry = parent;
}
・・・・




