27

I've searched for a viable answer to this question, and most of the answers include advice on why to not do it. However, here's the scenario, and what makes it necessary:

I have a console app, and in each user's .profile, there is a startup command for the app, and directly after the command that starts it up, there's an "exit" command, which logs them out of the system. I only want them to be able to access this console app through the interface provided by it. Upon startup, the app presents the user with a list of clients that can be accessed through the app, with each client having their own data directory. Users are granted access to only the clients that they will need access to.

Now here's the problem: If I give the users SSH access, they will also be able to log in using an SFTP client, which will give them direct access to the data directories for the app, which is VERY undesirable, since that will also give them access to the data directories to which they should not have access.

This was such a simple thing to do when using a telnet/FTP combination, but now that I want to give the users access from anywhere on the internet, I haven't been able to find a way to shut them out of SFTP, while still allowing them access to the shell where they can run the app.

sosaisapunk
  • 391
  • 1
  • 3
  • 5
  • 8
    I forget the details, but I think SSH allows you to restrict users to running a single command when they log in. They don't get full shell access if you set this up. It might be useful for your use case. (After all it is possible to emulate SFTP access using SSHFS. Just disabling SFTP will not stop a moderately determined user from getting to any file to which their user account has access.) – David Z Dec 19 '14 at 22:21
  • 3
    Additionally you might want to consider "chrooting" your users. and for spare data access, it looks like a design issue – Dennis Nolte Dec 19 '14 at 22:51
  • 2
    Using `.profile` for that sounds like the wrong solution. I believe setting up the user with an alternate shell would make a lot more sense. – kasperd Dec 20 '14 at 23:23
  • 3
    **Do not try to use the `.profile` trick to restrict access, as it is trivial to bypass.** (see my answer for details) – Aleksi Torhamo Dec 21 '14 at 10:25

4 Answers4

32

As others have mentioned, disabling sftp isn't anywhere near sufficient - a user with unrestricted ssh access can view any file that their account has permissions to view, can modify anything they have permission to modify, and can easily download anything they can read to their own machine. The only way to stop them from doing this is to actually restrict their access. It's also not ideal to rely on .profile to restrict users, as that's not what it's for (Edit: As Aleksi mentions in his answer, it is in fact trivial to bypass .profile; the thing about .profile is that it's for convenience, not security, so it's not intended to restrict the user. Use things designed for security, like the things below, to provide security).

There are two basic ways to do this: you could restrict them via file permissions, or force them to only execute your console app. The second way is better: Assign users who should be restricted to the console app to a group (e.g. customers); then, in sshd_config, add the following lines:

Match Group customers
ForceCommand /path/to/app

