目前有個項目要求 Apache以other的身份模拟root 使用者權限來執行!
目前想到的就是 fakeroot 以及S位,在此bing轉載了一篇setuid相關部落格,
Linux下對于一個檔案我們熟悉的權限有rwx,表示讀寫和執行。
今天介紹的是S位,稱為setuid位。
setuid
從一個例子開始
ping指令常常用來測試網絡是否通暢,我們看看ping的權限是啥:
| |
rws!第三個s是啥東西?将其删掉:
| |
發現就無法ping了,因為沒有打開socket的權限。而當我們又将s位給它的時候,又可以ping了。

Setuid位的作用
Setuid 是讓程式在執行過程中擁有檔案擁有者的權限。
用passwd指令舉例來說,passwd指令也一樣設定了setuid位。
為啥這個需要呢?我們知道linux中賬号與密碼分别存放在 /etc/passwd與 /etc/shadow,而後者的權限如下:
| |
當我們使用passwd指令修改自己的密碼(非root)的時候,顯然是不行的,因為無法對/etc/shadow進行修改。而setuid位此時就可以幫到忙啦!
此時程式暫時得到了檔案擁有者root的權限,于是就可以修改shadow檔案了。
Linux中設定Setuid的程式
要找出所有設定setuid的程式,隻需要使用下面任意一條指令
| |
結果如下:
/bin/ping /bin/mount /bin/su /bin/fusermount /bin/ntfs-3g /bin/umount /usr/sbin/pppd /usr/lib/snapd/snap-confine /usr/lib/i386-linux-gnu/ubuntu-app-launch/oom-adjust-setuid-helper /usr/lib/i386-linux-gnu/oxide-qt/chrome-sandbox /usr/lib/openssh/ssh-keysign /usr/lib/eject/dmcrypt-get-device /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/policykit-1/polkit-agent-helper-1 /usr/bin/chsh /usr/bin/newgrp /usr/bin/arping /usr/bin/traceroute6.iputils /usr/bin/gpasswd /usr/bin/chfn /usr/bin/passwd /usr/bin/pkexec /usr/bin/sudo
權能
在早先的Linux系統中,執行特權操作的時候,核心對于有效的user ID為0 (超級使用者root)的時候不進行檢查,而對user ID非0的情況進行許可檢查。主要是effective UID、effective GID。
Linux 2.2起,Linux有了權能的概念。當執行特權操作的時候,可以檢視是否有相應的權能。
權能可以讓普通使用者完成超級使用者可以做的事情。
繼續上一個例子
在上一個例子中,我們把setuid位取消了,然後發現ping指令不行了,現在我們用setcap賦予cap_new_raw的權能:
發現可以Ping!并且沒有setuid位!
權能的作用
權能相當于規範了你這個程式所擁有的權限。相當于特權的需求的細化。
比如ping指令需要有socket的連接配接,需要CAP_NET_RAW的權能,其說明如下:
CAP_NET_RAW
- Use RAW and PACKET sockets;
- bind to any address for transparent proxying.
現在linux的權能有好多,可以見man手冊
上面列印出了那麼多的setuid位的程式,比如ping 隻需要CAP_NET_RAW權能即可
其它部分需要的權能如下:
程式 | 需要的權能 |
/bin/ping | CAP_NET_RAW |
/bin/mount | CAP_SYS_ADMIN |
/bin/su | CAP_DAC_OVERRIDE,CAP_SETGID,CAP_SETUID |
/bin/fusermount | CAP_SYS_ADMIN |
/bin/umount | CAP_SYS_ADMIN |
/usr/bin/passwd | CAP_CHOWN ,CAP_DAC_OVERRIDE ,CAP_FOWNER |
權能三項與權能變換
在linux中,權能有如下三種:
- effective:目前有效的權能,執行某特權操作時,作業系統檢查cap_effective的對應位是否有效,而不再是檢查程序的有效UID是否為0。
- permitted:目前程序所有能使用的能力,effective包含于permitted
- inheritable:可以被繼承的能力
上面我們設定ping的權能就是設定了effective和permitted。
linux程式可以調用子程式,其權能變換如下:
During an execve, the kernel calculates the new capabilities of the process using the following algorithm:
1
2
3
P'(permitted) = (P(inheritable) & F(inheritable)) (F(permitted) & cap_bset)
P'(effective) = F(effective) ? P'(permitted) : 0
P'(inheritable) = P(inheritable)
- P:denotes the value of a thread capability set before the execve(2)
- P':denotes the value of a capability set after the execve(2)
- F:denotes a file capability set
- cap_bset:is the value of the capability bounding set (described below).
權能變換小程式
實作一個程式其滿足以下的功能:
- 能夠永久的删除其子程序的某個權能。
- 能暫時性的删除其子程序的某個權能。
- 能讓上面被暫時性删除的權能重新獲得
以ping為例,我們知道ping需要的權能為cap_net_raw,為了能在execve後執行ping,即execve中的ping有cap_net_raw權能,根據上面的變換規則:
- 設定/bin/ping權能為cap_net_raw+ei,使得可以繼承
- sudo setcap cap_net_raw+ei /bin/ping
- 設定目前的程序的權能有cap_net_raw+i權能
此外,我們的程式先進行fork(),在子程序中調用execve執行ping,因為不用fork執行ping後會退出程式。
fork()建立子程序,那麼子程序繼承父程序的所有能力,是以沒必要重新設定fork後的權能。
為了讓有更改權能的能力,首先以sudo運作程式,賦予 CAP_SETUID, CAP_SETGID, CAP_SETPCAP權能,然後setuid和gid為1000。
- sudo apt-get install libcap-dev
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#undef _POSIX_SOURCE
extern int errno;
void whoami() {
printf("uid=%i euid=%i gid=%i\n", getuid(), geteuid(), getgid());
}
void listCaps(){
cap_t caps = cap_get_proc();
ssize_t y = 0;
printf("The Process %d was give capabilities %s\n",(int) getpid(), cap_to_text(caps, &y));
0);
cap_free(caps);
}
void do_ping(){
if(!fork()){
whoami();
listCaps();
"ping","ping","-c 1","baidu.com",NULL);
}
}
void init(){
if(getuid() != 0){
puts("請使用sudo執行本程式");
exit(1);
}
cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID, CAP_SETPCAP ,CAP_NET_RAW};
cap_t caps = cap_init();
4, cap_values, CAP_SET);
4, cap_values, CAP_SET);
cap_set_proc(caps);
1, 0, 0, 0);
cap_free(caps);
1000);
1000);
caps = cap_get_proc();
4, cap_values, CAP_SET);
4, cap_values, CAP_SET);
cap_set_proc(caps);
cap_free(caps);
whoami();
listCaps();
}
void printInfo(){
puts("\n------權能測試小程式------\n\
請輸入如下字元:\n\
1 : 永久的删除其子程序的某個權能\n\
2 : 暫時性的删除其子程序的某個權能\n\
3 : 暫時性删除的權能重新獲得\n\
4 : ping -c 1 baidu.com\n\
5 : 檢視目前權能\n\
q : 退出\n\
---------------------------\n");
}
int main(int argc, char **argv)
{
init();
char line[128];
printInfo();
while(fgets(line, sizeof(line), stdin)){
char cmd = line[0];
if(cmd == '1' || cmd == '2' || cmd == '3'){
printf("輸入要操作的權能名,如cap_net_raw\n");
sizeof(line), stdin);
strlen(line) - 1] = '\0';
cap_value_t temp;
if(cap_from_name(line, &temp) < 0){
printf("Error capability name\n");
}
else{
cap_t caps = cap_get_proc();
if(cmd == '3'){
1, &temp, CAP_SET);
1, &temp, CAP_SET);
}
else{
1, &temp, CAP_CLEAR);
1, &temp, CAP_CLEAR);
if(cmd == '1')
1, &temp, CAP_CLEAR);
}
if(cap_set_proc(caps))
"cap_set_proc() ERROR: ");
else