Openwall GNU/*/Linux - a small security-enhanced Linux distro for servers
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date: Thu, 8 Jun 2017 20:05:34 +0200
From: Solar Designer <>
Subject: Vixie/ISC Cron group crontab to root escalation



Two group crontab to root privsep bypasses were found: one in Cron
itself (with some dependency on the rest of the system) and the other in
Debian/Ubuntu postinst script for it.  These issues are minor because
they are only privsep bypasses, so to exploit them one would need to
gain group crontab first (via some other vulnerability).  Many systems
(and upstream ISC Cron default build/install) don't even have this
privsep layer, so there's nothing in those to bypass in the first place.
For those that do, it provides some defense-in-depth, even if
non-perfect.  Some fixes and workarounds have been developed and
proposed, some more are expected.

These findings are a result of a discussion with people from Qualys,
OpenBSD, Debian, and Ubuntu.


Some historical notes on Vixie Cron (now ISC Cron):

"[...] Vixie cron, originally coded by Paul Vixie in 1987.  Version 3 of
Vixie cron was released in late 1993.  Version 4.1 was renamed to ISC
Cron and was released in January 2004.  Version 3, with some minor
bugfixes, is used in most distributions of Linux and BSDs.

In 2007, Red Hat forked vixie-cron 4.1 to the cronie project and
included anacron 2.3 in 2009."

First, this issue isn't exactly a vulnerability in ISC Cron.  While
Paul Vixie kindly accepted changes into upstream ISC Cron implementing
optional support for installation with the crontab(1) program SGID to
group crontab, so that security-hardened systems have fewer changes to
maintain, this isn't how upstream ISC Cron is configured and installed
by default.  The current (and probably final?) upstream version is
"cron_4.1.shar 201 KB 01/23/2004" at (the
only remaining upstream URL?) and it has CRON_GROUP commented out and
installation as SUID root:

X                       /* Define this to run crontab setgid instead of
X                        * setuid root.  Group access will be used to read
X                        * the tabs/atjobs dirs and the allow/deny files.
X                        * If this is not defined then crontab and at
X                        * must be setuid root.
X                        */
X/*#define CRON_GROUP   "crontab"       /*-*/


X                       $(INSTALL) -c -m 4111 -o root -s crontab $(DESTBIN)/

Thus, in a default install and on many systems this layer of privilege
separation is not enabled anyway, and so there's no "group crontab to
root escalation" since a suitable vulnerability in crontab(1) (or in the
SUID binary startup code in the kernel, dynamic linker, or libc) would
result in a root compromise right away.

It's only some systems that do install crontab(1) SGID crontab that are
affected by the issue described below, and only to the relatively minor
extent of at worst bringing them on par with the systems that did not
take advantage of this privilege separation layer.

Now let's proceed to the actual issue, fixed in OpenBSD yesterday.

In a mostly unrelated posting to the distros list, the Qualys Security
Advisory team casually mentioned that on OpenBSD "/usr/bin/at is
SGID-crontab (which can be escalated to full root privileges)".  I asked
them "How can group crontab be escalated to full root privileges?" and
on May 26 they provided the below detail:

| 1/ Group crontab can write to both:
|  25740    4 drwxrwx--T    2 root     crontab       512 Jul 26  2016 /var/cron/atjobs
|  25741    4 drwx-wx--T    2 root     crontab	   512 Jul 26  2016 /var/cron/tabs
| 2/ Both "at" and cron files are checked for the correct owner, but
| there's an exception for root-owned files. "at" files must have their
| user execute bit set so we couldn't exploit this loophole through "at"
| files, but there's no such check for cron files.
| 3/ On OpenBSD, at least one daemon allows you to create a temporary file
| (in /tmp) that belongs to root and contains your own (arbitrary) data,
| and you can then hard-link it to /var/cron/tabs.
| 4/ This doesn't give you root immediately, because there's already a
| /var/cron/tabs/root (and /var/cron/tabs is sticky), but:
| - you can either obtain an admin's privileges and trojan his su (or does
|   anyone have a better idea?);
| - or you can obtain the privileges of one of the system accounts, wait
|   until the next reboot, delete root's crontab through one of OpenBSD's
|   startup scripts, and then create your own root crontab.
| This includes a few OpenBSD-specific tricks, but maybe the main issue
| (hard-linked root-owned files whose content is attacker-controlled) is
| more generic?
| We thought this was a known issue

I wish that at this point we switched to public discussion on
oss-security, since the issue wasn't directly triggerable and was only a
privsep bypass, but out of inertia the discussion proceeded in private.

The gist of the attack is that the original program managing the
temporary file deletes its own link to the file, which results in the
remaining malicious link having st_nlink of 1, thus satisfying the
"st_nlink == 1" requirement.  When I first read Qualys' response above,
this detail looked potentially new to me.  However, further in the
discussion I looked at the patch I wrote for Owl back in 2000:;content-type=text%2Fplain

and found this comment in there:

+ * The link count check is not sufficient (the owner may delete their
+ * original link, reducing the link count back to 1), but this is all
+ * we've got.
+ */

So I hadn't exactly overlooked this issue, but more like neglected it
(at the time, Owl normally used -ow kernels that had hard link
restrictions by default, preventing the attack).

In 2002, Todd C. Miller made similar changes in OpenBSD, including the
st_nlink check, but not the comment.  The commit message said:

"crontab is no longer setuid root, it is now setgid crontab.
These changes were modelled after the Owl version of vixie-cron,
but developed independently."

These are also the changes that Paul Vixie partially accepted upstream.

We've since sync'ed the Vixie Cron package in Owl to newer OpenBSD's
(thus, gaining Todd's reimplementation of this functionality instead of
my original) in 2004 and again in 2006.  Dmitry V. Levin did the same in
ALT Linux, too.  This provided built-in support for "at", which isn't
found in upstream Vixie/ISC Cron (per Todd, it's something Paul chose
not to include in there).

In 2003, the original patch went from Owl into Debian (and thus Ubuntu),
along with the original comment above:

Thanks to Salvatore Bonaccorso of Debian for locating the above URL for
the current discussion.  In there, we also see the addition of a
postinst script changing permissions on existing crontab files.  This
was also pointed out by Seth Arnold of Ubuntu, who wrote:

| - postinst scripts are already brittle
| - postinst scripts themselves become a target for elevating privileges if
|   they'll just set the permissions as needed
| But the Debian/Ubuntu packaging already has scripts for this purpose:
| ...
| # Fixup crontab , directory and files for new group 'crontab'.
| # Can't use dpkg-statoverride for this because it doesn't cooperate nicely
| # with cron alternatives such as bcron
| if [ -d $crondir/crontabs ] ; then
|     chown root:crontab $crondir/crontabs
|     chmod 1730 $crondir/crontabs
|     # This used to be done conditionally. For versions prior to "3.0pl1-81"
|     # It has been disabled to suit cron alternative such as bcron.
|     cd $crondir/crontabs
|     set +e
|     ls -1 | xargs -r -n 1 --replace=xxx  chown 'xxx:crontab' 'xxx'
|     ls -1 | xargs -r -n 1 chmod 600
|     set -e
| fi

Qualys promptly broke this script, replying to Seth:

| Hmmm, you're right, the script itself is vulnerable to
| group-crontab-to-root escalation of privileges:
| root@...ian:~# usermod --append --groups crontab nobody
| root@...ian:~# su --login --shell /bin/bash nobody
| No directory, logging in with HOME=/
| nobody@...ian:/$ id
| uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup),107(crontab)
| nobody@...ian:/$ cd /var/spool/cron/crontabs
| # for example, this exploits the chown
| nobody@...ian:/var/spool/cron/crontabs$ ln --symbolic /etc/passwd- nobody
| # for example, this exploits the chmod
| nobody@...ian:/var/spool/cron/crontabs$ touch ./--reference=.RFILE
| nobody@...ian:/var/spool/cron/crontabs$ chmod 0666 .RFILE > .RFILE
| nobody@...ian:/var/spool/cron/crontabs$ ln --symbolic /etc/passwd 600
| nobody@...ian:/var/spool/cron/crontabs$ ls -l /etc/passwd*
| -rw-r--r-- 1 root root 1378 May 10 17:16 /etc/passwd
| -rw------- 1 root root 1378 May 10 17:16 /etc/passwd-
| # run the postinst script
| root@...ian:~# dpkg-reconfigure cron
| chown: missing operand
| Try 'chown --help' for more information.
| update-rc.d: warning: start and stop actions are no longer supported; falling back to defaults
| nobody@...ian:/var/spool/cron/crontabs$ ls -l /etc/passwd*
| -rw-rw-rw- 1    600 crontab 1378 May 10 17:16 /etc/passwd
| -rw------- 1 nobody crontab 1378 May 10 17:16 /etc/passwd-
| So this is a known issue?  (there may be more ways to exploit it --
| spaces, newlines, option injections, etc).

So this looked like two issues to fix: the temporary file hard link
attack (in OpenBSD, Debian, Ubuntu, ALT Linux, and Owl) and the postinst
script (in Debian and Ubuntu).

For the first issue, I suggested that the Cron daemon should check group
ownership of crontab files and require them to be of group crontab.
Ditto for at job files.  Luckily, crontab files were already created
with group crontab on them.  The at job files (where supported) were
created with the user's group on them, but having them work across this
one-time Cron upgrade isn't as important.

This defeats attacks via hard links to unrelated daemons' temporary
files, as well as via daemons' log files that are rotated and daemons'
"constant" database files (like cdb) that are replaced atomically with
rename(2) - these are the various attack vectors that came up in the
discussion.  However, Qualys came up with another attack scenario:

| 1/ we have somehow obtained GID crontab privileges;
| 2/ we found a SUID-root binary (this is hypothetical) that creates a
| temporary file with suitable permissions and contents, that we can
| hard-link to the crontab directory (possible, since we have GID
| crontab).
| But since we have GID crontab, the SUID-root binary directly creates the
| temporary file with GID crontab, and once hard-linked to the crontab
| directory this file would therefore be accepted by crond

One way to defeat this attack would be to additionally mark intentional
crontabs and at jobs with unusual file permissions - such as setting the
sticky bit on them.  This was considered, but we decided against it so
far because to maintain existing cron jobs across this one Cron upgrade
we'd require something like Debian's postinst script, and that's risky.

One aspect where we can make a little bit use of file permissions anyway
is to defeat "cross-links" between crontabs and at jobs.  Luckily, these
files already differ in permissions: the at jobs have the u+x bit set on
them, whereas crontabs don't.  I proposed that we harden the permission
checks even further, to require the exact permission bits that these two
types of files are being created with.

As to dealing with Qualys' hypothetical attack via some unrelated SUID
binary, Todd went for hardening the file parsing, to require that both
types of files have the correct format.  This was already the case for
at jobs, but not for crontabs.  Todd's latest commit message in:


"In cron(8), require that crontab and at files in the spool be owned
by group crontab.  The at(1) command now creates files owned by
group crontab, the crontab(1) command already does this.

Files in the crontab spool with parse errors are now ignored;
crontab(1) will not install a crontab file with parse errors.
The system crontab file (/etc/crontab) is not affected by this.

The required permissions on crontab files have been tightened.
Files in the cron spool must be mode 0600 (as created by crontab(1)).
The system crontab file may be readable/writable by the owner,
readable by group and readable by other.  The system crontab must
be readable by the owner."

That's it for OpenBSD.

For Debian and Ubuntu, I suggested:

| One way to fix the cron issue is to restrict hard links in the kernel.
| This is now the "fs.protected_hardlinks = 1" sysctl supported upstream.
| So maybe you just make this the default for your distro, if not already,
| and say that it's also the fix for the cron issue.  However, you will
| still need to fix the postinst script separately - or maybe remove that
| script since it's probably only still needed for upgrades from ancient
| versions of Debian, for which you probably no longer support direct
| upgrades for other reasons anyway.

Salvatore replied:

| Fortunately the first one is the default already in Debian since quite
| a long time:
|, so since 3.2.9-1 (then the default was
| reverted, but Debian added on top the patch to enable it by default in
| We will need to think about what to do with the postinst script yet.

and Moritz Muehlenhoff confirmed:

| > This is now the "fs.protected_hardlinks = 1" sysctl supported upstream.
| > So maybe you just make this the default for your distro, if not already,
| That is in fact the default since Debian 7/wheezy (and it's explicitly
| documented that that if someone builds a custom kernel instead of the
| kernel packages shipped in Debian, they need to enable it in a custom
| build as well).
| > However, you will
| > still need to fix the postinst script separately - or maybe remove that
| > script since it's probably only still needed for upgrades from ancient
| > versions of Debian, for which you probably no longer support direct
| > upgrades for other reasons anyway.
| Ack, but we need to loop in the Debian cron maintainers for further
| discussion, so I'm all for making this public.

For Owl, we had refocused from -ow patches (hardening the kernel) to
providing a safer userland and OpenVZ integration years ago, and
currently Owl is a legacy system that's barely maintained.  We might or
might not resume active development.  However, for now I backported
upstream's revision of the hardlink restrictions to the RHEL5/OpenVZ
kernels included in Owl-current.  I intend to also get this into
3.1-stable soon (almost the same kernels).  This is sufficient to deal
with the Cron issue, but we might also merge Todd's changes from OpenBSD

I guess ALT Linux will go either route, or both, too.

In Owl, we have our patches relative to OpenBSD's revision of 4.1 from
2006 in:

and the corresponding tarball as well as two older ones in e.g.:

(as well as on other mirrors).  These tarballs do not contain our
patches yet - they're extracts from OpenBSD, and our patches from the
CVS tree above are applied on top of them at package build time.
This is very old code, but aside from this recently (re)discovered issue
it just worked for us so far.

ALT Linux has these same patches too, plus some of their own (such as
adding SELinux support), using the same base version from OpenBSD (we
shared some development effort on this):

Gentoo has a wiki page describing their Vixie Cron here:

Per their description, it's based on 4.1 and has some Linux specifics
added (SELinux, PAM, etc.)  I don't know whether they use group crontab.

Ideally, we need a new upstream maintainer for ISC Cron, so that other
distros could easily introduce this privsep layer as well and readily
have the known issues fixed by simply using upstream code.  It'd be
great if Todd volunteered for this role.

I did not look at the Red Hat forks mentioned in the Wikipedia article,
though.  I doubt they're suitable to replace upstream, but maybe?
Someone should probably take a look before becoming the new upstream.

Sorry for the lengthy message.


Powered by blists - more mailing lists

Your e-mail address:

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Powered by Openwall GNU/*/Linux - Powered by OpenVZ