What this does is make it so that all connections from users in that group open the console app; they cannot start anything else, including the sftp server tool. This also stops them from doing anything else with the system, and unlike .profile, does so using the SSH server itself (.profile restricts them at the shell, ForceCommand also prevents doing other things that don't involve starting a shell). Also unlike .profile, this is designed as a security thing; it is specifically made to resist a malicious user evading it.

The (probably inferior) alternative would involve creating a new user to run the console app. You would then restrict the data directories to that user, set the console app owned by that user, and set u+s on the program. This is the setuid bit; it means that someone who runs the console program does so with the permissions of the program's owner. That way, the user does not themselves have access to the directories, they only get it through the program. However, you should probably just use ForceCommand, as that restricts all access to "just run this program".

cpast
  • 495
  • 4
  • 13
  • 6
    I'd like to add that `ForceCommand` doesn't prevent SSH port forwarding. – nyuszika7h Dec 20 '14 at 13:59
  • 1
    Actually, relying on `.profile` is more than "not ideal"; it is easily bypassed (see my answer for details) and thus provides no protection whatsoever, only false sense of security. – Aleksi Torhamo Dec 21 '14 at 10:14
  • @AleksiTorhamo Ah. I suspected you could bypass it, but wasn't certain how (I generally suspect that things not intended for security are bypassable). Thanks for the details on how! – cpast Dec 21 '14 at 16:37
25

Edit:

In case it's not obvious, the following answer isn't intended as a secure method of preventing SFTP from being used by anyone with shell access to the server. It's just an answer that explains how to disable it from external visibility. For a discussion about user level security, see answers from @cpast and @Aleksi Torhamo. If security is your focus, this answer is not the proper one. If simple service visibiliy is your focus, then this is your answer.

We now continue to the original answer:


Comment out sftp support in sshd_config (and of course restart sshd):

#Subsystem sftp /usr/lib/openssh/sftp-server

Wesley
  • 32,320
  • 9
  • 80
  • 116
  • 2
    Sometimes line can be `Subsystem sftp internal-sftp` – Kondybas Dec 19 '14 at 21:32
  • 1
    I have `Subsystem sftp /usr/libexec/sftp-server` But that did the trick. Thank you very much – sosaisapunk Dec 19 '14 at 21:43
  • 16
    This does not sound plausible. If you can execute arbitrary commands on the server you can execute the sftp server-side program. Even if it's not installed you could re-implement it as a long shell command and have the sftp client send this command instead of sftp. This method might make it less convenient to use sftp but there's no way to prevent a user who can run arbitrary commands from using those commands to make file transfers. – R.. GitHub STOP HELPING ICE Dec 20 '14 at 01:38
  • @R.. Or, for that matter, they could tar and base64-encode another user's data directory, send it to pastebin with `wget` or `curl`, copy it to their own computer, and base64-decode it and untar it to get the full data directory. Even if there's nothing ssh-related they can do, they can still download data directories. – cpast Dec 20 '14 at 06:23
  • This doesn't provide any protection whatsoever, since the `.profile` trick mentioned in the question is easily bypassed (see my answer for details), and like others already mentioned, you can just directly run the command (or any other command for that matter). – Aleksi Torhamo Dec 21 '14 at 10:21
  • @R.. I think my assumption was that a user who could log in via SSH would naturally not have the permissions to edit the required configs for sftp or start a service. To me it seemed so wildly counterintuative to turn a service off that a user had the power to turn on again, that I didn't even consider the implications of not talking about ForceCommand, etc. `¯\_(ツ)_/¯` – Wesley Dec 22 '14 at 18:47
  • @R.. Once the user is logged in, and the app I spoke of starts, there is no way for them to start commands other than the ones invoked from within the app itself. This would include programs such as tar or wget. – sosaisapunk Dec 24 '14 at 13:44
  • 1
    @sosaisapunk That isn't true. The users can run whatever command they want by using `ssh user@host command`, since `.profile` won't be run at all if you do that, and thus your app won't start at all. They can get full shell access by simply saying `ssh -t user@host bash`. Just try it and you'll see. The subsystem is just a command alias; if they could use sftp before, they can still use it - and any other command they want. Read my answer below. – Aleksi Torhamo Dec 24 '14 at 16:16
  • So, what I'm getting here is a really good reason to just turn off SSH, and revert back to telnet, but run it over a secure connection, such as a VPN. It seems that would solve most of the security concerns posted here. Am I getting warm? – sosaisapunk Dec 29 '14 at 22:18
  • 1
    @sosaisapunk: No, the point is that you can do this with ssh, but not with `.profile` as it isn't meant for security and is easily bypassed. In my answer, I listed three different methods of doing it with ssh. In short, just move the invocation of your app from `.profile` to a shell script and either 1) set the shell script as the user's shell 2) set the shell script as (properly matched) `ForceCommand` in `sshd_config` 3) switch to public-key authentication and set the shell script as `command` in `.ssh/authorized_keys`. (Also, you should use @name so that people get notified of the comments) – Aleksi Torhamo Jan 01 '15 at 16:36
  • 1
    @sosaisapunk: In fact, I just verified that I could bypass `.profile` with telnet too, at least with bash as the shell, by sending an environment variable that causes bash to not read `.profile`. I'm not that familiar with telnet, so there might even be some way to do it without relying on a specific shell. So the telnet + `.profile` solution that you had before was in fact broken in general as well. `.profile` just isn't meant for security / restricting access. Just use one of the methods I listed, cpast's answer even has more detailed instructions for the `ForceCommand` method. – Aleksi Torhamo Jan 01 '15 at 16:47
  • Nicely done @AleksiTorhamo. – Wesley Jan 01 '15 at 16:50
15

Do not attempt to do this with .profile because it provides no security whatsoever and restricts exactly nothing!

It doesn't matter what you put in .profile, since you can bypass it by simply giving a command to run on the ssh command line, like this: ssh user@host command. You can still get normal shell access by doing ssh -t user@host bash.

Disabling the sftp subsystem, like mentioned in another answer, doesn't help at all. Subsystems are essentially just aliases to commands, and you can still use sftp normally by doing sftp -s /path/to/sftp-executable user@host.

Like cpast and some commenters have said, you should use the proper mechanisms for restricting access. That is,

  • Use ForceCommand in sshd_config
  • Use passwordless login and command="..." in .ssh/authorized_keys
  • Change the user's shell to something that restricts what the user can do

Notes:

  • command="..." only applies for one key, so it doesn't restrict ssh login for the user using a password or another key
  • You might also want to restrict port forwarding etc. (port forwarding, x11 forwarding, agent forwarding and pty allocation are the ones I've heard about)
    • AllowTcpForwarding etc. in sshd_config
    • no-port-forwarding etc. in .ssh/authorized_keys
  • If you have other daemons (like FTP) running, you should verify that they don't let the user in (Some daemons make this decision based on the user's shell, so if you change that, you might want to re-check this)
  • You can change the user's shell to a script that does what you want; it's either run without arguments or like script -c 'command-the-user-wanted-to-run'
  • Both ForceCommand and command="..." run the command through the user's shell, so they don't work if the user's shell is set to eg. /bin/false or /sbin/nologin

Disclaimer: I'm no expert on the matter by any means, so while I can say that the .profile thing isn't safe, I can't promise there isn't some "gotcha" with the other methods that I don't know about. They're safe as far as I know, but I wouldn't be the first person to be wrong on the internet.

Aleksi Torhamo
  • 247
  • 1
  • 7
  • 1
    It seems that at least for `ForceCommand` and forced passwordless login with `command=` in `authorized_keys`, while it has been bypassed before, that's due to a bug in the server: it is _intended_ to be secure against a malicious user, and a user bypassing it counts as a serious vulnerability in the SSH server (and is thus a priority fix). As you point out, `.profile` is bypassable by design: as it's not considered a security feature, a user bypassing it is perfectly OK from the shell developer's point of view. – cpast Dec 21 '14 at 16:46
  • 1
    @cpast: Yeah, I was thinking about the existence of more features like port forwarding. I mean, if you didn't know that ssh allows port forwarding and just used `ForceCommand` to restrict access, and had a daemon that only listens to connections locally and doesn't perform authentication, the ssh user could still access the daemon. The features I listed are the ones eg. git hosting solutions usually disable, and I haven't seen any additional "evil-looking" ones in the manpages, but I haven't found any kind of official "this is how you use `ForceCommand` securely" -document yet, either. – Aleksi Torhamo Dec 22 '14 at 01:59
  • Admittedly `~/.profile` is user editable and not helpful for security, but could you make cpast's technique meaningful by putting it into `/etc/profile`? You could check the UID to limit it to the right users. – chicks Dec 24 '14 at 15:24
  • 1
    @chicks: No, it has the exact same problem. The problem isn't that the file is user-editable; The problem is that both files can be bypassed entirely. The files are only used for login shells, which you get if you just say `ssh user@host`, but if you tell ssh to run a command - ie. `ssh user@host command` - you no longer get a login shell and the files aren't touched at all. So you can trivially bypass whatever restrictions someone tried to create with the files by simply giving an extra argument to ssh. – Aleksi Torhamo Dec 24 '14 at 15:50
  • 2
    @chicks: Also, I just realized that you referred to cpast; the method in cpast's answer doesn't use `.profile`, it uses `sshd_config` and should be safe. He only mentions `.profile` as a method that you shouldn't use to do this. – Aleksi Torhamo Dec 24 '14 at 15:58
1

It is possible to enable SSH and disable SFTP both globally and per user/group.

I personally need this because I want to give access to some git repositories over SSH, and I like to disable systems that are not needed. In that case SFTP is not needed.

Globally

You can disable SFTP for all users in a couple of ways.

The missing subsystem

The SFTP daemon used by SSH can be configured through the Subsystem keyword. From the sshd_config(5) manual:

Subsystem
        Configures an external subsystem (e.g. file transfer daemon).
        Arguments should be a subsystem name and a command (with optional
        arguments) to execute upon subsystem request.

        The command sftp-server(8) implements the “sftp” file transfer
        subsystem.

        Alternately the name “internal-sftp” implements an in-process
        “sftp” server.  This may simplify configurations using
        ChrootDirectory to force a different filesystem root on clients.

        By default no subsystems are defined.

The last line suggests that it should be enough to not define any subsystem for "sftp".

A false lie

You could also disable SFTP by setting the SFTP daemon used by SSH to something unusable. For example, configure the "sftp" subsystem to /bin/false:

Subsystem sftp /bin/false

When something would try to log in via SFTP, the SSH daemon would try to spawn the "sftp daemon" /bin/false. The /bin/false program does only one thing, and that is to return an error code. The SFTP connection attempt is effectively denied.

Per user/group

It is also possible to disable SFTP per user, group, or a couple of other criterias.

This does not work if you want your user to get a regular shell prompt. Nor does it make sense, as you could circumvent most stuff if you have shell access. It will only work if you only want to give access to a specific program.

Matching

To match a set of users, you could configure SSH with the Match keyword. From the sshd_config(5) manual:

Match
        ...

        The arguments to Match are one or more criteria-pattern pairs or the
        single token All which matches all criteria.  The available criteria
        are User, Group, Host, LocalAddress, LocalPort, and Address.  The
        match patterns may consist of single entries or comma-separated
        lists and may use the wildcard and negation operators described in
        the PATTERNS section of ssh_config(5).

        ...

A couple of examples:

  • Match User eva matches the "eva" user
  • Match User stephen,maria matches the "stephen" and "maria" users
  • Match Group wheel,adams,simpsons matches the "wheel", "adams", "simpsons" groups

If you want more information, there are loads in the sshd_config(5) manual.

Forced command

Normally you get the user's login shell when you connect via SSH, but SSH can be configured to force a certain command. The command is forced for any SSH connection, including SFTP, and thus you might have the option to force the command you want.

The command to force can be configured with the ForceCommand keyword. From the sshd_config(5) manual:

ForceCommand
        Forces the execution of the command specified by ForceCommand,
        ignoring any command supplied by the client and ~/.ssh/rc if
        present.  The command is invoked by using the user's login shell
        with the -c option.  This applies to shell, command, or subsystem
        execution.  It is most useful inside a Match block.  The command
        originally supplied by the client is available in the
        SSH_ORIGINAL_COMMAND environment variable.  Specifying a command of
        “internal-sftp” will force the use of an in-process sftp server that
        requires no support files when used with ChrootDirectory.  The
        default is “none”.

So you can force the constrained command you want using ForceCommand <your command>. For example:

Match User kim
        ForceCommand echo 'successful login man, congrats'

Example

In my case where I want to give git access, I only need the user to have access to git-shell. This is the section that disables SFTP for my git users, along with some security options:

Match Group git

        # have to do this instead of setting the login shell to `git-shell`,
        # to disable SFTP
        ForceCommand /usr/bin/git-shell -c "$SSH_ORIGINAL_COMMAND"

        # disable stuff we don't need
        AllowAgentForwarding no
        AllowTcpForwarding no
        AllowStreamLocalForwarding no
        PermitOpen none
        PermitTunnel no
        PermitTTY no
        X11Forwarding no
aude
  • 191
  • 1
  • 4
  • In my case, I wanted to only allow MySQL access (disallowing both SFTP access and SSH terminal windows) for a particular authorized key (i.e., no changes to the sshd_config file). I managed to do this with the following ~/.ssh/authorized_keys options for the particular key: command="/usr/bin/echo 'Only MySQL access is allowed.'",no-pty,no-X11-forwarding,permitopen="127.0.0.1:3306" – Martin_W Jun 05 '18 at 21:29