Don't abuse su for dropping user privileges (2015)

50 points
a year ago
by aargh_aargh



This article is nonsense. I'm afraid.

Firstly, the su program isn't standardized by POSIX, so everything we say about it is system-dependent.

On the systems where that trick is used, su is documented as having that feature.

When su is executed by root (so that its setuid bit is moot), it knows this, and allows a credentials change to a specified account without prompting for a password. This is a feature and the example cron jobs are using it correctly to obtain their desired effect of impersonating that user.

su is not "dropping privileges"; it's impersonating a specific user, in a command dispatched from a root cron job or other script.

Thus all that might be wrong is the rhetoric used to describe the scripts, not what the scripts are actually doing, and only if we are nitpicky.

"Dropping privileges" has a strict meaning, referring to the situation that some user bob invoked a setuid root executable, which then disrobes itself of the root privilege, changing its effective-root uid to just bob again.

When root changes to another account that is unrelated to the real user ID, nevertheless, that situation fits the description of "dropping privileges" because that account is strictly less privileged than root.

> The right way ... Create a small wrapper binary with C

That's a silly nonstarter.

> The new implementation called fork() and only dropped privileges in the child, retaining a privileged account parent process that could call the PAM "user session" cleanup function once the child exited.

Privileged parents waiting for unprivileged children to terminate is an established pattern in Unix-like systems.

E.g. PID 1 (init or systemd) is privileged and handles the termination of everything that has no parent.

The author is not connecting the dots here: reporting some actual privilege escalation bug in the use of "su bob command ...".

Interactively, when you are root, you can do this:

  root # su bob
  /home/bob$ exit
  root #
This exit back to root is not a security hole, and it is not relevant to the scripted scenario at all. Bob has not actually gained root access. The order of messages on the TTY isn't what determines the security context semantics.
a year ago


> This exit back to root is not a security hole

Wait... Doing just "su bob" is vulnerable to a known decades old exploit. You should do either "exec su bob" or "su -P bob" or you just gave bob root rights on every Linux kernel older than 6.2 (and I think on 6.2 and more recent the default is still insecure?).

It's explained here with a simple PoC exploit (I tried the exploit and it works/worked) and it's been frontpage on HN in the past I think:

a year ago


> Consider this python sample and assume it’s automatically being run at logon from the .bashrc of a technical account, such as postgres, to which the root user changes by typing su - postgres

From su man page: "su is mostly designed for unprivileged users, the recommended solution for privileged users (e.g., scripts executed by root) is to use non-set-user-ID command runuser(1) that does not require authentication and provides separate PAM configuration. If the PAM session is not required at all then the recommended solution is to use command setpriv(1)."

a year ago


Why does PAM have sessions? I thought it was a layer of indirection over whether you’re using a password file or NIS or LDAP to verify passwords.

a year ago


PAM provides authentication, authorization, session management and password changing.

a year ago


I think that with that ioctl in place, once root hands the TTY file descriptor to processes in the bob security context, it can no longer trust that TTY object to be secure. Doing an "exec su ..." doesn't necessarily fix it, unless that has the effect of destroying the session (logging out). If root keeps using that TTY and trusting its input, it's a problem.

Say you want to fix the problem, while continuing to support that dubious ioctl. What you need is the ability for the TTY driver to send a SIGKILL to every user bob process that has an open file descriptor to that TTY. After killing all those processes, then restore the original TTY settings before the su command was run using tcsetattr with TCSAFLUSH to throw away any pushed input.

Even then, there could be exploits that use weaknesses in the terminal emulator to reflect input.

Also, social engineering hacks. The same human that operated the root shell is su'd into bob, and malicious software installed by bob can fool that human. For instance, by simulating a system login prompt.

a year ago


I still have some SysV init and I use it to respawn Oracle clients in runlevel 4.

I use this C to knock down the privilege from root to the app client account before I execve() the target:

  if(setgroups(NULL) || setresgid(g,g,g) || setresuid(u,u,u))
    fprintf(stderr, "permissions error\n");
I hope that I did this correctly. Even if incorrect, it's worked since 2013.

Interesting that the post's linked article does not even mention real, effective, and saved user/groupIDs.

a year ago


> Interesting that the post's linked article does not even mention real, effective, and saved user/groupIDs.

Even worse, the article doesn't even mention TIOCSTI, which is another big reason, you should not use `su` this way (see e.g.

