I'm trying to create a temporary sftp "inbox", so users can share files more easily. To do that I want the sender to generate a temporary key pair, send me the public key securely (perhaps over TLS or a trusted third party), then I can add a line in authorized_keys like this: restrict,command="internal-sftp",chroot-directory="/run/ssh-inbox/1000/05b475...a592b2" ssh-rsa AAAAB3NzaC...kIQX3jyJ2oM= Which allows only sftp access to the following key, chrooted to the given directory (which is owned by root, created by a daemon/suid binary/etc), which is /run/ssh-inbox/<UID>/<SHA256(pubkey)>/ My patch verifies that the key has restrict and command="internal-sftp" set before accepting the key. I tried to stick to the surrounding code style as much as I could, let me know if i need to fix anything. Thanks, - David
diff --git a/.gitignore b/.gitignore index 34a95721..6f227a7f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ survey.sh **/*.out **/*.a autom4te.cache/ +.idea/ scp sftp sftp-server diff --git a/auth-options.c b/auth-options.c index 2d200944..7a9515a4 100644 --- a/auth-options.c +++ b/auth-options.c @@ -135,6 +135,22 @@ cert_option_list(struct sshauthopt *opts, struct sshbuf *oblob, opts->force_command = command; found = 1; } + if (strcmp(name, "chroot-directory") == 0) { + if ((r = sshbuf_get_cstring(data, &command, + NULL)) != 0) { + error("Unable to parse \"%s\" " + "section: %s", name, ssh_err(r)); + goto out; + } + if (opts->chroot_directory != NULL) { + error("Certificate has multiple " + "chroot-directory options"); + free(command); + goto out; + } + opts->chroot_directory = command; + found = 1; + } if (strcmp(name, "source-address") == 0) { if ((r = sshbuf_get_cstring(data, &allowed, NULL)) != 0) { @@ -207,6 +223,7 @@ sshauthopt_free(struct sshauthopt *opts) free(opts->cert_principals); free(opts->force_command); + free(opts->chroot_directory); free(opts->required_from_host_cert); free(opts->required_from_host_keys); @@ -364,6 +381,14 @@ sshauthopt_parse(const char *opts, const char **errstrp) ret->force_command = opt_dequote(&opts, &errstr); if (ret->force_command == NULL) goto fail; + } else if (opt_match(&opts, "chroot-directory")) { + if (ret->chroot_directory != NULL) { + errstr = "multiple \"chroot-directory\" clauses"; + goto fail; + } + ret->chroot_directory = opt_dequote(&opts, &errstr); + if (ret->chroot_directory == NULL) + goto fail; } else if (opt_match(&opts, "principals")) { if (ret->cert_principals != NULL) { errstr = "multiple \"principals\" clauses"; @@ -614,6 +639,32 @@ sshauthopt_merge(const struct sshauthopt *primary, additional->force_command)) == NULL) goto alloc_fail; } + + /* + * When both multiple chroot-directory are specified, only + * proceed if they are identical, otherwise fail. + */ + if (primary->chroot_directory != NULL && + additional->chroot_directory != NULL) { + if (strcmp(primary->chroot_directory, + additional->chroot_directory) == 0) { + /* ok */ + ret->chroot_directory = strdup(primary->chroot_directory); + if (ret->chroot_directory == NULL) + goto alloc_fail; + } else { + errstr = "chroot directory options do not match"; + goto fail; + } + } else if (primary->chroot_directory != NULL) { + if ((ret->chroot_directory = strdup( + primary->chroot_directory)) == NULL) + goto alloc_fail; + } else if (additional->chroot_directory != NULL) { + if ((ret->chroot_directory = strdup( + additional->chroot_directory)) == NULL) + goto alloc_fail; + } /* success */ if (errstrp != NULL) *errstrp = NULL; @@ -660,6 +711,7 @@ sshauthopt_copy(const struct sshauthopt *orig) } while (0) OPTSTRING(cert_principals); OPTSTRING(force_command); + OPTSTRING(chroot_directory); OPTSTRING(required_from_host_cert); OPTSTRING(required_from_host_keys); #undef OPTSTRING @@ -799,6 +851,8 @@ sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, (r = serialise_nullable_string(m, untrusted ? "true" : opts->force_command)) != 0 || (r = serialise_nullable_string(m, + untrusted ? "true" : opts->chroot_directory)) != 0 || + (r = serialise_nullable_string(m, untrusted ? NULL : opts->required_from_host_cert)) != 0 || (r = serialise_nullable_string(m, untrusted ? NULL : opts->required_from_host_keys)) != 0) diff --git a/auth-options.h b/auth-options.h index d96ffede..12d4bdd6 100644 --- a/auth-options.h +++ b/auth-options.h @@ -49,6 +49,7 @@ struct sshauthopt { int force_tun_device; char *force_command; + char *chroot_directory; /* Custom environment */ size_t nenv; diff --git a/auth.c b/auth.c index 086b8ebb..b5e09edc 100644 --- a/auth.c +++ b/auth.c @@ -1006,9 +1006,10 @@ auth_log_authopts(const char *loc, const struct sshauthopt *opts, int do_remote) snprintf(buf, sizeof(buf), "%d", opts->force_tun_device); /* Try to keep this alphabetically sorted */ - snprintf(msg, sizeof(msg), "key options:%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + snprintf(msg, sizeof(msg), "key options:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", opts->permit_agent_forwarding_flag ? " agent-forwarding" : "", opts->force_command == NULL ? "" : " command", + opts->chroot_directory == NULL ? "" : " chroot-directory", do_env ? " environment" : "", opts->valid_before == 0 ? "" : "expires", do_permitopen ? " permitopen" : "", diff --git a/auth2-pubkey.c b/auth2-pubkey.c index 815ea0f2..25577f8f 100644 --- a/auth2-pubkey.c +++ b/auth2-pubkey.c @@ -611,6 +611,20 @@ check_authkey_line(struct ssh *ssh, struct passwd *pw, struct sshkey *key, auth_debug_add("%s: bad key options: %s", loc, reason); goto out; } + /* Allowing users to have a shell which is chrooted may allow them to escape + * with SUID binaries + */ + if (keyopts->chroot_directory && + !(keyopts->restricted + && keyopts->force_command + && strcmp(keyopts->force_command, INTERNAL_SFTP_NAME) == 0)) { + debug("%s: cannot use 'chroot-directory' without 'restrict' and " + "'command=\"" INTERNAL_SFTP_NAME "\"'", loc); + auth_debug_add("%s: cannot use 'chroot-directory' without " + "'restrict' and 'command=\"" INTERNAL_SFTP_NAME "\"'", loc); + goto out; + } + /* Ignore keys that don't match or incorrectly marked as CAs */ if (sshkey_is_cert(key)) { /* Certificate; check signature key against CA */ diff --git a/session.c b/session.c index 8c0e54f7..4ad7eb18 100644 --- a/session.c +++ b/session.c @@ -1362,7 +1362,7 @@ safely_chroot(const char *path, uid_t uid) void do_setusercontext(struct passwd *pw) { - char uidstr[32], *chroot_path, *tmp; + char uidstr[32], *chroot_path, *tmp, *chroot_directory = NULL; platform_setusercontext(pw); @@ -1390,9 +1390,15 @@ do_setusercontext(struct passwd *pw) platform_setusercontext_post_groups(pw); - if (!in_chroot && options.chroot_directory != NULL && - strcasecmp(options.chroot_directory, "none") != 0) { - tmp = tilde_expand_filename(options.chroot_directory, + if (options.chroot_directory) { + chroot_directory = options.chroot_directory; + } else if (auth_opts->chroot_directory != NULL) { + chroot_directory = auth_opts->chroot_directory; + } + + if (!in_chroot && chroot_directory != NULL && + strcasecmp(chroot_directory, "none") != 0) { + tmp = tilde_expand_filename(chroot_directory, pw->pw_uid); snprintf(uidstr, sizeof(uidstr), "%llu", (unsigned long long)pw->pw_uid); @@ -1404,6 +1410,8 @@ do_setusercontext(struct passwd *pw) /* Make sure we don't attempt to chroot again */ free(options.chroot_directory); options.chroot_directory = NULL; + free(auth_opts->chroot_directory); + auth_opts->chroot_directory = NULL; in_chroot = 1; }
_______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev