learn/phase-3/p3-w12/lesson 07
Milestone 12 · lesson 7

Writable Files & PATH Hijacking

Hands-on. When root reads or runs something you can write, or trusts a $PATH you control, you control root.
Lab: Own the Service

Find a root-run systemd service file you can write to, the privesc vector.

What you'll learn

  • Find every file and directory your user can write to, fast.
  • Turn a writable file that a root process trusts into code execution as root.
  • Exploit PATH hijacking: a root program calling a command by name in a writable directory.
  • Recognise writable systemd units and service configs as root-run targets.

Writable Files & PATH Hijacking

A recurring privesc theme: root trusts something you can change. Sometimes it's a file root reads or runs; sometimes it's the name of a command root looks up through $PATH. Either way, if you control what root ends up executing, you control root.

Step 1: find what you can write

The single most productive enumeration command after SUID:

find / -writable -type f 2>/dev/null | grep -vE "/proc|/sys"
find / -writable -type d 2>/dev/null            # writable directories too

Now ask of each hit: does anything running as root read or execute this? A writable file nobody privileged touches is harmless. A writable file in a root cron job, a systemd unit, a startup script, or a config a root service parses, that's your way up.

Writable files root trusts

Common high-value writable targets:

  • A script run by root cron (you saw this in cron privesc).
  • A systemd .service file or its ExecStart target. If /etc/systemd/system/app.service is writable, point ExecStart= at your payload:
    [Service]
    ExecStart=/bin/bash -c 'cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash'
    
    then it runs as root on the next restart or boot.
  • A config file a root daemon reads (some let you set a command, a plugin path, an include).
  • /etc/passwd or /etc/shadow if permissions are wrong (see weak permissions).

PATH hijacking

This one's subtle and very common. When a program runs a command by name (backup) instead of full path (/usr/bin/backup), the shell searches the directories in $PATH, in order, and runs the first match. Two things make it exploitable:

  1. A root process (a cron job, a SUID wrapper, a service) calls a command by bare name.
  2. $PATH contains a directory you can write to, listed before the real one.
$ echo $PATH
/home/analyst/binwritable!:/usr/bin:/bin
root's script runs backup (no full path)
→ shell searches PATH left to right, runs the first match
→ your /home/analyst/bin/backup is found first → your code runs as root

Put a writable directory in front of PATH and you decide what "backup" means.

$ echo $PATH
/home/analyst/bin:/usr/bin:/bin     # your dir is FIRST

# the root script runs 'backup' by name, so drop your own 'backup' first in PATH:
$ printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash\n' > /home/analyst/bin/backup
$ chmod +x /home/analyst/bin/backup
# ...root runs YOUR backup -> SUID root bash -> /tmp/rootbash -p

Even when PATH looks safe, a SUID binary that calls system("service …") or popen("id") without a full path is hijackable: you set PATH=/tmp:$PATH, drop a malicious service/id in /tmp, and run the SUID binary.

The fix (what you report)

Root-run scripts should be root-owned and non-writable by others, call every binary by absolute path, and set a clean, fixed PATH at the top. Never leave a world-writable directory early in root's PATH. Report the writable file (with its permissions) or the bare-name command call.

Common questions

"find / -writable printed hundreds of files. Which ones matter?" Almost none of them on their own. A writable file is only a privesc if something running as root reads or runs it. Cross-reference your writable hits against root cron jobs, systemd units, and startup scripts, ignore the writable files in your own home directory and /tmp.

"How do I know if PATH hijacking will even work here?" Two checks. Run echo $PATH, is a directory you can write to listed before /usr/bin? And does a root process call a command by bare name (backup) rather than full path (/usr/bin/backup)? You need both. If PATH is clean and root uses absolute paths, this vector is closed.

"My PATH looks safe (system dirs only). Is PATH hijacking off the table?" Not always. A SUID binary that internally calls system("id") with a bare name is still hijackable, you prepend your own directory (PATH=/tmp:$PATH), drop a fake id there, and run the SUID program. The binary trusts PATH even if your login shell's PATH looked fine.

"Why edit ExecStart in a .service file instead of just running the service?" Because ExecStart is the command systemd runs as root when the unit starts. If the unit file is writable, you point that line at your payload, then trigger a restart (or wait for boot) and root runs it for you. Same "root trusts something you can change" idea as cron.

Why this matters

These bugs need no exploit code, just find, echo, and an understanding of who runs what. A single writable service file or a sloppy $PATH is one of the most common real-world privescs, and one of the easiest to miss if you don't go looking.

Check your understanding

4 questions

Type an answer and press Check. Grading is keyword-based and forgiving, so short answers are fine.

  1. 1

    Write the find command that lists every file the current user can write to, hiding errors.

  2. 2

    Write the command that prints your current PATH so you can spot a writable directory in it.

  3. 3

    A root script runs backup (no absolute path) and your PATH starts with /home/you, which you can write to. In one line, how do you exploit it?

  4. 4

    You find a writable .service file that runs as root. Write the systemctl command to make your change take effect.