Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Shell Corner: Safely Sharing Screen Sessions with sudo


UnixReview.com
March 2006

Shell Corner: Safely Sharing Screen Sessions with sudo

Hosted by Ed Schaefer
Written by Rod Knowlton

Listing 1: sh_screen

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 script
The 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_screen
As 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 SCREEN
Each 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_screen
Sharp-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 sscreen
This 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_addviewer
ss_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 ssallow
The 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_listsessions
And 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 ssls
Everything 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_screen
Configuration 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 = SSCOMMANDS
Now 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: jack
Manny 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/otherscreen
Conclusion

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].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.