The program run under su keeps the same TTY and can use the TIOCSTI ioctl to push data back into the TTY input buffer. When the program exits and the parent reads from stdin, it receives those bytes. If the parent happens to be a root shell from which you dropped privileges using su, you have a problem.

a year ago


TIOCSTI is irrelevant. When one is dropping privileges, in a system cron job or in a process supervised by one's favourite service management system, there is no terminal involved. TIOCSTI simply doesn't enter into the picture at all.

Only when one is in a terminal login session and using su to elevate / add privileges, does TIOCSTI become relevant. But no-one here is saying not to use su to add privileges.

People blame su, sudo, and (as the person at did) doas for this feature of operating system kernels. The right thing to do with TIOCSTI it to just eliminate it from the kernel. OpenBSD did back in version 6.

Sadly, the argument from Alan Cox, Linux developer, when this was proposed years ago was that it should stay in Linux, and all of the programs like su, sudo, and doas should have even more things to do in the parent process that sticks around, namely pump I/O to and from a controlling pseudo-terminal that su/sudo/doas sets up for the shell subprocess, breaking (as the maintainer of OpenDoas pointed out) the long-standing notion that the child processes belong to the same terminal session and share things like a single getlogname() with the login shell.

6 years after and, there is no sign of anyone doing anything of the sort in any su or doas implementation. (It was briefly in one su implementation, but taken out in 2017 for being a "stupid hack":

Fortunately, some six months ago Linux developers finally made TIOCSTI removable and the right course of action is available to those that want it:

a year ago


The linked page also describes how this problem is solved by using "su -P" or the "use_pty" option with sudo, but says that is disabled by default. Any idea why the security fix isn't enabled by default?

a year ago


I assume that is why I see gosu [1] used so often for dropping privileges in Docker containers.

It's good to have some explanation as to why su is unsuitable for this.


a year ago


The article seems quite unclear about potential consequences of using su this way.

I typically use su to run some program in its own user account to ensure:

- it has its own homedir and doesn't fill mine with garbage.

- there is some level of isolation from the rest of the system for security, a basic "jail". I'm not trying to protect against targeted attacks from extremely competent threat actors here, but rather trying to stick software into its own user account so it can only access that account, with isolation of the same level as if I had manually logged in to a secondary account.

Can such programs break out of their "jail" when using su?

Or is the author of the article just angry for other reasons?

a year ago


The article does not talk about security problems. It's about whether your daemon works correctly.

1. Depending on the environment, process supervision can break if there is an unexpected process sitting between the supervisor and the supervisee. The article describes how the switch to PAM forced the introduction of an in-the-middle process responsible for closing the PAM session. The old su used exec, which avoids an in-the-middle process.

2. Su uses the shell of the target user, and will outright not work if the target user has a "nologin" shell.

The article goes on to mention correct workarounds for this like daemontools setuidguid, Runit chpst or just rolling your own exec wrapper.

a year ago


Yes, the program can likely setuid to the original user with su, as the session is the parent session and still active.

This is not a behavior you want of a jail. Use chroot, LXC or your own setuid wrapper that removes the privilege.

a year ago


Well, os.setuid and os.seteuid in Python give me "PermissionError: [Errno 1] Operation not permitted". Do you have an example of how this could be done?

a year ago


Every process in your system either still has an ancestor that is still running and has superuser privileges. Either because that was the real ancestor, or else because that ancestor is PID 1 due to reparenting.

A process cannot use its existing ancestral sessions to gain control of their account.

a year ago


> angry

Seems like an unnecessary assumption.

a year ago


So, what is the correct default (nothing self-built, please) command to start a shell using the privilege level of some less privileged user? Setuigid doesn't seem to be included by default in distributions.

a year ago


a year ago


According to man su(1):

“su is mostly designed for unprivileged users, the recommended solution for privileged users (e.g. scripts executed by root) is to use non-set-user-ID command runuser(1) that does not require authentication and provide separate PAM configuration. If the PAM session is not required at all then the recommend solution is to use command setpriv(1).”

a year ago


The su command is correct for that purpose, as is sudo.

This is kind of a subtle point that the article makes—the problem is that “su” is not good for dropping user privileges in general, but it does work if you are a user trying to spawn an interactive shell as another user.

Keep in mind that you are giving control of your terminal to that less-privileged account, and that this is a potential point of privilege escalation.

a year ago


Writing dæmon instead of daemon is not cute and not clever.

a year ago


i think its cute

a year ago