sudo option "tty_tickets" gives false sense of security due to reused pts numbers

Bug #87023 reported by kko
24
This bug affects 1 person
Affects Status Importance Assigned to Milestone
sudo (Ubuntu)
Fix Released
Wishlist
Unassigned

Bug Description

Binary package hint: sudo

When we have the option "tty_tickets" in '/etc/sudoers', for each new terminal (tty) or pseudo-terminal-slave (pts) where we use 'sudo', we get a sudo ticket in '/var/run/sudo/yourusername/'. These are named "tty1", "tty2" etc. for tty's, and "1", "2", "3", etc. for pts's.

Also, when we invoke a graphical instance of 'sudo' through e.g. 'kdesu', new pts's are reserved for this purpose. Applications started through 'kdesu' require _two_ pts's, and sudo tickets are assigned corresponding to the numbers of these pts's.

This report affects, as I understand it, both 'sudo' and 'kdesu'. I do not know what is the behaviour of 'gksudo' in relation to this bug, so I don't know if this report should also affect it.

Now, I will give three different scenarios in which 'sudo' or a graphical application requiring sudo capabilities can be run without a password, but when the user would _not_ expect to be able to do so.

N.B.: Before each scenario, we must be sure we have no active sudo tickets in the above mentioned directory. Doing 'sudo su', 'rm /var/run/sudo/yourusername/*' and 'exit' in a terminal should be enough to ensure this. (I will start the examples from a clean desktop with e.g. no Konsole windows running. This is not completely necessary, as the _absolute_ numbers of pts's involved are not important to reproduce the behaviour.)

Useful commands for demonstrating what happens are:
'sudo ls --full-time /var/run/sudo/yourusername/'
'ps au' (to show which pts's are in use, and by what processes)
'tty' (run e.g. in Konsole to show which pts we are on)

Scenario 1:
- Open a Konsole (or Yakuake).
- Do 'sudo ls --full-time /var/run/sudo/yourusername', and give your password.
- Do 'tty'
- Do 'exit'. If you are in Konsole, this should close it completely -> now, start a new Konsole. If you are in Yakuake, it should do this automatically for you (spawn a new shell).
- _Now_, we have closed the console we used. It _seems_ reasonable to expect that after we closed the console where 'sudo' was used, the sudo capability would expire.
- Do 'sudo foo' or 'sudo ls --full-time /var/run/sudo/yourusername', and you can do this without giving your password.
- If we again do 'tty', we will see that we are re-using the previous pts number, which is the cause of this phenomenon.

Scenario 2:
- Open a Konsole (or Yakuake).
- Add a new terminal tab, and in this tab do 'sudo foo', giving your password. Do not close this tab.
- Add a third terminal tab, and in this tab do 'sudo foo', giving your password. Now we have three tabs open in Konsole or Yakuake.
- Close tabs two and three. (This will free two pts's.)
(- In the first tab, just to illustrate, run 'sudo ls --full-time /var/run/sudo/yourusername', again giving your password.)
- Run e.g. Adept or Synaptic, expecting to give your password. You should be able to do this without giving your password.
- If we again do 'sudo ls --full-time /var/run/sudo/yourusername' in the first terminal tab, we will notice that two of the timestamps have the same time. What happens is that 'kdesu' will utilise the first two free pts's, which we just freed, and which, incidentally, happened to have valid sudo timestamps.

Scenario 3:
(Optional: - Open a Konsole or Yakuake.)
- Run e.g. Adept or Synaptic, this time giving your password. Close it.
- Open two new Yakuake tabs or Konsole windows or tabs.
(- In the already opened terminal, do 'sudo ls --full-time /var/run/sudo/yourusername', which will give you the number of the valid sudo ticket.)
- In the second newly opened terminal, you should be able to run 'sudo foo' without giving your password. (IIRC, I have done this in the first new terminal instance sometime, possibly. It all depends on which pts number we are given. N.B. Normally 'kdesu' leaves behind only one valid sudo ticket, as in this scenario.)
(- You can do 'tty' to verify that this is the same terminal that we saw had the valid sudo ticket.)

I discovered this misbehaviour in sudo and applications that require sudo capabilities while investigating bug #72545 (and bug #50971).

There are two separate issues here, as I understand it.

Issue 1. As implied by the title of this bug, this gives the user a false sense of security under some conditions.

Case 1.1: The user can (IMO rightly) expect that closing the terminal should _remove sudo capabilities_. It isn't intuitive to expect that a _new_ terminal instance should inherit previously affirmed sudo capabilities. When pts numbers get re-used and sudo tickets are not cleared, this is the resulting problem.

Case 1.2: The user can expect that launching a graphical application that requires sudo capability should _always_ ask for the password. (I won't go into separate password caching capabilities of these, I fear it may be an entirely separate can of worms.) As previously, it isn't intuitive to expect that this should inherit previously affirmed sudo capabilities. This case isn't _necessarily_ all that damaging, e.g. in the case of running Adept or Synaptic, _but_ wrong nonetheless; however, we can imagine a situation where a graphically launched application requiring sudo capabilities could harm the user's system, and would _normally_ alert the user's attention by requiring the user's password.

Case 1.3: Running a graphical application that requires sudo capability should _not_, in my opinion, leave behind a used sudo ticket that can be re-used in a terminal.

Issue 2. This is at least a theoretical vulnerability in the system, if this _same_ user's account can be exploited. As I understand it, pts numbers are assigned to remote connections as well, so by coincidence a remote connection could receive a used but recently freed pts that has a valid sudo tty_ticket.

Case 2.0: If this user's account (in the "admin" group) and _password_ have been compromised, this is a _non-issue_. If the password is out, trying to exploit _this_ behaviour isn't necessary.

Case 2.1: If this user's account can be accessed otherwise, but the password isn't known, this would appear to be an issue. I don't think I can think of many scenarios where this could happen, but I imagine it is still at least a possibility. (Using 'tty_tickets' is in this case of course more secure than the system would be without it.)

Revision history for this message
kko (kko) wrote :

As this issue seems somewhat hard to remedy completely, I decided to comment on possible remedies separately.

It would seem beneficial that unused tty_tickets from '/var/run/sudo/yourusername' should be cleared. More specifically, the tickets for such tty's and pts's that _are not in use_ at any point in time should be cleared.

Now, what process could be responsible for doing this?

A. The kernel and "devpts", which is responsible for assigning new pts's at '/dev/pts/*'. It could be argued that this would be the right place, to tackle the authorisation issue at a low level. It could also be argued the opposite, that authorisation issues do not belong here, that it is not the kernel's job to do things for sudo. This would mean that this should be handled in userspace.

B. A root level "sudo daemon" that would keep an eye on tty's and pts's on one hand and sudo's tty_tickets on the other hand. Whenever a terminal or a pseudo-terminal is freed, the daemon would clear the appropriate sudo tty_ticket for all users, and possibly make a note in a log. (The tty's probably would need to be handled differently than the pts's.)

C. For graphical applications requiring sudo access, the application that granted the access could also clear the ticket when the sudo capability isn't needed anymore. (E.g. 'kdesu' or 'kdesud'. It seems the 'kdesud' is used for caching the password given to 'kdesu'.)

D. A work-around is to use "timestamp_timeout=0" in '/etc/sudoers', but this takes away a usability feature from sudo.

A couple non-working "solutions" follow:
E. "The command 'sudo' should be responsible for clearing the ticket." This can't be done, because sudo is not running when the terminal is freed.
F. "The terminal or 'bash' should be responsible." This can't be done because the terminal or the shell are not root-level processes, and so can't remove root-owned tty_tickets.

Revision history for this message
kko (kko) wrote :

One more possible solution / workaround:
G. For (primarily) _non_-graphical use-cases of sudo, and to clean-up after graphical applications that lack this functionality -> A new usage option for 'sudo' that is like 'sudo -K' but, instead of only operating on the ticket of the current terminal, cleans _all_ the tickets in '/var/run/sudo/yourusername/'.

The possible downside is that this requires that the user manually (or semi-manually) evokes it. The upside is the same as that stated in 'man sudo' for 'sudo -k' (and, implied, for 'sudo -K'): "This option does not require a password and was added to allow a user to revoke sudo permissions from a .logout file."

As demonstrated by this report and some others, it is quite easy and all the more common to have a number of terminals open, and to use 'sudo' in those or through graphical means. In view of this, I think the option presented here would often prove useful. I wouldn't actually mind if 'sudo -K' would operate on all of the current user's tickets by default.

Revision history for this message
Kees Cook (kees) wrote :

Thanks for this well-described bug report! I wonder if some people would describe the "authenticated old tty" issue as a "feature"? I would tend to agree, though, that it is surprising to open a terminal and not get prompted. I find your "Issue 2" the most compelling -- this should not be allowed to happen.

As you've hinted, the "original" solution, in the design of sudo in general, was for users to add "sudo -K" to their ~/.bash_logout file. However, I suspect that isn't a proper solution (especially for gksu/kdesu). I haven't checked, but if udev receives notifications about pts devices being removed, perhaps it could be responsible for running "sudo -K" (or something similar)? That might solve all three scenarios, though I'm curious about the double pts allocation with kdesu.

Revision history for this message
kko (kko) wrote :

The double pts requirement with 'kdesu' could (even) be by design to "circumvent" the issue of old tickets lying around (compare with the bugs referenced above), but this would be bad design. It seems more likely, though, that it is just a side effect of starting a process and then forking it into different parts that have different tasks.

description: updated
Revision history for this message
kko (kko) wrote :

Also, what is problematic with the cases under "Issue 1", is that these cross the border between graphical and command line sudo access, which is very unintuitive for the user. It's OK if 'kdesu' remembers that I've just given my password, _if_ I've accepted this in its options. However, having a sudo capable shell isn't really OK, if I've only given my password to 'kdesu'. And vice versa.

(As a parenthesis: A real-life case I can think of is wanting to try out and './install' some 3rd party software, which has its own installation method (be it text-based or graphical). This could even have the option to _either_ install globally (if given the permissions) or as a fall-back to install locally for the user only. A password prompt at this point is very indicative to the user about where exactly things will be installed, and missing the password prompt can lead, at best, to confusion.)

Kees Cook (kees)
Changed in sudo:
status: Unconfirmed → Confirmed
Revision history for this message
Kees Cook (kees) wrote :

After talking to gregkh and hpa, it seems there are no udev events for pts add/remove. :( To do this, the pts kernel could would need to be written to use kobjects.

Revision history for this message
Kees Cook (kees) wrote :

That should read "the pts kernel code would need to be written to use kobjects."

Revision history for this message
kko (kko) wrote :

I'm wondering - should this report be marked as concerning also 'kdesu', i.e. "kdebase", due to it leaving behind a valid sudo ticket?

Also, I am curious to know - does 'gksudo' leave behind a valid sudo ticket, or is the ticket cleared afterwards? (And, consequently, if a ticket is left behind, should this report be marked as also concerning the package providing 'gksudo'?)

description: updated
Revision history for this message
Martin Pitt (pitti) wrote :

kko: No, this only affects sudo and perhaps udev -- things like kdesu that build on top of sudo do not have any particular privileges and cannot fiddle with tickets.

Revision history for this message
kko (kko) wrote :

Thank you for the information. Taking a look at kdesu's permissions I see what you mean.

Revision history for this message
Kees Cook (kees) wrote :

I see a few options for dealing with console log-outs:

1) Add "sudo -K" to /etc/skel/.bash_logout via patch to bash pkg. (This doesn't catch upgrades, and doesn't handle people not using bash.)

2) Dig into pam's session management and write new module to be added as login and ssh required "session" modules. (Seems a bit sloppy.)

3) Using a recent patch to mainline, write a logout-watching daemon that cleans up after sudo when a pts goes away (this seems really like too heavy a solution)

4) Patch the pts code to use kobjects and hook up a listener to "remove" events. (This is a lot of work.)

As for the GUI apps, I think sudo (and the GUI su-ing apps) should be patched to add something like "--single-shot", where no tty-based ticket is left behind.

Revision history for this message
kko (kko) wrote :

Incidentally, I was pondering this too, after my last comment, so I'll comment on these suggestions.

For GUI apps, I agree with you. It should be easy for 'kdesu' or 'gksudo' to:
- Obtain needed privileges and launch the app requiring sudo privileges.
- Immediately afterwards call 'sudo -K' on the appropriate pts.

This should take care of the GUI side of the problem - see further though.

I don't really understand how 'kdesud' is supposed to work. I suppose it just sits there for as long as kdesu is configured to remember the password given to it, and is supposed to catch further launches of 'kdesu'. (It doesn't for me, so I wonder _why_ it is launched at all, but it's a different issue.) I don't know if 'kdesud' needs the ticket at all - according to 'man kdesu' it wouldn't: "The passwords are NOT written to disk, but stored in memory using a special program, kdesud."

If the ticket isn't needed to implement the grace period (i.e. remembering the password for a set time) functionality in GUI sudo apps, the option "--single-shot" definitely sounds like a cleaner way to implement this.

For the CLI, I tend to agree that your suggestions 3 and 4 seem heavy, although number 4 would probably be a good solution. I can't really say anything about suggestion 2.

I came up with the same thought as your suggestion 1 also. Basically, I thought it would be useful, but while trying it out I found a serious drawback. Unfortunately, .bash_logout is only handled for _login_ shells, i.e. it is not executed for inter-active shells from Konsole. (People can also remove '~/.bash_logout', but then it's their choice. I also wondered about the case where the shell (e.g. hanged and) got terminated in an abnormal way and it couldn't execute the logout script - in this case a solution not dependent on the shell would be more reliable.)

Revision history for this message
Richard Laager (rlaager) wrote :

Calling sudo -K from a GUI sudo wrapper is a bad idea. It would invalidate any tickets for other terminals as well. Solutions #2 and #4 seem reasonable. I think #2 would be easier to do, but I'm not an expert on any of this.

Kees Cook (kees)
Changed in sudo (Ubuntu):
importance: Undecided → Wishlist
Revision history for this message
Joshua Kwan (joshk) wrote :

FYI, gksu (as of libgksu version 2.0.11) allocates a new pseudoterminal, runs sudo -v to acquire a token, then uses g_spawn_sync in the parent process once the authentication is successful. (This allows gksu to be executed with the 'Defaults requiretty' option, which is enabled by default on RHEL.) Obviously, this fix doesn't work properly with the tty_tickets option. I'm going to write another patch to try and address this issue.

Until then, I wouldn't be using the new libgksu in Karmic!

Revision history for this message
Leonid Evdokimov (darkk) wrote :

I have part of idea of another workaround for pty (but not for tty).

Open console:
$ ls -l --full-time --time=ctime `tty`
crw------- 1 darkk tty 136, 2 2009-11-14 15:41:39.260720360 +0600 /dev/pts/2

Close console, open console again:
$ ls -l --full-time --time=ctime `tty`
crw------- 1 darkk tty 136, 2 2009-11-14 15:43:31.320713474 +0600 /dev/pts/2

So ticket may include ctime of pty.

On the other hand, malicious attacker can change ctime of the /dev/pts/2 and he may get correct ctime watching /dev/pts (he can't get ctime from ticket data). I don't know if it may be avoided using something like POSIX ACL's and I assume that POSIX ACL's are not supported by ptsfs. Maybe someone has better idea.

Kees Cook (kees)
security vulnerability: yes → no
Revision history for this message
Removed by request (removed3425744) wrote :

This report is now a few years old and I'm seeing an interesting behavior of sudo:

1. First I'm Cleaning /var/lib/sudo/$USER as root with "rm -rf /var/lib/sudo/sworddragon/*".
2. Then I'm opening a terminal with my user account.
3. Typing tty returns /dev/pts/2.
4. Then I'm typing "sudo true" and entering my correct password.
5. Typing "md5sum /var/lib/sudo/sworddragon/2" as root returns "6ec673eac24f4e2fc0c5fa149eebfcef /var/lib/sudo/sworddragon/2"
6. Then I'm closing the terminal on /dev/pts/2 and opening it again.
7. Typing tty returns /dev/pts/2.
8. On typing sudo true I'm asked again for my password.
9. Instead of typing my password I'm typing now as root again "md5sum /var/lib/sudo/sworddragon/2" which returns "6ec673eac24f4e2fc0c5fa149eebfcef /var/lib/sudo/sworddragon/2".
10. Typing "ls -a /var/lib/sudo/sworddragon" as root returns ". .. 2".

This means even on getting the same pts without expiring of /var/lib/sudo/sworddragon/2 sudo asks me for my password. Can somebody tell me where is hiding the security mechanism here? Also are the cases in this ticket still a problem or are they maybe already fixed?

Revision history for this message
Seth Arnold (seth-arnold) wrote :

Sworddragon, I believe this code from plugins/sudoers/check.c in check_user() prevents the tty reuse problem:

    /* Stash the tty's ctime for tty ticket comparison. */
    if (def_tty_tickets && user_ttypath && stat(user_ttypath, &sb) == 0) {
        tty_info.dev = sb.st_dev;
        tty_info.ino = sb.st_ino;
        tty_info.rdev = sb.st_rdev;
        if (tty_is_devpts(user_ttypath))
            ctim_get(&sb, &tty_info.ctime);
    }

    if (build_timestamp(&timestampdir, &timestampfile) == -1) {
        rval = -1;
        goto done;
    }

    status = timestamp_status(timestampdir, timestampfile, user_name,
        TS_MAKE_DIRS);

    if (status != TS_CURRENT || ISSET(validated, FLAG_CHECK_USER)) {
        /* Bail out if we are non-interactive and a password is required */
        if (ISSET(mode, MODE_NONINTERACTIVE)) {
            warningx(_("sorry, a password is required to run %s"), getprogname());
            rval = -1;
            goto done;
        }

        /* XXX - should not lecture if askpass helper is being used. */
        lecture(status);

        /* Expand any escapes in the prompt. */
        prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt,
            user_name, user_shost);

        rval = verify_user(auth_pw, prompt);
    }
    /* Only update timestamp if user was validated. */
    if (rval == TRUE && ISSET(validated, VALIDATE_OK) &&
        !ISSET(mode, MODE_IGNORE_TICKET) && status != TS_ERROR)
        update_timestamp(timestampdir, timestampfile);

Thanks

Revision history for this message
Removed by request (removed3425744) wrote :

Thanks for the answer. If I'm not wrong ctime has a nanosecond resolution. The only way to abuse this is if:

- The tty/pts gets closed.
- The time adjusts to the past (for example due to ntp).
- The next opened console/terminal gets the same number and is created exactly at the same time.

While this is theoretically possible it should only be seen as an extremely rare race-condition. Also normally while the user opens a terminal, types sudo and enters his password at least a few seconds will be gone. Normally the adjustment of ntp is only a few milliseconds. If I'm not wrong here too ntp refuses to adjust the time to a too far point.

So this is a very nice security mechanism of sudo (does gksudo build on sudo too and benefit from this?).

Revision history for this message
Richard Laager (rlaager) wrote :

I might be overstepping here, but I'm marking this as Fix Released. I can confirm, as with comment #16, that this is no longer an issue. I'm on Vivid.

Changed in sudo (Ubuntu):
status: Confirmed → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Duplicates of this bug

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.