ptrace介面和execve同步導致的一個核心漏洞

來源:互聯網
上載者:User

linux中是不可以依附別的進程的,更不可能建立遠程線程,然而一種不太正規的方式卻可以做到這一點,這就是ptrace介面,ptrace可以依附任何使用者進程,用特殊的參數甚至可以更改任何進程的寄存器和記憶體映射,這個功力和建立遠程線程不相上下,甚至比其更加靈活,如果理解了elf映像在記憶體的布局便可以通過ptrace修改被調試進程的任意記憶體。然而有一個限制就是ptrace介面只能調試屬於自己使用者的進程,也就是說它不能調試別的使用者的進程,一種顯而易見的限制就是普通使用者的ptrace不能調試root進程,但是2.6.29核心有一個明顯的漏洞使得普通使用者可以提升本地許可權。

簡單來講就是在調用exec的時候需要經曆一系列的uid,euid的計算,特別是exec具有suid的程式的時候,這種計算將變得更加繁複,在ptrace的時候需要進行一系列uid,euid的判斷,那麼如果處理不好這二者之間的同步,必然會導致ptrace的誤判,就是說ptrace調試了一個根本不屬於自己的進程,甚至可能是root進程,如果ptrace的調用進程更改了被調用的具有suid的進程的記憶體,使之exec了一個shell,那麼該shell將會是屬於root的,因為exec的調用進程也就是suid進程的euid是0.

早期的核心心漏洞更加明顯,看看2.4.9的核心中的ptrace_attach

int ptrace_attach(struct task_struct *task)

{

task_lock(task);

...

if(((current->uid != task->euid) ||

(current->uid != task->suid) ||

(current->uid != task->uid) ||

(current->gid != task->egid) ||

(current->gid != task->sgid) ||

(!cap_issubset(task->cap_permitted, current->cap_permitted)) ||

(current->gid != task->gid)) && !capable(CAP_SYS_PTRACE))

goto bad;

...

task->ptrace |= PT_PTRACED;

task_unlock(task);

write_lock_irq(&tasklist_lock);

if (task->p_pptr != current) {

REMOVE_LINKS(task);

task->p_pptr = current;

SET_LINKS(task);

}

write_unlock_irq(&tasklist_lock);

send_sig(SIGSTOP, task, 1);

return 0;

bad:

task_unlock(task);

return -EPERM;

}

這個早期的核心顯得很整潔,因為都到了很少量的鎖,鎖的粒度自然很粗,不過還好,看看第一行就鎖住了這個進程,然後進行一系列的判斷,鎖住進程的目的就是阻止別的執行緒在判斷期間更改進程的屬性,這個想法很好,但是再看一個函數:

void compute_creds(struct linux_binprm *bprm)

{

kernel_cap_t new_permitted, working;

int do_unlock = 0;

new_permitted = cap_intersect(bprm->cap_permitted, cap_bset);

working = cap_intersect(bprm->cap_inheritable,

current->cap_inheritable);

new_permitted = cap_combine(new_permitted, working);

if (bprm->e_uid != current->uid || bprm->e_gid != current->gid ||

!cap_issubset(new_permitted, current->cap_permitted)) {

current->mm->dumpable = 0;

lock_kernel(); //僅僅鎖住了kernel而沒有鎖住task,只要沒有執行緒和該執行緒競爭kernel鎖,那麼誰也不會等待

if (must_not_trace_exec(current) //注意這個函數,見下面。該位置設為A

|| atomic_read(&current->fs->count) > 1

|| atomic_read(&current->files->count) > 1

|| atomic_read(&current->sig->count) > 1) {

if(!capable(CAP_SETUID)) { //千萬別通過這個if語句,否則弊大於利,攻擊成功的可能性就小了

bprm->e_uid = current->uid;

bprm->e_gid = current->gid;

}

...

}

do_unlock = 1;

}

...//注意,以下就應該設定新進程的各種ID了。下面的位置設定為B。

current->suid = current->euid = current->fsuid = bprm->e_uid;

current->sgid = current->egid = current->fsgid = bprm->e_gid;

if(do_unlock)

unlock_kernel();

current->keep_capabilities = 0;

}

static inline int must_not_trace_exec(struct task_struct * p)

{

return (p->ptrace & PT_PTRACED) && !cap_raised(p->p_pptr->cap_effective, CAP_SYS_PTRACE);

}

注意,在A和B之間並沒有進行task本身的保護,最起碼沒有和ptrace互斥,ptrace為了安全起見被設計為不能跟蹤自己使用者id之外的進程,比如一個普通使用者進程不能跟蹤suid進程,但是看看2.4.9的代碼,如果在A和B之間,ptrace闖了進來要跟蹤正在exec的進程,此時被跟蹤的正在 exec的進程尚未設定好新的euid和egid之類的id,那麼ptrace進程很容易就得逞了,通過了ptrace_attach中的那麼長的判斷,從而成功實現一個普通使用者進程進程跟蹤一個suid進程,以後可以通過ptrace介面修改被跟蹤的suid進程的記憶體實現注入,如果注入一個root的 shell,那麼一切就成功了!在後來的核心中,在compute_creds也用了task_lock(task)將進程鎖住,那麼exec和 ptrace就不能同時進入到競爭態了,如果exec先進去,那麼ptrace_attach的一系列判斷會將有企圖的進程趕出去,如果ptrace先進去,那麼must_not_trace_exec會將exec新進程的euid設定成current的uid從而降低了它的許可權,仔細看 must_not_trace_exec發現它也有漏洞,如果suid程式本身有漏洞,那麼!cap_raised(p->p_pptr->cap_effective, CAP_SYS_PTRACE)這個條件就有可能被利用,從而繞過bprm->e_uid = current->uid,因此後來的版本將該函數的後一個條件去掉了,只要一個進程被跟蹤了,那麼就儘可能不讓它獲得特權,除非它的調用者本身就是特權程式,皇帝自殺誰也擋不住啊。

雖然後來的版本在兩個地方加上了鎖,但是一下子就鎖一個進程未免粒度過大,於是2.6的後期核心將鎖的粒度減小,引入了ptrace和exec進行互斥的專用鎖,這樣核心的效率會更高,和這二者無關的操作不會因為鎖而被阻攔。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.