PRIMARY CATEGORY → LINUX PRIVESC

Theory

systemd is normally executed as PID 1 and manages system services through unit files.

System services run as ROOT by default unless another account is explicitly defined through the User= directive.

Therefore, write permissions over a privileged unit file may allow us to modify the commands executed by the service.

The modified configuration must subsequently be loaded and the unit started through one of the following actions:

  • Restarting the service
  • Reloading the systemd configuration and starting the service
  • Rebooting the system when the unit is enabled or started by another unit

Service Units

Requirements
  • Write permissions over an existing service unit file

e.g. /etc/systemd/system/my-custom-app.service

Or:

  • Write permissions over a systemd unit directory to create a new unit

  • The unit must run as ROOT or another privileged user

  • The controlled user must be able to start/restart the service or reboot the system

  • The unit must be enabled or pulled in by another unit during boot

WantedBy=multi-user.target does not enable the unit by itself. systemctl enable creates the corresponding symbolic link inside multi-user.target.wants/.

Enumeration
Writable Unit Files
find /etc/systemd/system /run/systemd/system /usr/lib/systemd/system /lib/systemd/system -type f -writable -ls 2> /dev/null
Writable Unit Directories
find /etc/systemd/system /run/systemd/system /usr/lib/systemd/system /lib/systemd/system -type d -writable -ls 2> /dev/null
Unit Configuration
systemctl cat <SERVICE>
systemctl show <SERVICE> --property=FragmentPath,User,Group,UnitFileState
systemctl is-enabled <SERVICE>

An empty User= value in a system service means that the default execution user is ROOT.

File Permissions
namei -l /etc/systemd/system/<SERVICE>
Service/System Restart
sudo -l

Look for privileges that allow:

  • Running the systemctl command
  • Restarting a particular service
  • Executing reboot, halt or poweroff
Abuse
Listing Sudo Privileges

From the target 🎯

Let’s suppose that we compromise a web application, achieve RCE and establish a reverse shell on the target.

First, we list the current user’s sudo privileges:

sudo -l

The user cannot execute arbitrary commands through sudo, but can reboot the system as ROOT.

A reboot causes the new systemd process to read the unit files from disk and start the units enabled for the boot target.

Checking Write Permissions

From the target 🎯

Check whether the systemd unit directory is writable:

namei -l /etc/systemd/system/

The directory is not writable, so we search for writable unit files:

find /etc/systemd/system -type f -writable -ls 2> /dev/null
Inspecting the Service Unit
systemctl cat pythonapp.service
systemctl is-enabled pythonapp.service

The service runs as john because of the User=john directive.

Replacing ExecStart= would prevent the original Flask application from starting. Instead, we can preserve the original command and change only its execution context.

Running the Original Service as ROOT

Modify the execution user while preserving the original ExecStart=:

After the reboot, Flask will listen again on port 50000, but the process will run as ROOT.

Exploiting the original web vulnerability again will therefore provide command execution as ROOT without requiring an additional payload.

Privileged ExecStartPre

Alternatively, we can preserve User=john and execute only one command with full privileges:

The + prefix makes that specific command ignore the restrictions configured through User=.

The execution flow will be:

  1. ExecStartPre= sets the SUID permission on /bin/bash as ROOT.
  2. The command finishes successfully.
  3. ExecStart= starts the original Flask application as john.
  4. The web application becomes available again on port 50000.

Comments must be placed on separate lines. systemd does not support inline comments after directive values.

Validating the Unit

Before rebooting, check the unit syntax:

systemd-analyze verify /etc/systemd/system/pythonapp.service

Confirm that the existing unit is enabled:

systemctl is-enabled pythonapp.service

If a new unit was created, it must also be enabled. This normally requires systemctl enable or write permissions over the corresponding .wants/ directory.

Rebooting the System
sudo --user root -- /sbin/reboot

A complete reboot reloads the modified unit automatically, so systemctl daemon-reload is not required.

halt and poweroff stop the system, but the payload will not execute until the machine is started again.

Validation

After reconnecting, verify the service:

systemctl status pythonapp.service --no-pager -l
journalctl -b -u pythonapp.service --no-pager
ss -lnt | grep -- ':50000'

When using the SUID method, verify the permissions:

ls -l /bin/bash

Expected Output

-rwsr-xr-x 1 root root <SIZE> <DATE> /bin/bash

The privileged shell must preserve its effective user ID:

/bin/bash -p
id

Expected Output

uid=<USER_ID>(john) gid=<GROUP_ID>(john) euid=0(root)