UnixReview.com
March 2006
Shell Corner: Safely Sharing Screen Sessions with sudo
Hosted by Ed Schaefer
Written by Rod Knowlton
This month, Rod Knowlton addresses a sudo security problem that John Spurgeon and I introduced in our "Using Screen in Scripts" column. Rod describes the problem as "small"; we feel he's being too kind.
Safely Sharing Screen Sessions with Sudo
by Rod Knowlton
A while ago, Ed and John Spurgeon published an article in Sys Admin magazine on the use of screen in scripts. Within the article, there was a small problem with the section on using sudo to share screens— it would give anyone using the shared session the ability to create a shell running as root!
Since just one compromised machine is one too many, I dashed off an email to Ed explaining the bug and including a possible way to mitigate it. In this article, I'll describe a couple of tempting but insecure methods for easing the work of sharing screen sessions, reveal their faults, and then present a set of scripts that act together to provide a safe, easy to use alternative.
The goal and original problem
Ed and John's script was meant to make life simpler for the casual screen user. Rather than having screen installed setuid and having the users learn the various screen commands related to sharing screens, they would use sudo to run a script with root privileges. The script would either create a screen session with the requested name and force them into a shell under their own id, or, if a session by that name already existed, attach them to it.
The original code (don't do this):
#!/bin/ksh # path: /usr/local/bin/share_screen <P> screen_name=${1:-share_screen} <P> if ! /usr/local/bin/screen -x "$screen_name" then /usr/local/bin/screen -S "$screen_name" \ /usr/local/bin/ssh $(logname)@$(uname -n) fi # end scriptThe security problem arose from the fact that, although the shell the user is presented with is under her own id, the instance of screen is running as root. This means that any time screen's internal command screen (Ctrl-A,c, by default) is issued, the new window created will have a root shell.
A First Pass at a Fix
Here's the fix I emailed to Ed (you probably shouldn't do this, either):
#!bin/ksh # path: /usr/local/bin/share_screen <P> screen_name=${1:-share_screen} <P> if ! /usr/local/bin/screen -x "$screen_name" then <P> # start the session detached, so we can send a # command to it before the user logs in /usr/local/bin/screen -m -d -S "$screen_name" \ /usr/local/bin/ssh $(logname)@$(uname -n) <P> # disallow multiple windows. Using the "-X" option # allows us to do this without having to manipulate # or replace any ..screenrc files. /usr/local/bin/screen -S "$screen_name" -X maxwin 1 <P> # now attach /usr/local/bin/screen -x "$screen_name" <P> fi <P> # end share_screenAs is probably clear from the comments, the idea was to give the screen the internal command
maxwin 1
at startup so that additional
windows could not be created.
While this fix addresses the problem identified, it still has a couple of problems. The screen invocations still hold root privileges, and the login shell that is shared will allow anyone connecting to operate with the privileges of the user that first created it.
What Was the Question?
I've shown the two approaches not to use partly because they contain methods that will show up in the final fix, but mostly to help illustrate something to keep in mind when scripting — be sure you're answering the right question.
My fix addressed the question "How can a shared screen session running as root prevent users from creating a root shell?", but that's not the question on which I should have been focused. The right question is the one Ed and John were trying to answer: "How can users share screens without learning commands and without screen being setuid root?"
A New Approach
The best (if not the only) way to keep users from creating a root
shell is simply to avoid running screen as root. We could create a
dummy user with restricted permissions and have the shared_screen
users use sudo -u dummy shared_screen
so that new shells will only
have the dummy's privileges, but that doesn't address the problem of
all the sharing users being able to work with the creator's privileges
in the inner ssh shell.
Setting screen to setuid root
would enable mult-iuser commands,
but we really want to keep setuid root programs to a minimum. We'll abstract
away the multiuser commands in a script so the users don't have to deal with
them, and we'll use sudo to make screen setuid, but only at launch time.
You'll notice in all of the following scripts that I'm using fully qualified pathnames for all executables used. This is always a good idea when scripting, but is especially important when root privileges are in play. Another good idea, for portability, is to create a configuration file and assign all of the executables' pathnames to read-only constants, like so:
typeset -r SCREEN=/usr/local/bin/screen export SCREENEach script can then load the configuration file via the dot operator and use the constants rather than hard-coded pathnames, like so:
#!/bin/ksh # . /usr/local/bin/ssconfig ${SCREEN} -x "foo"I'm not doing this here because I feel code examples are often clearer without that extra level of indirection, but if you want to install these scripts on machines with varying directory structures, you'll probably want to consider it. setuid_screen...strike that...sh_screen Let's create a script that sets setuid on screen, launches screen with all arguments it was passed, and then removes setuid from screen. Since we don't want to wait for screen to return before removing setuid, we'll send ourselves a little "message in a bottle" to get the job done, like so:
#!/bin/ksh # # sh_screen - launches screen setuid and # then switches it back # # path: /usr/local/bin/sh_screen # this file should be chmod 700, owned by root /bin/chmod +s /usr/bin/screen # adjust paths to fit your system # the message in a bottle (/bin/sleep 1;/bin/chmod -s /usr/bin/screen) & #while the message floats, launch screen as the login user /usr/bin/sudo -u $(/usr/bin/logname) /usr/bin/screen $* # wait for the background process to finish wait # end sh_screenSharp-eyed readers probably noticed that this script introduces a race condition, in that it's dependent on the sudo invocation of screen completing before the background process removes setuid. You can check whether the race condition is affecting you by executing sudo
./sh_screen
under your non-root login. If all went well, you'll
find your screen session socket in /tmp/screens/S-username/, otherwise
it'll be in /tmp/uscreens/S-username/, which indicates that screen was
not setuid root when launched. If the latter is the case, you may need
to increase the sleep parameter.
A Wrapper Script for sh_screen
While security by obscurity alone is no security, adding obscurity to
a process that is already secure by other means does increase the
security of the process. To this end, in addition to limiting user
access to sh_screen via the /etc/sudoers file, we'll launch sh_screen
from a wrapper script like so:
#!/bin/ksh # # sscreen - wrapper script for sudo'ing sh_screen # # path: /usr/local/bin/sscreen # this file should be chmod 755, owned by root /usr/bin/sudo /usr/local/bin/sh_screen $* # end sscreenThis accomplishes two things: it simplifies usage for the end user, as they don't have to remember or even know that sudo is involved; and it allows us to assign root ownership and owner-only read, write, and execute permissions to sh_screen. Cranking down the permissions on sh_screen means that even an end user that we've allowed to run it can't read it in search of ways to exploit it. I'm sure you noticed that I changed the name of the script from the more informative setuid_screen to sh_screen before I started this section. This is because a name like setuid_screen, even if the user can't read the script, might be a little too tempting to some. A Couple of Helpers Now we're almost ready to revisit the original script and use our new tools to make it a little safer to use. But, there are a couple of issues we'll have to deal with that weren't present when all sessions ran as root. The first issue is that viewers will have to be explicitly invited by the creator. To make this simpler for the creator, we'll take advantage of the fact that by default screen checks to see if it's being run from inside a copy of itself and, if it is, acts on that copy rather than creating a whole new instance. To allow read-only viewers to attach to your session, you need to add them with
acladd
and then restrict their access with aclchg
. We'll do all of these
things from within a single sudo script.
Adding a viewer:
#!/bin/ksh # # ss_addviewer - grant a user read-only access to an # sscreen session from within that session # # path: /usr/local/bin/ss_addviewer # this file should be chmod 700, owned by root SSCREEN=/usr/local/bin/sscreen if [[ "${STY} = "" ]] then # we're not in an sscreen session echo "This script is only meant to run from" echo "within an existing sscreen session" exit fi read "username?User to add: " # make sure we're in multiuser mode ${SSCREEN} -X multiuser on # screen makes you add a full privileged user, then subtract # privileges ${SSCREEN} -X acladd ${username} # take away the ability to type in the session # and to execute screen commands ${SSCREEN} -X aclchg ${username} -w-x '#?' # give them the right to detach (Ctrl-A, d) ${SSCREEN} -X aclchg ${username} +x detach # end ss_addviewerss_addviewer's user-friendly wrapper:
#!/bin/ksh # # ssallow - sudo wrapper for ss_adduser # # path: /usr/local/bin/ssallow # this file should be chmod 755, owned by root /usr/bin/sudo /usr/local/bin/ss_addviewer # end ssallowThe second issue is that each user's screen sockets are stored in a directory that only that user can access. This means we'll need a sudo run script if we want to list sessions that are already established. This isn't strictly necessary, since the creator can tell the viewer(s) the name of the session when they add them, but it'll be nice to have in case someone forgets what they were told. List sessions:
#!/bin/ksh # # ss_listsessions - list established sscreen sessions # # path: /usr/local/bin/ss_listsessions # this file should be chmod 700, owned by root /usr/bin/find /tmp/screens -type p \ | /bin/sed 's/^.*screens\/S-//;s/\/.*\./\//' # end ss_listsessionsAnd its wrapper:
#!/bin/ksh # # ssls - sudo wrapper for ss_listsessions # # path: /usr/local/bin/ssls # this file should be chmod 755, owned by root /usr/bin/sudo /usr/local/bin/ss_listsessions # end sslsEverything Old is New Again Now it's time to put together our new shared_screen script, which actually looks a lot like the original:
#!/bin/ksh # # shared_screen - safely share screen sessions # # path: /usr/local/bin/shared_screen # this file should be chmod 755 screen_name=${1:-share_screen} if ! /usr/local/bin/sscreen -x '${screen_name}' then /usr/local/bin/sscreen -S ${screen_name##*/} fi # end shared_screenConfiguration and Use To allow Manny, Moe, and Jack to use the shared_screen scripts, we'll use
visudo
to add the following lines to our /etc/sudoers file.
There's no need for us to include the wrapper scripts, only the three
"wrapped" scripts:
User_Alias SSUSERS = manny, moe, jack Cmnd_Alias SSCOMMANDS = /usr/local/bin/sh_screen, \ /usr/local/bin/ss_addviewer, \ /usr/local/bin/ss_listsessions SSUSERS ALL = SSCOMMANDSNow if Manny wants to start up a session named "service" and let Moe and Jack view it, he does as follows:
[manny ~] shared_screen service Password: ******** (sscreen starts0 [manny ~] ssallow User to add: moe [manny ~] ssallow User to add: jackManny then tells Moe and Jack the name of the session, so they can attach to it like so:
[moe ~] shared_screen manny/service Password: ********or if they've forgotten the name:
[jack ~] ssls Password: ******** manny/service otheruser/otherscreenConclusion
While slightly more complicated than the original, this approach provides three huge advantages. It doesn't require the user to remember to use sudo or to issue screen commands directly. It doesn't leave screen sitting around setuid root all of the time. And it doesn't involve working inside of a session that's actually owned by root.
Note: The -X option used in this article is only present in newer versions of screen. If you have trouble using these scripts, try upgrading to a version of screen >= 4.0 (some 3.9.x versions also support it)
Rod Knowlton is an IBM Certified Advanced Technical Expert in AIX and a Senior Unix Systems Administrator for a large midwestern insurance company. An inveterate geek, his idea of a good time is learning a new programming language. His favorite time of all, though, is any time spent in the company of his best friend and wife, Amanda Shankle-Knowlton. He can be reached at [email protected].