Unprivileged SSH Account

From WTFwiki
Jump to navigation Jump to search


I've had a need quite a few times to create an "unprivileged" account for use with SSH,
allowing only a command or subset of commands. After researching it, I've mixed and
matched a few methods to come up with one that I like.

A few examples/use cases are given below, and its entirely notable that you're responsible
for sanity checking them and making sure they're secure in your environment. Allowing ANY
access to your servers for ANY purpose carries risks.

"Client" Machine Setup

General Info

Hostname: client.example.com
Account username: username

Generate SSH key (passphrase-less)

 $ ssh-keygen -t rsa -b 2048 -N  -f $HOME/.ssh/id_rsa_nopassphrase
 Generating public/private rsa key pair.
 Your identification has been saved in /home/username/.ssh/id_rsa_nopassphrase.
 Your public key has been saved in /home/username/.ssh/id_rsa_nopassphrase.pub.
 The key fingerprint is:
 1b:8d:5a:4c:14:b6:c9:20:ec:ad:c5:1b:9c:a9:34:7d username@client.example.com

"Server" Machine Setup

General Info

Hostname: server.example.com
Account username: unpriv

Create Account

Let's assume this is a Linux machine, yours may be different.

 $ sudo adduser -c "Unprivileged User" -d /home/unpriv -m -s /bin/bash unpriv

Setup SSH authorized_keys

It will probably be easiest to copy/paste the public key generated on the client, because
instead of the standard line in authorized_keys, we're going to do something a little
strange. The line you insert into $HOME/.ssh/authorized_keys on the server should look
like this:

 from="",command="/home/unpriv/bin/limited.sh" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAtE/.....CulL6pnubGIwR6n5QfGc3Q== username@client.example.com

Make sure permissions on this file (/home/unpriv/.ssh/authorized_keys) are a+r,o+w (644),
but owned by root. This is possibly unnecessary, but a safe step: it will allow you to
safeguard against someone modifying your command= to allow whatever they want, should both
your key on the client get compromised, and you screw up the limited.sh script.

"limited.sh" Script

Now create a directory:

 # mkdir /home/unpriv/bin
 # chown root /home/unpriv/bin
 # chmod 755 /home/unpriv/bin

Create another directory, for use later:

 # mkdir /home/unpriv/dropfile-tmp
 # chown unpriv /home/unpriv/dropfile-tmp

In this directory, create a file called "limited.sh", with the following contents.
This file should also be owned by root, and unwritable (but readable/executable) by
'unpriv' user.


echo "Rejected"
echo "Rejected"
echo "Rejected"
echo "Rejected"
echo "Rejected"
echo "Rejected"
echo "Rejected"
cat >$HOME/dropfile-tmp/list1.txt
exit 0
cat >$HOME/dropfile-tmp/list2.txt
exit 0
exit 0
echo "Rejected"

exit -1

Example Results


The above 'limited.sh' script allows two "dropfile" commands, sort of like standard scp,
but less useful and more restricted. This means no weird dependency on scponly, and no
hassles of setting it up. You get a different set of hassles!

From the client, you can use this as so:

 $ cat /path/to/list1.txt | ssh -i $HOME/.ssh/id_rsa_nopassphrase unpriv@server.example.com dropfile-list1

This will create a file (remember in limited.sh, ">list1.txt"), overwriting its contents
with whatever is in list1.txt on the client. Ideally, you can't use this dropfile command
to create any other file on the system. You also shouldn't be able to read any files.


The above 'limited.sh' script allows the client to run "uptime" as well. Like so:

 $ ssh -i $HOME/.ssh/id_rsa_nopassphrase unpriv@server.example.com uptime
  16:05:14 up 17 days,  6:52,  1 user,  load average: 0.00, 0.00, 0.00

Note that the "command" specified in limited.sh is an arbitrary string, doesn't like to have
spaces or other similar characters in it, and simple executes a block of shell commands when

Failure results

If you call a command that doesn't exist in limited.sh, here's what it looks like:

 $ ssh -i $HOME/.ssh/id_rsa_nopassphrase unpriv@server.example.com dropfile-etc-passwd
 $ ssh -i $HOME/.ssh/id_rsa_nopassphrase unpriv@server.example.com id

Other Ideas

I use this mechanism as a simplified way to call custom nagios plugins on remote hosts, without
something like NRPE, etc. It's a bit more manual in the way it is setup, but it is also more
explicit, which I like.