##################################################################### # This patch provides a chroot+sftp hack. The patch was created # against OpenSSH source version 4.3p2. # # See the sections below on CAVEATS and SECURITY before using. # ### SETUP ########################################################### # # Setting up a user environment for chroot'd sftp access # ------------------------------------------------------ # 1) create a new user (I'm using "foo") and set its password # root:~# adduser foo # # 2) set up the new user's directory. the "newroot" directory # will be foo's new home directory and that is the only area # that we want owned/writable by user foo. Everything else # should be owned by root and not writable/readable by anyone # including foo. This includes all dot-file (.bashrc,.profile) # # root:~# cd ~foo # root:~# mkdir newroot # root:~# chown -R root.root . # root:~# chmod -R u+rw,go-rwx . # root:~# chmod 555 newroot # # The hacked chroot+sftp will write a warning to the system logs # and abort if the "newroot" directory is not owned by root. # You can make sub-directories below ~foo/newroot that are owned # and writable by the foo user. These subdirectories are the # foo user's playground. # # For example, you can create the following to provide a r/w and # ro area. # # root:~# mkdir -p ~foo/newroot/incoming # root:~# mkdir -p ~foo/newroot/pub # root:~# chown foo.foo ~foo/newroot/incoming # root:~# chmod 755 ~foo/newroot/incoming # root:~# chmod 555 ~foo/newroot/pub # # User foo can upload files to the incoming area, but can only # download files from pub. # # 3) Edit the new user's /etc/passwd entry to make it look similar # to the example below. Note the "/./" trailing foo's home # directory (/home/foo/newroot). This is the special tag to tell # the sftp-server subprocess that this is a chroot'd user. Note # also that we set the user's shell to the actual sftp-server # program which should be SUID root. # # foo:x:501:501:Foo,,,:/home/foo/newroot/./:/usr/lib/misc/sftp-server # # Recall that any other users in /etc/passwd that DO NOT have the # special "/./" indicator are allowed to use the sftp-server # subprocess in the normal way! # # NOTE: I did NOT need to add anything to /etc/shells to get this # to work. # ### ROBUSTNESS ###################################################### # # I've tried to make the hack semi-robust regarding its new ability. # - If the sftp-server executable is not SUID, it should operate # in its original form. # # - If the sftp-server is not SUID and a user entry contains the # "/./" indicator, they are dropped into the directory specified # below the indicator. i.e. "/home/foo/newroot/./" becomes # "/home/foo/newroot" as their starting point. # # - If a user does not contain "/./" in /etc/passwd, it should # operate in its original form regardless of the sftp-server # being SUID or not. # ### CAVEATS ######################################################### # # Any user containing the special sequence "/./" in their /etc/passwd # entry will not be able to use the system for anything except # the chroot'd sftp service. This is because their shell is set to # "sftp-server" rather than /bin/bash, /bin/sh, /usr/bin/csh, etc. # Therefore, the users you want to limit via chroot and still have # access to the system will require two separate accounts. # # In most "chroot" environments, it is necessary to re-create a mini # root filesystem with entries like /etc, /dev, /bin, and /lib. This # is not the case with this hack. Since the sftp-server subprocess # is invoked as the user's shell, there is no need for the chroot'd # environment to contain a mini system. # ### SECURITY ######################################################## # # WARNING! This patch is not supported by the OpenSSH team and is used # by me for internal use only. Use this patch at your own risk! I am # not responsible for any benefits or consequences to using this patch. # YOU HAVE BEEN WARNED! This software is provided as-is. # # With that being said.... # # I think I've gotten most of the security holes fixed from the # original patch submitted to the openssh-unix-dev mailing list, but # I'm no expert by a long stretch and further suggestions for # improvement are welcome. # # Most notable improvements are: # - don't rely on HOME environment variables, pull from /etc/passwd # - make sure the new root is owned by root and is a directory # # However, since the sftp-server is now SUID, you want to make sure # to do whatever you can to limit user access to it. For example, # if you can, I suggest you block access to port 22 accept from # the remote IPs of your users. # ##################################################################### --- sftp-server.c 2006-01-02 04:40:51.000000000 -0800 +++ sftp-server-chroot.c 2006-09-17 21:06:40.000000000 -0700 @@ -26,6 +26,16 @@ #include "sftp.h" #include "sftp-common.h" +#define CHROOT +#ifdef CHROOT /* needed for getpwuid */ +#include +#include +#include +#include +#include +#include +#endif /* CHROOT */ + /* helper */ #define get_int64() buffer_get_int64(&iqueue); #define get_int() buffer_get_int(&iqueue); @@ -1030,9 +1040,78 @@ buffer_consume(&iqueue, msg_len - consumed); } +#ifdef CHROOT +int +chroot_init(void) +{ + int can_chroot=1; + char *user_dir, *new_root; + struct passwd *passent; + struct stat st_root; + + if (geteuid() != 0) { + /* if we're not SUID root, then we can't call chroot but we can + * go ahead and allow "regular" sftp access. If the requesting + * user has the "/./" in /etc/passwd, we'll strip that and use + * the base as the new home dir. */ + logit("must be SUID root for the CHROOT hack"); + can_chroot=0; + } + + /* grab HOME from /etc/passwd */ + if ((passent = getpwuid(getuid())) == NULL) { + fatal("could not obtain home directory from getpwuid()"); + } + + user_dir = passent->pw_dir; + new_root = user_dir + 1; + + while ((new_root = strchr(new_root, '.')) != NULL) { + new_root--; + if (strncmp(new_root, "/./", 3) == 0) { + *new_root = '\0'; + new_root += 2; + + /* do some sanity checking on new_root */ + if ((stat(user_dir,&st_root)) != 0) { + fatal("can't stat %s: %s",user_dir,strerror(errno)); + } + if (!S_ISDIR(st_root.st_mode)) { + fatal("%s not a directory",user_dir); + } + if (st_root.st_uid != 0 && can_chroot) { + fatal("owner of %s is uid %d, not root's uid",user_dir,st_root.st_uid); + } + if (st_root.st_gid != 0 && can_chroot) { + fatal("group of %s is gid %d, not root's gid",user_dir,st_root.st_gid); + } + + /* ok, it is a dir and root owns it, let's try and use it */ + if (chdir(user_dir) != 0) { + fatal("can't chdir to %s: %s",user_dir,strerror(errno)); + } + if (can_chroot) { + if (chroot(user_dir) != 0) { + fatal("can't chroot to %s: %s",user_dir,strerror(errno)); + } + } + + setenv("HOME", new_root, 1); + break; + } + new_root += 2; + } + return can_chroot; +} +#endif /* CHROOT */ + + int main(int ac, char **av) { +#ifdef CHROOT + int can_chroot=1; +#endif fd_set *rset, *wset; int in, out, max; ssize_t len, olen, set_size; @@ -1049,6 +1128,16 @@ log_init("sftp-server", SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 0); #endif +#ifdef CHROOT + can_chroot = chroot_init(); +#endif + /* drop the need to be root */ + if (can_chroot) { + if (setuid(getuid()) != 0 || setgid(getgid()) != 0) { + fatal("couldn't drop privileges: %s", strerror(errno)); + } + } + in = dup(STDIN_FILENO); out = dup(STDOUT_FILENO);