October 20, 2008

OpenSSH: Going flexible with forced commands

Filed under: Security, UNIX & Linux — Tags: , — martin @ 9:32 am

As we all know, it is possible to use SSH not only for obtaining an interactive login session on a remote machine, but also for executing commands remotely. For instance, the following command will log on to myserver.example.com, execute “uname -a” and return to the local shell:

ssh myserver.example.com uname -a

(The local SSH client returns the exit code from the remote command, if you’re into this kind of detail.)

You might have some users (or scheduled automatisms) that you don’t want to be able to log on to that machine at all, but who should be permitted to execute only a given command. In order to achieve this, you can configure key-based authentication. Once this has been done, the key can be prefixed with a number of configuration options. Using one of these options, it is possible to enforce execution of a given command when this key is used for authentication.

In this example from ~/.ssh/authorized_keys, the user wants to look at the process list, so we set the command to “ps -ef”.

command="/bin/ps -ef"

Using this, when the user tries to log in, or tries to execute an arbitrary command, “/bin/ps -ef” is executed instead and the SSH session terminates.

In addition to enforcing a command, it is advisable to disable a number of advanced SSH features, such as TCP and X11 forwarding. Assignment of a pseudo terminal to the user’s SSH session may also be suppressed, by adding a number of additional configuration options next to the forced command:


Here’s what a full entry from ~/.ssh/authorized_keys might look like:

command="/bin/ps -ef",no-port-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAp0KMipajKK468mfihpZHqmrMk8w+PmzTnJrZUFYZZNmLkRk+icn+m71DdEHmza2cSf9WdiK7TGibGjZTE/Ez0IEhYRj5RM3dKkfYqitKTKlxVhXNda7az6VqAJ/jtaBXAMTjHeD82xlFoghLZOMkScTdWmu47FyVkv/IM1GjgX/I8s4307ds1M+sICyDUmgxUQyNF3UnAduPn1m8ux3V8/xAqPF+bRuFlB0fbiAEsSu4+AkvfX7ggriBONBR6eFexOvRTBWtriHsCybvd6tOpJHN8JYZLxCRYHOGX+sY+YGE4iIePKVf2H54kS5UlpC/fnWgaHbmu/XsGYjYrAFnVw== Test key

This is quite nice: We have successfully limited this user to requesting a process list.

This is called an SSH forced command.

So much for the introduction. 😀

Here’s what I’m really getting at today – What, if we want the user to not only execute a single command, but a number of commands, such as:

– Show process list (ps)
– Show virtual memory statistics (vmstat)
– Stop and start the print server (/etc/init.d/cupsys stop/start)

Following the approach described above, this would give us four key pairs, four entries in ~/.ssh/authorized_keys, and four entirely different invocations of SSH on the client side, each of them using a dedicated private key. In other words: An administrative nightmare.

This is where the environment variable $SSH_ORIGINAL_COMMAND comes in. (This nice facility was pointed out to me last week by G., who had read about it somewhere but wondered what it might be useful for.)

Until now, all we know is that with a forced command in place, the SSH server ignores the command requested by the user. This is not entirely true, though. The SSH server does in fact remember the command that was requested, stores it in $SSH_ORIGINAL_COMMAND and thus makes it available within the environment of the forced command.

With this in mind, it is possible to allow more flexibility inside forced commands, without the need to go crazy with countless key pairs. Instead, it is possible to just create a wrapper script that is called as the forced command from within ~/.ssh/authorized_keys and decides what to do, based on the content of $SSH_ORIGINAL_COMMAND:

# Script: /usr/local/bin/wrapper.sh 

		ps -ef
		vmstat 1 100
	"cups stop")
		/etc/init.d/cupsys stop
	"cups start")
		/etc/init.d/cupsys start
		echo "Sorry. Only these commands are available to you:"
		echo "ps, vmstat, cupsys stop, cupsys start"
		exit 1

It is important to be aware of potential security issues here, such as the user escaping to a shell prompt from within one of the listed commands. Setting the “no-pty” option already makes this kind of attack somewhat difficult. In addition, some programs, such as “top”, for example, have special options to run them in a “secure” read-only mode. It is advisable to closely examine all programs that are called as SSH forced commands for well-meant “backdoors” and to find out about securing them.

It’s up to you to decide based on your own situation, whether you want to run this wrapper as the root user or if you prefer to use password-less “sudo” commands to raise privileges where needed.

If you encounter problems while debugging $SSH_ORIGINAL_COMMAND, please make absolutely sure that you are authenticating with the correct key. I found it helpful to unset SSH_AUTH_SOCK in the window where I do my testing, in order to prevent intervention from identies stored in the SSH agent.


  1. hi,

    i used the same one for my (german) Nagios Howto (you know, where 🙂 ..) and the idea from here (also german), but somebody says, that it isn’t very secure. But I’m sorry, i can’t get it, why it should be unsecure.

    Comment by Denny — October 20, 2008 @ 12:38 pm

  2. Denny, the security issue is not in $SSH_ORIGINAL_COMMAND, but in the way you (and the author of the other blog post) implemented it. The caller could, for example, just append “; shutdown -h now” to the third argument (properly escaped by himself) and shut down your machine. As long as the private key remains protected, this is not exploitable, but it’s still bad style.

    Comment by martin — October 20, 2008 @ 1:51 pm

    • SSH properly escapes $SSH_ORIGINAL_COMMAND, that is not to say you shouldn’t be diligent about what you do with user-supplied input. Also, it is the public key that contains the forced command. The public key is stored on the server where the command is going to be executed. So if someone has the ability to modify the authorized_keys file and change the command, then they already have shell access and access to that account, so breaking out of ‘jail’ isn’t really a concern.

      Comment by Anonymous — March 15, 2014 @ 6:35 am

      • ssh *is* *not* *escaping* $SSH_ORIGINAL_COMMAND ! get your facts straight and test. Yes I just tested. version 6.2p2

        Comment by gunstick — March 28, 2014 @ 6:00 pm

  3. hi Martin, thanks for this hint.

    Comment by Denny — October 24, 2008 @ 8:16 pm

  4. […] “OpenSSH: Going flexible with forced commands” (#!/bin/blog; 2008.10.20) – https://binblog.info/2008/10/20/openssh-going-flexible-with-forced-commands/ […]

    Pingback by SSH, OpenSSH « Eikonal Blog — February 3, 2011 @ 9:39 pm

  5. just got down to design a wrapper script allowing any parameters but not executing them. It got lots of debug, but basically it’s just 10 lines. Have a look:

    echo “cmd=$SSH_ORIGINAL_COMMAND”
    # to execute a command you have to replace all spaces separating parameters
    # with a # character (or change that in the $separator)
    # example: ssh server ‘ls#-l#my file with spaces#;#pwd#;#is not executed’
    if [ “$1” != “ls” ]
    echo “only ls is allowed”
    echo “executing: ‘${1}’ ‘${2}’ ‘${3}’ ‘${4}’ ‘${5}’ ‘${6}’ ‘${7}’ ‘${8}'”
    for i in “$@”
    echo “> $i”
    echo “run:”

    Comment by gunstick — October 29, 2012 @ 12:07 pm

  6. This topic is still really up to date. To understand what the security concerns are, you need first to read all of that:

    Then, it is still possible, in my opinion, to get an acceptable level of security by only using command not prone to shell escape, AND, you will absolutely also need to filter user input.

    Something like the following: (not tested at all)
    # Script: /usr/local/bin/wrapper.sh
    export VAR_CLEAN=”`echo “${SSH_ORIGINAL_COMMAND}” | tr -cd ‘[:alnum:] [:space:]’`”
    case “${VAR_CLEAN}” in
    ps -ef
    vmstat 1 100
    “cups stop”)
    /etc/init.d/cupsys stop
    “cups start”)
    /etc/init.d/cupsys start
    echo “Sorry. Only these commands are available to you:”
    echo “ps, vmstat, cupsys stop, cupsys start”
    exit 1

    Comment by johnythepowwa — March 18, 2013 @ 1:03 am

  7. […] to blog entry "OpenSSH: Going flexible with forced commands (https://binblog.info/2008/10/20/opens…rced-commands/). I did not comment there because I do not have the required WordPress, Twitter or Facebook […]

    Pingback by ssh authorized_keys command= script example - LinuxQuestions.org — June 25, 2015 @ 5:48 am

  8. […] Openssh going flexible with forced commands Restricting SSH commands O’Reilly Online Guide — SSH: Per-Account Server Configuration […]

    Pingback by SSH Keys |  RuNetBSD — March 4, 2016 @ 12:36 pm

  9. [Disclosure: I wrote sshdo which is described below]

    There’s a program called sshdo for doing this. It controls which commands may be executed via incoming ssh.

    It’s available for download from http://raf.org/sshdo/ (read manual pags here) and from https://github.com/raforg/sshdo/ (GPLv2+).

    It has a training mode to allow all commands and a –learn option to construct the configuration needed to allow the encountered commands in future. Then training mode can be turned off and any unknown commands after that will not be executed. It also has an –unlearn option to remove commands from the configuration that are no longer in use so as to maintain strict least privilege as requirements change over time. It can also be configured manually instead.

    It’s easier and safer than hard-coding allowed commands in a shell script.

    Comment by raforg — April 23, 2019 @ 2:27 am

  10. […] “remote: forced command” that suggested me that the server somewhat has a command whitelist. I found that it is possible to configure SSH only to execute certain commands, so I tried different command, nothing […]

    Pingback by Exploiting VulnHub Tr0ll2 machine – Alkampfer's Place — September 18, 2019 @ 10:12 pm

RSS feed for comments on this post. TrackBack URI

Leave a Reply to Anonymous Cancel reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Create a free website or blog at WordPress.com.

%d bloggers like this: