posted 2016-12-27
Running chown or chmod as root is dangerous. Especially in scripts. Cut that shit out.
Both chown and chmod follow filesystem links. Do you check every file that you chown to verify that it isn't a link? I sure the fuck don't. Symbolic links stand out, but hard links are more or less invisible. To summarize,
So what? Here's what. Most root uses of chown and chmod are exploitable by unprivileged users to gain root.
First, I create a hard link in my home directory:
user $ ln /root/.bashrc /home/mjo/.bashrc
Now I complain to my system administrator that he accidentally messed up the permissions on my ~/.bashrc—which looks just like you'd expect a ~/.bashrc file to look—and could he please chown it to me? It's in my home directory, so what's the worst that could possibly happen?
root # chown mjo:mjo /home/mjo/.bashrc
I told you to cut that shit out. Now I'm root.
user $ ls /root/.bashrc
-rw-r--r-- 2 mjo mjo 1.6K 2016-12-23 10:51 /root/.bashrc
If you think that example is far-fetched, did you know that tar archives can contain absolute symlinks?
root # tar -xf stupid-webapp-1.2.3.tar.xz
root # chown apache:apache stupid-webapp-1.2.3/*
Ha ha, the tarball contained a symlink! You're fucked.
root # ls /etc/passwd
-rw-r--r-- 1 apache apache 1.6K 2016-12-25 14:21 /etc/passwd
root # ls stupid-webapp-1.2.3/
total 8.0K
lrwxrwxrwx 1 mjo mjo 11 2016-12-26 12:53 CHANGES -> /etc/passwd
-rw-r--r-- 1 apache apache 8 2016-12-26 12:53 index.php
-rw-r--r-- 1 apache apache 8 2016-12-26 12:53 README.txt
Now I know what you're thinking…
Go peep CVE-2016-8641 for Nagios, whose init script calls chown as root. The nagios user owns the /var/nagios directory, and the Nagios init script calls chown on /var/nagios/nagios.log every time Nagios is started. To paraphrase,
The problem here is that the nagios user can replace /var/nagios/nagios.log with a symlink, because he owns the whole directory.
nagios $ ln --symbolic --force /etc/passwd /var/nagios/nagios.log
The next time Nagios is started, the init script will give the nagios user ownership of the target of his symlink. Oops. The same problem exists with hard links. If you call chown with --no-dereference, then it won't follow a symlink, so you might think that doing so is safe. Cut that shit out. The “fix” for CVE-2016-8641 changes all of the chown calls to include the --no-dereference flag. The end result is—paraphrasing again,
start() {
chown nagios:nagios /var/nagios
...
chown --no-dereference nagios:nagios /var/nagios/nagios.log
...
}
See a problem? The fix only works for symlinks. The nagios user can just as easily do,
nagios $ ln --force /etc/passwd /var/nagios/nagios.log
The next time Nagios is started, the nagios user will be given ownership of the target of the hard link. Hard links can only point to a file living on the same filesystem, but, if you try hard enough, you can usually find something nasty to own.
As a variation on a theme, chown will not follow symlinks when it is operating recursively. Yet this is equally stupid,
because chown will follow hard links recursively. The same trick,
nagios $ ln /etc/passwd /var/nagios/whatever.log
will get nagios ownership of /etc/passwd the next time Nagios is started.
Just cut that shit out. As root, it's only safe to call chown/chmod in a directory that root controls, and on a path that root controls.
And it rarely needs to be done. A common misuse is to configure some new thing—a new website, a new daemon, a new home directory, whatever—as root, and then chown everything to the account that will actually use it. A better means to that end is to create only what you need as root, and then switch to the other user to do the rest.
For example, this is bad:
root # mkdir /home/derp
root # cp /etc/skel/.bashrc /home/derp/.bashrc
root # echo -e "[user]\n name = Derp" > /home/derp/.gitconfig
root # chown -R derp:derp /home/derp
This is a much safer way to accomplish the same thing:
root # mkdir /home/derp
root # chown derp:derp /home/derp
root # su - derp
derp $ cp /etc/skel/.bashrc ~/
derp $ echo -e "[user]\n name = Derp" > ~/.gitconfig
In both cases you need to ensure that /home is
owned by root:root
and is not world-writable. When you
do need to chown or
chmod as root, be careful, and implement
the what to do: as a user precautions.
Quit using chown and chmod in your init scripts.
OpenRC init scripts should be using the checkpath helper, which doesn't follow links of any kind. Instead of this,
You should be doing this:
The checkpath helper is documented in the openrc-run man page. Generally, the files that are created by a daemon (in a directory that it can write to) will already have the correct ownership and permissions. If not, the umask of the process can be changed: don't use a chmod shotgun as a workaround. You can use checkpath to create and set permissions on files as well; but generally, if you have to “fix” a bunch of permissions whenever a daemon starts, you're doing it wrong.
If some file needs to be created once (with certain permissions), don't do it in the init script. This is bad:
start() {
if [ ! -f /etc/foo/secret.key ]; then
dd if=/dev/urandom bs=1 count=1024 > /etc/foo/secret.key
chmod 640 /etc/foo/secret.key
fi
}
There's an exploit there if /etc/foo is writable by anyone other than root. Instead, document the creation of /etc/foo/secret.key as part of your installation procedure, and then don't run chmod automatically as root. Gentoo packages would be better off creating that file in the ebuild's pkg_config phase. If you really really really really really want to create that key in the init script, use umask to avoid calling chmod:
start() {
if [ ! -f /etc/foo/secret.key ]; then
local orig_umask=$(umask)
umask 027
dd if=/dev/urandom bs=1 count=1024 > /etc/foo/secret.key
umask $orig_umask
fi
}
But seriously, if it needs to be done once, it doesn't belong in an init script. There's still a race condition in the version above. Just don't even try.
You might think that you want to make your daemon user
configurable. It sounds good; all you need is a variable
FOO_USER=foo
, and then you can launch the daemon as
that user. But for that to work, you have to change ownership of the
daemon's files every time the value of the FOO_USER
variable changes. And since you have no way to know when
that is, you must fiddle with the ownership constantly:
start() {
# if FOO_USER is different than the last time we started, we
# need to fix the permissions on its runtime directory.
chown --recursive $FOO_USER:$FOO_USER /var/lib/foo
}
You already know what I'm going to tell you: cut that shit out. But
how can we make things work if the FOO_USER
variable
can change at any time? Sorry: you can't. Might as well get over
it. If some dude wants to dick around with the users/groups that
you've given him, then he's on his own to fix the
permissions—he'll only have to do it once. Doing it
automatically is not an option: the FOO_USER
above can
put hard links in /var/lib/foo and take over
the system.
As a user, you mainly have one avenue to prevent these sorts of attacks: prevent the links from being created in your kernel. The behavior of filesystem links is specified by POSIX and changing it now would be backwards-incompatible, so you shouldn't hold your breath waiting for this to get fixed anywhere else.
The vanilla Linux kernel has two sysctl options that can be used to prevent certain filesystem links from being created. You should go enable both of these right now. If your kernel doesn't have them, they were added in v3.6 (2012), so frickin upgrade already.
When enabled, the fs.protected_hardlinks sysctl disallows hard-linking to shit you don't have any business hard-linking to: files you don't own, and can't read/write. Likewise, the fs.protected_symlinks sysctl will disallow symlinks from a sticky world-writable directory unless you control the target. The Linux kernel documentation explains the same thing with less cussing.
Turn them both on permanently by adding the following to your /etc/sysctl.conf:
To enable them now, before you reboot, do
root # sysctl --write fs.protected_hardlinks=1
root # sysctl --write fs.protected_symlinks=1
The Gentoo kernel enables fs.protected_hardlinks and fs.protected_symlinks by default. Good for y'all.
The folks at grsecurity
maintain a kernel patchset that fixes… a lot of things. If
you set CONFIG_GRKERNSEC_LINK=y
, then it enacts the
same linking restrictions that
fs.protected_hardlinks and
fs.protected_symlinks together would
provide. It does that via another sysctl,
kernel.grsecurity.linking_restrictions, which
is enabled by default.
Hard links only work between files on the same filesystem. In theory, if you could mount each user's home (or working, for daemons) directory on a separate filesystem, then the hard-link attack would be mitigated. That's too impractical to be of any use, however.
CUT THAT SHIT OUT