[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

**security warning** cp -au backups are insecure with GNU cp



 I'm using GNU cp, from version 4.0.43 of fileutils, on Debian woody/testing.

 I've found a potential security hole in /bin/cp.  If the destination file
is a symlink, and the source file isn't, it still opens the destination with
O_TRUNC.  This is bad if root is running cp -au on a user-controlled
directory.  I'm talking about replace-/etc/shadow bad!!!!  There is a
workaround, which reduces the attack to a race condition that requires
microsecond timing, instead of a whole day or so;  See below.

 I use cp for backups from one hard drive to another on my machine, with the
following command line:
/bin/cp --parents -au /root /home /etc /var/lib /var/spool/mail    \
      /var/spool/cron /boot/backup

 (don't ask why my backups are on /boot.  I'm going to repartion as soon as
I get my hands on another big drive so I can run RAID. :) BTW, using /backup 
(a symlink to /boot/backup) for the destination causes cp --parents -au to
do nothing.)

 I discovered this problem when investigating an error message from cp -au:
/bin/cp: cannot create regular file /boot/backup/etc/ntp.conf': No such 
file or directory

 /etc/ntp.conf is a regular file, but it used to be a symlink.
/backup/etc/ntp.conf is a symlink to /etc/dhcpc/ntp.conf.  (and has been for
a long time.  I hadn't bothered tracking down this error message. :)  I used
strace to debug this:

lstat64(0x810fe08, 0xbffff54c)          = 0
stat64(0x810fe08, 0xbffff3ac)           = -1 ENOENT (No such file or directory)
open("/etc/ntp.conf", O_RDONLY|0x8000)  = 4
open("/boot/backup/etc/ntp.conf", O_WRONLY|O_TRUNC|0x8000) = -1 ENOENT (No such file or directory)
write(2, "/bin/cp: ", 9)                = 9
write(2, "cannot create regular file /boo"..., 54) = 54

 (strace doesn't seem to handle stat64 or lstat64 on my 2.4.3 kernel :(, but it
is pretty obvious that the lstat and stat calls were on /backup/etc/ntp.conf)

 ****** back on topic to the security report ********

 I decided to see if this was exploitable.  It turns out that is most
certainly _is_.  I created a symlink to /etc/hackfile in my home directory.
I ran cp -au ...  to update my backup directory. /backup/home/peter/hackfile
was then a symlink to /etc/hackfile (note: not /backup/etc/hackfile.) I
removed hackfile from my home directory, and replaced it with a regular
file.  /etc/hackfile did _not_ get created, because /backup/etc/hackfile was
open()ed without O_CREAT.  As root, I touch(1)ed /etc/hackfile, set its
permissions to 664, and reran cp. /etc/hackfile now contained the same
thing as the file in my home directory, owned by root, and with permissions
664.  (The permissions are copied from hackfile in my home directory, but
the file retains root ownership.  Even setting the permissions on
/etc/hackfile to 600 does not prevent overwriting, or having its permissions
set to 664.  Root can still write the file.)

 Users won't be able to read files with this, only replace them with their
own versions.  A user with a home directory that's automatically backed up
with cp -au can replace /etc/shadow, but the change will certainly be
noticed, because they won't be able to get the old state. A more viable
target would be files in /etc/init.d, or /etc/crontab, or even /dev/kmem
(yikes!).  /etc/crontab can used to trivially get root access within minutes
of cp -au being run, and if /etc/crontab is world readable, they can
preserve the rest of it and even restore it after they're done, so the
attack will probably go unnoticed (unless you scan for setuid programs).

 If the user can write to the backup directory (which will be the case, if
using cp -au in the normal way), the don't have to wait a day for cp to
create the symlink.  They can ln -s /etc/crontab /backup/home/peter/hackfile
right now, so the attack will happen at the first backup run, instead of the
second.

 **************** workaround ****************

 use cp --remove-destination --parents -au.  --remove-destination leads to
this behaviour:

lstat64(0x80f5800, 0xbffff2bc)          = 0
unlink("/boot/backup/home/peter/hackfile") = 0
open("/home/peter/hackfile", O_RDONLY|0x8000) = 3
open("/boot/backup/home/peter/hackfile", O_WRONLY|O_CREAT|0x8000, 0100664) = 4
fstat64(0x4, 0xbffff11c)                = 0
fstat64(0x3, 0xbffff11c)                = 0
read(3, "file contents\n", 4096)        = 14
write(4, "file contents\n", 14)         = 14
read(3, "", 4096)                       = 0
close(4)                                = 0
close(3)                                = 0
utime("/boot/backup/home/peter/hackfile", [2001/04/27-03:27:30, 2001/04/27-03:28:12]) = 0
msgrcv(135223296, {134526718, 0x4}, 1000, 0, 0x3e8) = 0
chmod("/boot/backup/home/peter/hackfile", 0100664) = 0

 /backup/home/peter/hackfile is safely unlinked before it is opened for
writing.  However, if the user has write permission in the destination
directory, there is still a race condition.  If they recreate the symlink
before the open() call, you still lose.

-- 
#define X(x,y) x##y
Peter Cordes ;  e-mail: X(peter@llama.nslug. , ns.ca)

"The gods confound the man who first found out how to distinguish the hours!
 Confound him, too, who in this place set up a sundial, to cut and hack
 my day so wretchedly into small pieces!" -- Plautus, 200 BCE



Reply to: