[Python] 設定 setuid 讓 python 程式可以用 root 權限執行

[Python] 設定 setuid 讓 python 程式可以用 root 權限執行

最近需要讓一個 python 程式可以在 Mac/Linux 上被普通 (非 root) 的 user 呼叫,

可是它本身又需要 root 的權限來操作一些檔案,

考慮了一下,似乎只能用 setuid 來解決~

 

setuid 是一個檔案權限的設定,設定了之後,

被執行起來的程式,會以檔案擁有者 (file owner) 的權限執行,

而不是執行程式的用戶權限~

因此以這個例子來說,我們只要讓那個檔案 owner 是 root,

然後授與它 setuid,它就能被一般用戶執行,且能操作 root 才能碰到的東西了~

不過在實作時,接連遇到了幾個問題~

 

第一個問題是 setuid 設定在 python script 上無效~

雖然可以 chmod 4755 xxx.py,可是在執行 script 時會用 python 來執行,

而 python 並沒有 setuid,因此還是用戶權限執行起來的~

我們也不可能將 setuid 設定在 python 上面,

因為這樣大家都可以藉由 python 來提昇為 root 權限,非常危險~

 

為了解決這個問題,網路上一般的解法是寫一個小的 C/C++ 程式,

這個 C/C++ 程式設定了 setuid,讓它再去執行 python script~

下面是一個小程式 testsuid.cpp,它先試著自己寫 /etc/xxx,

接著再呼叫 python 去執行 testsuid.py:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
FILE* pf = fopen("/etc/xxx", "w");
if (pf != NULL)
{
printf("Can write file /etc/xxx\n");
fclose(pf);
}
else
{
printf("Cannot write file /etc/xxx\n");
}
system("python testsuid.py");
return 0;
}

 

而 testsuid.py 的內容也很簡單,就只是試著去寫 /etc/yyy 這個檔案:

open("/etc/yyy", "w")

 

先不設定 setuid,編譯後執行看看:

testuser@localhost ~ $ ./testsuid
Cannot write file /etc/xxx
Traceback (most recent call last):
File "testsuid.py", line 1, in <module>
open("/etc/yyy", "w")
IOError: [Errno 13] Permission denied: '/etc/yyy'

 

不意外的,用 fopen() 是沒有權限建立 /etc/xxx,而 python 程式也沒辦法建立 /etc/yyy。

接著把 testsuid 程式設定成 setduid 再執行看看:

testuser@localhost ~ $ sudo chmod 4755 testsuid
testuser@localhost ~ $ sudo chown root testsuid
testuser@localhost ~ $ ./testsuid
Can write file /etc/xxx
Traceback (most recent call last):
File "testsuid.py", line 1, in <module>
open("/etc/yyy", "w")
IOError: [Errno 13] Permission denied: '/etc/yyy'

 

奇怪的事發生了,setuid 看來是有效果,因此 fopen() 現在有辦法寫出 /etc/xxx 了,

可是 python 程式卻還是沒有權限去寫,是怎麼回事呢?

 

經由同事的幫忙,這才知道有 uid 和 euid 這兩個東西~

uid 是登入的用戶 id,euid (effective uid) 則是執行中的 process 的 uid,

當我們設定 setuid bit 時,

process 本身的 euid 會被設定成 file owner 的 uid (本例中是 root),

但 uid 沒有改變,因此在接下來的 system() 呼叫時,

會以 uid 的值再產生下一個 process,導致 setuid 的效果沒有繼承至下一個 process~

 

要解決這個問題,就是在 setuid 的程式裡,多呼叫一個 setuid(0),

把自己的 uid 設定成 root,這樣子 uid 和 euid 都是 0,

下一個被呼叫的子 process 就也會是 root 權限了:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
setuid(0);
FILE* pf = fopen("/etc/xxx", "w");
if (pf != NULL)
{
printf("Can write file /etc/xxx\n");
fclose(pf);
}
else
{
printf("Cannot write file /etc/xxx\n");
}
system("python testsuid.py");
return 0;
}

 

執行後的結果如下,C++ 和 python 程式都沒有遇到錯誤,

可以成功的建立 /etc/xxx 和 /etc/yyy 了:

testuser@localhost ~ $ ./testsuid
Can write file /etc/xxx

 

 

 

 

(本頁面已被瀏覽過 1,719 次)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料