一)概述:
1)從2.1版開始,Linux內核有了能力(capability)的概念,即它打破了UNIX/LINUX操作系統中超級用戶/普通用戶的概念,由普通用戶也可以做只有超級用戶可以完成的工作.
2)capability可以作用在進程上(受限),也可以作用在程序文件上,它與sudo不同,sudo只針對用戶/程序/文件的概述,即sudo可以配置某個用戶可以執行某個命令,可以更改某個文件,而capability是讓某個程序擁有某種能力,例如:
capability讓/tmp/testkill程序可以kill掉其它進程,但它不能mount設備節點到目錄,也不能重啓系統,因爲我們只指定了它kill的能力,即使程序有問題也不會超出能力範圍.
3)每個進程有三個和能力有關的位圖:inheritable(I),permitted(P)和effective(E),對應進程描述符task_struct(include/linux/sched.h)裏面的cap_effective, cap_inheritable, cap_permitted,所以我們可以查看/proc/PID/status來查看進程的能力.
4)cap_effective:當一個進程要進行某個特權操作時,操作系統會檢查cap_effective的對應位是否有效,而不再是檢查進程的有效UID是否爲0.
例如,如果一個進程要設置系統的時鐘,Linux的內核就會檢查cap_effective的CAP_SYS_TIME位(第25位)是否有效.
5)cap_permitted:表示進程能夠使用的能力,在cap_permitted中可以包含cap_effective中沒有的能力,這些能力是被進程自己臨時放棄的,也可以說cap_effective是cap_permitted的一個子集.
6)cap_inheritable:表示能夠被當前進程執行的程序繼承的能力.
二)capability的設定與清除
我們在下面的程序中給當前的進程設定能力,最後我們清除掉所設定的能力,源程序如下:
編譯:
gcc capsettest.c -o capsettest -lcap
運行:
./capsettest
uid=0 euid=0 gid=0
The process 2383 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip
dropping caps
The process 2383 was give capabilities =
注:
1)我們對該進程增加了5種能力,隨後又清除了所有能力.
2)首先通過cap_init()初始化存放cap能力值的狀態,隨後通過cap_set_flag函數的調用,將三種位圖的能力設置給了變量caps,再通過cap_set_proc(caps)設定當前進程的能力值,通過cap_get_proc()返回當前進程的能力值,最後通過cap_free(caps)釋放能力值.
3)cap_set_flag函數的原型是:
int cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap,const cap_value_t *caps, cap_flag_value_t value);
我們這裏的調用語句是:cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);
第一個參數cap_p是存放能力值的變量,是被設定值.這裏是caps.
第二個參數flag是是三種能力位圖,這裏是CAP_PERMITTED.
第三個參數ncap是要設定能力的個數,這裏是num_caps,也就是5.
第四個參數*caps是要設定的能力值,這裏是capList數組,也就是CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP.
第五個參數value是決定要設定還是清除,這裏是CAP_SET.
4)cap_set_proc函數的原型是:int cap_set_proc(cap_t cap_p);
cap_set_proc函數通過cap_p中的能力值設定給當前的進程.
5)cap_get_proc函數的原型是:cap_t cap_get_proc(void);
cap_get_proc函數返回當前進程的能力值給cap變量.
6)cap_free函數的原型是:cap_free(caps);
cap_free函數清理/釋放cap變量.
7)如果我們fork()了子進程,那麼子進程繼承父進程的所有能力.
8)不能單獨設定CAP_EFFECTIVE,CAP_INHERITABLE位圖,必須要和CAP_PERMITTED聯用,且CAP_PERMITTED一定要是其它兩個位圖的超集.
9)如果兩次調用cap_set_proc函數,第二次調用的值力值不能少於或多於第一次調用.如第一次我們授權chown,setuid能力,第二次只能是chown,setuid不能是其它的能力值.
10)普通用戶不能給進程設定能力.
三)進程的能力掩碼:
我們可以通過下面的程序獲取當前進程的掩碼,它是通過capget函數來獲取指定進程的能力掩碼,當然我們也可以用capset來設定掩碼,下面獲取掩碼的體現:
gcc capget0.c -o capget0 -lcap
普通用戶:
./capget0
Cap data 0x0, 0x0, 0x0
超級用戶:
/home/test/capget0
Cap data 0xffffffff, 0xffffffff, 0x0
這也說明了默認情況下,root運行的進程是什麼權限都有,而普通用戶則什麼權限都沒有.
我們可以將本程序與上面的程序進行整合,如下:
編譯並執行:
gcc capsettest.c -o capsettest -lcap
./capsettest
uid=0 euid=0 gid=0
The process 3101 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip
Cap data 0x25c0, 0x25c0, 0x25c0
注:0x25c0=10 0101 1100 0000(二進制)
對映的能力如下:
cap_setgid=6(位)
cap_setuid=7(位)
cap_setpcap=8(位)
cap_net_bind_service=10(位)
cap_net_raw=13(位)
在程序sleep的時候,我們查看一下進程的status,如下:
cat /proc/`pgrep capsettest`/status
略
CapInh: 00000000000025c0
CapPrm: 00000000000025c0
CapEff: 00000000000025c0
CapBnd: ffffffffffffffff
略
我們看到進程的status也反映了它的能力狀態.
CapBnd是系統的邊界能力,我們無法改變它.
一)capability的工具介紹
在我們的試驗環境是RHEL6,libcap-2.16軟件包中包含了相關的capability設置及查看工作,如下:
rpm -ql libcap-2.16-5.2.el6.i686
/lib/libcap.so.2
/lib/libcap.so.2.16
/lib/security/pam_cap.so
/usr/sbin/capsh
/usr/sbin/getcap
/usr/sbin/getpcaps
/usr/sbin/setcap
/usr/share/doc/libcap-2.16
/usr/share/doc/libcap-2.16/License
/usr/share/doc/libcap-2.16/capability.notes
/usr/share/man/man8/getcap.8.gz
/usr/share/man/man8/setcap.8.gz
getcap可以獲得程序文件所具有的能力(CAP).
getpcaps可以獲得進程所具有的能力(CAP).
setcap可以設置程序文件的能力(CAP).
我們下面主要用setcap來進行調試.
二)CAP_CHOWN 0(允許改變文件的所有權)
授權普通用戶可以用/bin/chown程序更改任意文件的owner,如下:
setcap cap_chown=eip /bin/chown
查看/bin/chown程序的能力值,如下:
getcap /bin/chown
/bin/chown = cap_chown+eip
切換到test用戶,將/bin/ls程序的owner改爲test,如下:
su - test
chown test.test /bin/ls
ls -l /bin/ls
-rwxr-xr-x. 1 test test 118736 Jun 14 2010 /bin/ls
注:
1)cap_chown=eip是將chown的能力以cap_effective(e),cap_inheritable(i),cap_permitted(p)三種位圖的方式授權給相關的程序文件.
2)如果改變文件名,則能力保留到新文件.
3)用setcap -r /bin/chown可以刪除掉文件的能力.
4)重新用setcap授權將覆蓋之前的能力.
三)CAP_DAC_OVERRIDE 1(忽略對文件的所有DAC訪問限制)
授權普通用戶可以用/usr/bin/vim程序修改所有文件的內容,如下:
setcap cap_dac_override=eip /usr/bin/vim
切換到普通用戶
su - test
修改/etc/shadow文件內容
vim /etc/shadow
root:$6$3hJf.BoIVU/cdLKb$JxLXcQScrLS032aFPAQvVc4RzKYNadcIIzxmzAIw.jejrYOHhqdr0oV7sNBL.IhGBo.mMOYEdevlnCp2OGku8.:15094:0:99999:7:::
bin:*:14790:0:99999:7:::
daemon:*:14790:0:99999:7:::
adm:*:14790:0:99999:7:::
注:
DAC_OVERRIDE能力是DAC_READ_SEARCH能力的超集.
四)CAP_DAC_READ_SEARCH 2(忽略所有對讀、搜索操作的限制)
授權普通用戶可以用/bin/cat程序查看所有文件的內容,如下:
setcap cap_dac_read_search=eip /bin/cat
切換到普通用戶
su - test
查看/etc/shadow,如下:
cat /etc/shadow
root:$6$3hJf.BoIVU/cdLKb$JxLXcQScrLS032aFPAQvVc4RzKYNadcIIzxmzAIw.jejrYOHhqdr0oV7sNBL.IhGBo.mMOYEdevlnCp2OGku8.:15094:0:99999:7:::
bin:*:14790:0:99999:7:::
daemon:*:14790:0:99999:7:::
adm:*:14790:0:99999:7:::
五)CAP_FOWNER 3(以最後操作的UID,覆蓋文件的先前的UID)
cp /etc/passwd /tmp/
ls -l /tmp/passwd
-rw-r--r-- 1 root root 1171 2011-04-29 19:21 /tmp/passwd
授權cap_fowner權限給/usr/bin/vim
setcap cap_fowner=eip /usr/bin/vim
切換到test用戶
su - test
編輯/tmp/passwd文件,存盤退出.
vi /tmp/passwd
修改文件,並保存退出.
查看/tmp/passwd,發現owner已經變成test
-rw-r--r-- 1 test test 1176 2011-04-29 19:21 /tmp/passwd
六)CAP_FSETID 4(確保在文件被修改後不修改setuid/setgid位)
起因是當文件被修改後,會清除掉文件的setuid/setgid位,而設定CAP_FSETID後將保證setuid/setgid位不被清除.但這對chown函數無用.
測試程序如下:
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main()
{
int handle;
char string[40];
int length, res;
if ((handle = open("/tmp/passwd", O_WRONLY | O_CREAT | O_TRUNC,S_IREAD | S_IWRITE)) == -1)
{
printf("Error opening file.\n");
exit(1);
}
strcpy(string, "Hello, world!\n");
length = strlen(string);
if ((res = write(handle, string, length)) != length)
{
printf("Error writing to the file.\n");
exit(1);
}
printf("Wrote %d bytes to the file.\n", res);
close(handle);
}
gcc fsetid.c -o fsetid
先測試沒有設FSETID的情況,如下:
chmod 6777 /tmp/passwd
ls -l /tmp/passwd
-rwsrwsrwx 1 test test 14 2011-04-30 14:22 /tmp/passwd
/tmp/fsetid
Wrote 14 bytes to the file.
ls -l /tmp/passwd
-rwxrwxrwx 1 test test 14 2011-04-30 14:25 /tmp/passwd
我們看到setuid/setgid位被清除了.
下面是設定FSETID,如下:
chmod 6777 /tmp/passwd
ls -l /tmp/passwd
-rwsrwsrwx 1 test test 14 2011-04-30 14:25 /tmp/passwd
切換到root用戶,給/tmp/fsetid程序授權CAP_FSETID能力,如下:
setcap cap_fsetid=eip /tmp/fsetid
切換到普通用戶
/tmp/fsetid
Wrote 14 bytes to the file.
ls -l /tmp/passwd
-rwsrwsrwx 1 test test 14 2011-04-30 14:28 /tmp/passwd
七)CAP_KILL 5 (允許對不屬於自己的進程發送信號)
我們先模擬沒有加CAP_KILL能力的情況,如下:
終端1,用root用戶啓用top程序,如下:
su - root
top
終端2,用test用戶kill之前的top進程,如下:
pgrep top
3114
/bin/kill 3114
kill: Operation not permitted
我們發現無法對不屬於自己的進程發送信號.
下面我們用CAP_KILL能力的程序向不屬於自己的進程發送信號,如下:
設定kill命令的kill位,如下:
setcap cap_kill=eip /bin/kill
殺掉3114進程,沒有問題,如下:
/bin/kill 3114
echo $?
0
注意:
普通用戶要用/bin/kill這種絕對路徑的方式,而不能用kill這種方式.
八)CAP_SETGID 6 (設定程序允許普通用戶使用setgid函數,這與文件的setgid權限位無關)
cp /etc/shadow /tmp/
chown root.root /tmp/shadow
chmod 640 /tmp/shadow
切換到普通用戶test,並編寫setgid測試程序,如下:
su - test
#include <unistd.h>
int
main ()
{
gid_t gid = 0;
setgid(gid);
system("/bin/cat /tmp/shadow");
return 0;
}
gcc setgid.c -o setgid
更改setgid程序爲CAP_SETGID
setcap cap_setgid=eip /tmp/setgid
切換到普通用戶,運行/tmp/setgid程序,如下:
su - test
/tmp/setgid
root:$1$S9AmPHY8$ZIdORp6aLnYleb5EORxw8/:14479:0:99999:7:::
daemon:*:14479:0:99999:7:::
bin:*:14479:0:99999:7:::
sys:*:14479:0:99999:7:::
sync:*:14479:0:99999:7:::
games:*:14479:0:99999:7:::
man:*:14479:0:99999:7:::
lp:*:14479:0:99999:7:::
mail:*:14479:0:99999:7:::
news:*:14479:0:99999:7:::
uucp:*:14479:0:99999:7:::
proxy:*:14479:0:99999:7:::
www-data:*:14479:0:99999:7:::
backup:*:14479:0:99999:7:::
list:*:14479:0:99999:7:::
我們看到普通用戶可以查看/tmp/shadow文件,而取消CAP_SETGID則使程序不能擁有setgid的權限,如下:
setcap -r /tmp/setgid
su - test
/tmp/setgid
/bin/cat: /tmp/shadow: Permission denied
九)CAP_SETUID 7 (設定程序允許普通用戶使用setuid函數,這也文件的setuid權限位無關)
cp /etc/shadow /tmp/
chown root.root /tmp/shadow
chmod 640 /tmp/shadow
切換到普通用戶test,並編寫setuid測試程序,如下:
su - test
cd /tmp/
vi setuid.c
#include <unistd.h>
int
main ()
{
uid_t uid = 0;
setuid(uid);
system("/bin/cat /tmp/shadow");
return 0;
}
gcc setuid.c -o setuid
切換到root用戶,更改setuid程序爲CAP_SETUID
su - root
setcap cap_setuid=eip /tmp/setuid
切換到test用戶,運行/tmp/setuid程序,如下:
su - test
/tmp/setuid
root:$1$S9AmPHY8$ZIdORp6aLnYleb5EORxw8/:14479:0:99999:7:::
daemon:*:14479:0:99999:7:::
bin:*:14479:0:99999:7:::
sys:*:14479:0:99999:7:::
sync:*:14479:0:99999:7:::
games:*:14479:0:99999:7:::
man:*:14479:0:99999:7:::
lp:*:14479:0:99999:7:::
我們看到普通用戶可以查看/tmp/shadow文件,而取消CAP_SETUID則使程序不能擁有setuid的權限,如下:
setcap -r /tmp/setuid
su - test
/tmp/setuid
/bin/cat: /tmp/shadow: Permission denied
十)CAP_SETPCAP 8 (允許向其它進程轉移能力以及刪除其它進程的任意能力)
事實上只有init進程可以設定其它進程的能力,而其它程序無權對進程授權,root用戶也不能對其它進程的能力進行修改,只能對當前進程通過cap_set_proc等函數進行修改,而子進程也會繼承這種能力.
所以即使使用了CAP_SETPCAP能力,也不會起到真正的作用.
十一)CAP_LINUX_IMMUTABLE 9 (允許修改文件的不可修改(IMMUTABLE)和只添加(APPEND-ONLY)屬性)
普通用戶不能通過chattr對文件設置IMMUTABLE(chattr +i)和APPEND-ONLY(chattr +a)權限,而通過CAP_LINUX_IMMUTABLE可以使普通用戶通過自己增減(immutable/append-only)權限.
普通用戶通過chattr給文件增加immutable權限,如下:
touch /tmp/test
chattr +i /tmp/test
chattr: Operation not permitted while setting flags on /tmp/test
我們看到授權失敗了,而如果我們對chattr增加了LINUX_IMMUTABLE權限,則可以成功,如下:
此時切換到root用戶:
su -
setcap cap_linux_immutable=eip /usr/bin/chattr
切換到普通用戶:
su - test
chattr +i /tmp/test
lsattr /tmp/test
----i-------------- /tmp/test
我們看到授權成功了,注意,這裏只能對自己的文件授權(immutable/append-only)權限,對於其它用戶的權限LINUX_IMMUTABLE不起作用(root除外),如下:
su - test
chattr +i /etc/passwd
chattr: Permission denied while setting flags on /etc/passwd
十二)CAP_NET_BIND_SERVICE 10(允許綁定到小於1024的端口)
普通用戶不能通過bind函數綁定小於1024的端口,而root用戶可以做到,CAP_NET_BIND_SERVICE的作用就是讓普通用戶也可以綁端口到1024以下.
普通用戶通過nc綁定端口500,如下:
nc -l -p 500
Can't grab 0.0.0.0:500 with bind : Permission denied
增加CAP_NET_BIND_SERVICE能力到nc程序,如下:
setcap cap_net_bind_service=eip /usr/bin/nc
再切換到普通用戶,通過nc綁定端口500,如下:
nc -l -p 500
查看該端口:
netstat -tulnp|grep nc
tcp 0 0 0.0.0.0:500 0.0.0.0:* LISTEN 2523/nc
十三)CAP_NET_BROADCAST 11(允許網絡廣播和多播訪問)
事實上它並沒有被應用,普通用戶也可以使用ping -b 192.168.0.255也發送廣播包
十四)CAP_NET_ADMIN 12(允許執行網絡管理任務:接口,防火牆和路由等)
普通用戶不能創建新的網絡接口(interface),不能更改ip地址,而CAP_NET_ADMIN可以幫助普通用戶完成這項工作,如下:
用普通用戶創建新的網卡接口eth0:1失敗
/sbin/ifconfig eth0:1 172.16.27.133 netmask 255.255.255.0
SIOCSIFADDR: Permission denied
SIOCSIFFLAGS: Permission denied
SIOCSIFNETMASK: Permission denied
此時我們把CAP_NET_ADMIN能力授權給ifconfig程序,如下:
setcap cap_net_admin=eip /sbin/ifconfig
我們再次用普通用戶即可以新建網絡接口eth0:1,並可以DOWN掉接口,如下:
/sbin/ifconfig eth0:1 172.16.27.133 netmask 255.255.255.0
/sbin/ifconfig eth0:1
eth0:1 Link encap:Ethernet HWaddr 00:0c:29:f9:5e:06
inet addr:172.16.27.133 Bcast:172.16.27.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
Interrupt:18 Base address:0x1080
/sbin/ifconfig eth0:1 down
同樣CAP_NET_ADMIN可以讓普通用戶增加/刪除路由,如下:
/sbin/route add -host 192.168.27.139 gw 192.168.27.2
SIOCADDRT: Operation not permitted
授權NET_ADMIN,如下:
setcap cap_net_admin=eip /sbin/route
再次用普通用戶增加路由,如下:
/sbin/route add -host 192.168.27.139 gw 192.168.27.2
/sbin/route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.27.139 192.168.27.2 255.255.255.255 UGH 0 0 0 eth0
192.168.27.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
0.0.0.0 192.168.27.2 0.0.0.0 UG 0 0 0 eth0
/sbin/route del -host 192.168.27.139 gw 192.168.27.2
我們看到我們除了可以增加路由之外,也可以刪除路由.
最後NET_ADMIN可以幫助我們讓普通用戶來管理防火牆.
普通用戶不能用iptables來管理防火牆,如下:
/sbin/iptables -L -n
iptables v1.4.2: can't initialize iptables table `filter': Permission denied (you must be root)
Perhaps iptables or your kernel needs to be upgraded.
我們將CAP_NET_ADMIN授權給iptables程序,注意我們也要將CAP_NET_RAW授權給iptables,CAP_NET_RAW我們後面再解釋,如下:
setcap cap_net_admin,cap_net_raw=eip /sbin/iptables-multi
此時就可以用普通用戶來管理防火牆了,如下:
/sbin/iptables -A INPUT -p tcp -j ACCEPT
/sbin/iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
我們也可以刪除防火牆策略,並清空當前的數據流量,如下:
/sbin/iptables -F
/sbin/iptables -Z
/sbin/iptables -X
十五)CAP_NET_RAW 13 (允許使用原始(raw)套接字)
原始套接字編程可以接收到本機網卡上的數據幀或者數據包,對監控網絡流量和分析是很有作用的.
最常見的就是ping的實現,如下:
socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = 3
我們先把ping的setuid權限去掉
chmod u-s /bin/ping
ls -l /bin/ping
-rwxr-xr-x 1 root root 30788 2007-12-09 23:03 /bin/ping
用普通用戶使用ping
ping 192.168.27.2
ping: icmp open socket: Operation not permitted
提示沒有權限,我們將ping授權CAP_NET_RAW能力,如下:
setcap cap_net_raw=eip /bin/ping
切換到普通用戶再次ping 192.168.27.2,發現可以ping通,如下:
ping 192.168.27.2
PING 192.168.27.2 (192.168.27.2) 56(84) bytes of data.
64 bytes from 192.168.27.2: icmp_seq=1 ttl=128 time=0.266 ms
64 bytes from 192.168.27.2: icmp_seq=2 ttl=128 time=0.280 ms
64 bytes from 192.168.27.2: icmp_seq=3 ttl=128 time=0.319 ms
NET_RAW也同樣用於tcpdump/iftop,一個普通用戶無法使用tcpdump/iftop,而CAP_NET_RAW可以解決這個問題,方式同ping一樣,所以我們不做演示.
十六)CAP_IPC_LOCK 14 (在允許鎖定內存片段)
root和普通用戶都可以用mlock來鎖定內存,區別是root不受ulimit下的鎖定內存大小限制,而普通用戶會受到影響.
測試程序如下:
#include <stdio.h>
#include <sys/mman.h>
int main(int argc, char* argv[])
{
int array[2048];
if (mlock((const void *)array, sizeof(array)) == -1) {
perror("mlock: ");
return -1;
}
printf("success to lock stack mem at: %p, len=%zd\n",
array, sizeof(array));
if (munlock((const void *)array, sizeof(array)) == -1) {
perror("munlock: ");
return -1;
}
printf("success to unlock stack mem at: %p, len=%zd\n",
array, sizeof(array));
return 0;
}
gcc mlock.c -o mlock
切換到普通用戶,我們看到mlock是不受限制
ulimit -a
max locked memory (kbytes, -l) unlimited
我們運行程序
./mlock
success to lock stack mem at: 0xbfd94914, len=8192
success to unlock stack mem at: 0xbfd94914, len=8192
我們將限制改爲1KB,再次運行程序,如下:
ulimit -l 1
./mlock
mlock: : Cannot allocate memory
切換到root用戶,將CAP_IPC_LOCK能力授權給mlock測試程序,如下:
setcap cap_ipc_lock=eip /tmp/mlock
用普通用戶再次運行程序,執行成功:
./mlock
success to lock stack mem at: 0xbfec1584, len=8192
success to unlock stack mem at: 0xbfec1584, len=8192
十七)CAP_IPC_OWNER 15 (忽略IPC所有權檢查)
這個能力對普通用戶有作用,如果用root用戶創建共享內存(shmget),權限爲600,而普通用戶不能讀取該段共享內存.
通過CAP_IPC_OWNER可以讓普通用戶的程序可以讀取/更改共享內存.
我們用下面的程序創建共享內存段,並寫入0xdeadbeef到共享內存段中.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
int main (int argc, char *argv[])
{
key_t mykey = 12345678;
const size_t region_size = sysconf(_SC_PAGE_SIZE);
int smid = shmget(mykey, region_size, IPC_CREAT|0600);
if(smid == -1)
error_out("shmget");
void *ptr;
ptr = shmat(smid, NULL, 0);
if (ptr == (void *) -1)
error_out("shmat");
u_long *d = (u_long *)ptr;
*d = 0xdeadbeef;
printf("ipc mem %#lx\n", *(u_long *)ptr);
return 0;
}
gcc test.c -o test -lrt
我們用root用戶來執行本程序,創建共享內存,如下:
/tmp/test
ipc mem 0xdeadbeef
查看當前的共享內存
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00bc614e 458752 root 600 4096 0
修改程序,將*d = 0xdeadbeef;改爲*d = 0xffffffff;
再編譯,用普通用戶運行程序,如下:
gcc test.c -o test -lrt
su - test
/tmp/test
shmget: Permission denied
我們看到沒有權限更改共享內存.
用root用戶把CAP_IPC_OWNER能力授權給test程序,如下:
setcap cap_ipc_owner=eip /tmp/test
再次用普通用戶運行程序test,更改共享內存,如下:
/tmp/test
ipc mem 0xffffffff
我們看到修改成功,但要說明CAP_IPC_OWNER不能讓進程/程序刪除/脫離共享內存
十八)CAP_SYS_MODULE 16 (允許普通用戶插入和刪除內核模塊)
由於普通用戶不能插入/刪除內核模塊,而CAP_SYS_MODULE可以幫助普通用戶做到這點
例如,用戶test插入內核模塊nvram
/sbin/modprobe nvram
FATAL: Error inserting nvram (/lib/modules/2.6.38.4/kernel/drivers/char/nvram.ko): Operation not permitted
系統提示權限不足.
我們把CAP_SYS_MODULE能力授權給modprobe程序,如下:
setcap cap_sys_module=eip /sbin/modprobe
再用普通用戶加載內核模塊,如下:
/sbin/modprobe nvram
lsmod |grep nvram
nvram 3861 0
我們看到可以加載.
同樣我們可以將CAP_SYS_MODULE授權給rmmod程序,讓其可以刪除模塊,如下:
setcap cap_sys_module=eip /sbin/rmmod
su - test
/sbin/rmmod nvram
lsmod |grep nvram
十九)CAP_SYS_RAWIO 17 (允許用戶打開端口,並讀取修改端口數據,一般用ioperm/iopl函數)
ioperm只有低端的[0-0x3ff] I/O端口可被設置,且普通用戶不能使用.
iopl可以用於所有的65536個端口,因此ioperm相當於iopl調用的一個子集.
下面的程序首先設置0x3FF端口的讀寫權限,然後讀出原先的值,然後將原值的LSB翻轉並寫回端口,並在此讀取端口值.
#include <unistd.h>
#include <sys/io.h>
#define PORT_ADDR 0x3FF
int main(void)
{
int ret;
char port_val;
/*set r/w permission of all 65536 ports*/
ret = iopl(3);
if(ret < 0){
perror("iopl set error");
return 0;
}
port_val = inb(PORT_ADDR);
printf("Original value of port 0x%x is : %.2x\n", PORT_ADDR, port_val);
/*reverse the least significant bit */
outb(port_val^0x01, PORT_ADDR);
port_val = inb(PORT_ADDR);
printf("Current value of port 0x%x is : %.2x\n", PORT_ADDR, port_val);
/*set r/w permission of all 65536 ports*/
ret = iopl(0);
if(ret < 0){
perror("iopl set error");
return 0;
}
return 0;
}
編譯:
gcc iopl.c -o iopl
普通用戶運行iopl程序,提示沒有權限.
/tmp/iopl
iopl set error: Operation not permitted
給程序iopl授權CAP_SYS_RAWIO能力,此時普通用戶可以執行iopl程序,如下:
setcap cap_sys_rawio=eip /tmp/iopl
su - test
/tmp/iopl
Original value of port 0x3ff is : 01
Current value of port 0x3ff is : 00
/tmp/iopl
Original value of port 0x3ff is : 00
Current value of port 0x3ff is : 01
二十)CAP_SYS_CHROOT 18 (允許使用chroot()系統調用)
普通用戶不能通過chroot系統調用更改程式執行時所參考的根目錄位置,而CAP_SYS_CHROOT可以幫助普通用戶做到這一點.
普通用戶使用chroot,如下:
/usr/sbin/chroot / /bin/bash
/usr/sbin/chroot: cannot change root directory to /: Operation not permitted
通過root授權CAP_SYS_CHROOT能力給chroot程序,如下:
capset cap_sys_chroot=eip /usr/sbin/chroot
普通用戶再次用chroot切換根目錄,如下:
/usr/sbin/chroot / /bin/sh
sh-3.2$
二十一)CAP_SYS_PTRACE 19 (允許跟蹤任何進程)
普通用戶不能跟蹤任何進程,不包括它自己的進程,而CAP_SYS_PTRACE可以幫助普通用戶跟蹤任何進程.
切換到普通用戶,跟蹤PID1的進程.
strace -p 1
attach: ptrace(PTRACE_ATTACH, ...): Operation not permitted
切換到root用戶,將CAP_SYS_PTRACE能力授權給strace程序,用於跟蹤進程,如下:
setcap cap_sys_ptrace=eip /usr/bin/strace
切換到普通用戶,跟蹤PID1的進程,如下:
strace -p 1
Process 1 attached - interrupt to quit
select(11, [10], NULL, NULL, {3, 771381}) = 0 (Timeout)
time(NULL) = 1304348451
stat64("/dev/initctl", {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
fstat64(10, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
stat64("/dev/initctl", {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
select(11, [10], NULL, NULL, {5, 0}) = 0 (Timeout)
time(NULL) = 1304348456
二十二)CAP_SYS_PACCT 20 (允許配置進程記帳process accounting)
要完成進程記帳,要保證有寫入文件的權限,這裏我們將進程記錄寫入到/home/test/log/psacct.
mkdir /home/test/log/
touch /home/test/log/psacct
程序通過acct函數,將進程的記帳寫入到指定文件中,如果acct函數的參數爲NULL,則關閉進程記帳.
#include <stdlib.h>
#include <sys/acct.h>
int
main()
{
int ret;
ret = acct("/home/test/log/pacct");
if(ret < 0){
perror("acct on error");
return 0;
}
system("/bin/ls -l");
acct(NULL);
if(ret < 0){
perror("acct off error");
return 0;
}
return 0;
}
gcc psacct.c -o psacct
./psacct
acct on error: Operation not permitted
給psacct程序授權CAP_SYS_PACCT能力,如下:
setcap cap_sys_psacct /home/test/psacct
注意這裏的能力是sys_psacct,而不是sys_pacct
回到普通用戶,執行psacct
./psacct
total 24
drwxr-xr-x 2 test test 4096 2011-05-02 13:28 log
-rw-r--r-- 1 test test 64 2011-05-02 13:25 pacct
-rwxr-xr-x 1 test test 6590 2011-05-02 13:31 psacct
-rw-r--r-- 1 test test 314 2011-05-02 13:31 psacct.c
我們看到已經執行成功,下面我們再看下進程記錄,如下:
lastcomm -f /home/test/log/pacct
ls test pts/0 0.03 secs Mon May 2 13:33
二十三)CAP_SYS_ADMIN 21 (允許執行系統管理任務,如掛載/卸載文件系統,設置磁盤配額,開/關交換設備和文件等)
下面是普通用戶擁有相關管理權限的測試
1)更改主機名
setcap cap_sys_admin=eip /bin/hostname
su - test
hostname test2
hostname
test2
2)掛載/卸載文件系統
這裏我們注意,系統的mount命令不能做這個試驗,因爲程序中做了判斷,如果不是root用戶請退出.所以我們用mount()函數來完成這個試驗,程序如下:
#include <stdlib.h>
#include <sys/mount.h>
int
main ()
{
int ret;
ret = mount("/dev/sda1", "/mnt/", "ext3", MS_MGC_VAL, NULL);
if(ret < 0){
perror("mount error");
return 0;
}
return 0;
}
用普通用戶編譯運行:
gcc mounttest.c -o mounttest
./mounttest
mount error: Operation not permitted
我們看到普通用戶不能完成mount操作,而CAP_SYS_ADMIN可以幫助普通用戶完成該操作,如下:
setcap cap_sys_admin=eip /home/test/mounttest
切換到普通用戶,執行mounttest程序,如下:
./mounttest
cat /proc/mounts
略
/dev/sda1 /mnt ext3 rw,relatime,errors=remount-ro,barrier=0,data=writeback 0 0
umount和mount一樣,我們在這裏不做演示.
3)swapon/swapoff
普通用戶不能進行swapon/swapoff操作,而CAP_SYS_ADMIN可以幫助普通用戶完成swapon/swapoff操作,如下:
dd if=/dev/zero of=/tmp/testdb bs=10M count=1
1+0 records in
1+0 records out
10485760 bytes (10 MB) copied, 0.164669 s, 63.7 MB/s
/sbin/mkswap /tmp/testdb
Setting up swapspace version 1, size = 10481 kB
no label, UUID=0ff46dc8-781c-4c3f-81b3-fe860f74793e
/sbin/swapon /tmp/testdb
swapon: /tmp/testdb: Operation not permitted
我們看到swapon操作被拒絕,這裏我們對swapon進行授權,如下:
setcap cap_sys_admin /sbin/swapon
普通用戶再次swapon,如下:
/sbin/swapon /tmp/testdb
/sbin/swapon
/sbin/swapon -s
Filename Type Size Used Priority
/dev/sda6 partition 7815584 0 -1
/tmp/testdb file 10236 0 -2
我們看到swapon操作成功.因爲swapoff是swapon的軟鏈接,所以可以直接swapoff掉交換分區,如下:
/sbin/swapoff /tmp/testdb
/sbin/swapon -s
Filename Type Size Used Priority
/dev/sda6 partition 7815584 0 -1
ls -l /sbin/swapoff
lrwxrwxrwx 1 root root 6 2009-08-23 07:49 /sbin/swapoff -> swapon
二十四) CAP_SYS_BOOT 22 (允許普通用使用reboot()函數)
這裏我們無法用reboot命令來做測試,因爲reboot命令做了判斷,只允許root(UID=0)的用戶可以使用.
我們用下面的程序進行測試.
#include <unistd.h>
#include <sys/reboot.h>
int main()
{
sync();
return reboot(RB_AUTOBOOT);
}
編譯:
gcc reboot1.c -o reboot1
./reboot1
這時系統沒有重啓.
我們查看程序的返回碼,這裏是255,說明程序沒有運行成功.
echo $?
255
我們用CAP_SYS_BOOT能力使reboot1程序可以被普通用戶重啓,如下:
setcap cap_sys_boot=eip /home/test/reboot1
用普通用戶再試運行reboot1,如下:
reboot1
此時系統被重啓了.
二十五)CAP_SYS_NICE 23(允許提升優先級,設置其它進程的優先級)
對於普通用戶程序的NICE優先級,不能超過ulimit對它的限制,如下:
nice -n -5 ls
nice: cannot set niceness: Permission denied
而CAP_SYS_NICE可以幫助普通用戶設置一個想要的一個任意優先級.
setcap cap_sys_nice=eip /usr/bin/nice
切換到普通用戶,指定優先級,如下:
nice -n -5 ls
log mnt mount.c mounttest pacct psacct psacct.c reboot1 reboot1.c test
普通用戶也不能給指定進程指定NICE優先級,而CAP_SYS_NICE也可以做的,如下:
renice -5 2255
renice: 2255: setpriority: Operation not permitted
setcap cap_sys_nice=eip /usr/bin/renice
renice -5 2255
2255: old priority 0, new priority -5
我們甚至可以用CAP_SYS_NICE來指定實時優先級
chrt -f 50 ls
chrt: failed to set pid 0's policy: Operation not permitted
給/usr/bin/chrt命令授權CAP_SYS_NICE能力,如下:
setcap cap_sys_nice=eip /usr/bin/chrt
切換到普通用戶,如下:
chrt -f 50 ls
log mnt setulimit setulimit.c
我們也可以指定它的CPU親和性,如下:
taskset -p 1 2255
pid 2255's current affinity mask: 1
sched_setaffinity: Operation not permitted
failed to set pid 2255's affinity.
給/usr/bin/taskset命令授權CAP_SYS_NICE能力,如下:
setcap cap_sys_nice=eip /usr/bin/taskset
切換到普通用戶,再次運行taskset,可以成功的設置CPU親和性
taskset -p 1 2255
pid 2255's current affinity mask: 1
pid 2255's new affinity mask: 1
二十六) CAP_SYS_RESOURCE 24 忽略資源限制
普通用戶不能用setrlimit()來突破ulimit的限制
我們用下面的程序進行測試,普通用戶是無法修改RLIMIT_STACK(堆棧大小)的.如下:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
int
main (int argc, char *argv[])
{
int r = 0;
struct rlimit rl;
getrlimit (RLIMIT_STACK,&rl);
printf("crrent hard limit is %ld\n",
(u_long) rl.rlim_max);
rl.rlim_max = rl.rlim_max+1;
r = setrlimit (RLIMIT_STACK, &rl);
if (r){
perror("setrlimit");
return -1;
}
printf("limit set to %ld \n", (u_long) rl.rlim_max+1);
return 0;
}
gcc setulimit.c -o setulimit
我們先來查看當前的限制,這裏是10MB,如下:
ulimit -H -s
10240
./setulimit
crrent hard limit is 10485760
setrlimit: Operation not permitted
我們給setulimit程序以CAP_SYS_RESOURCE的能力,如下:
setcap cap_sys_resource=eip /home/test/setulimit
用普通用戶再次運行程序,已經可以通過setrlimit()設定超過限額了,如下:
./setulimit
crrent hard limit is 10485760
limit set to 10485762
同樣我們也可以用CAP_SYS_RESOURCE能力使程序超出磁盤限額,如下:
quotacheck -avug
quotaon -avug
edquota -u test
Filesystem blocks soft hard inodes soft hard
/dev/sda7 0 3 5 0 0 0
mkdir /export/test
chown -R test.test /export/test/
切換到普通用戶,用dd命令產生3MB的文件,這明顯超過了5kb的限制,如下:
su - test
dd if=/dev/zero of=/export/test/test bs=1M count=3
sda7: warning, user block quota exceeded.
sda7: write failed, user block limit reached.
dd: writing `test': Disk quota exceeded
1+0 records in
0+0 records out
4096 bytes (4.1 kB) copied, 0.0117371 s, 349 kB/s
授權CAP_SYS_RESOURCE能力給/bin/dd命令,如下:
setcap cap_sys_resource=eip /bin/dd
再次用普通用戶運行dd命令,即可以超過5kb的限額了.
su - test
dd if=/dev/zero of=test bs=1M count=3
sda7: warning, user block quota exceeded.
3+0 records in
3+0 records out
3145728 bytes (3.1 MB) copied, 0.423662 s, 7.4 MB/s
二十七)CAP_SYS_TIME 25(允許改變系統時鐘)
普通用戶不能改變系統時鐘,如下:
date -s 2012-01-01
date: cannot set date: Operation not permitted
Sun Jan 1 00:00:00 EST 2012
CAP_SYS_TIME可以幫助普通用戶改變系統時鐘,如下:
setcap cap_sys_time=eip /bin/date
切換到普通用戶再次改變時間,發現已經可以改變了
su - test
date -s 2012-01-01
Sun Jan 1 00:00:00 EST 2012
date
Sun Jan 1 00:00:02 EST 2012
二十八)CAP_SYS_TTY_CONFIG 26(允許配置TTY設備)
我們下面用vhangup()函數來掛起當前的tty
程序如下:
#include <stdio.h>
#include <unistd.h>
int main ()
{
int r;
r=vhangup();
if (r){
perror ("vhanguo");
}
return 0;
}
gcc vhup.c -o vhup
./vhup
vhanguo: Operation not permitted
我們給vhup程序設定CAP_SYS_TTY_CONFIG能力,如下:
setcap cap_sys_tty_config=eip /home/test/vhup
再次用普通用戶執行程序vhup,tty被掛起,如下:
./vhup
此時當前tty被掛起.
二十九) CAP_MKNOD 27 (允許使用mknod系統調用)
普通用戶不能用mknod()來創建設備文件,而CAP_MKNOD可以幫助普通用戶做到這一點,如下:
mknod /tmp/tnod1 c 1 5
mknod: `/tmp/tnod1': Operation not permitted
setcap cap_mknod=eip /bin/mknod
切換到普通用戶,再次用mknod命令創建設備文件,如下:
mknod /tmp/tnod1 c 1 5
ls -l /tmp/tnod1
crw-r--r-- 1 test test 1, 5 2012-01-01 00:31 /tmp/tnod1
三十) CAP_LEASE 28(允許在文件上建立租借鎖)
系統調用fcntl()可以用於租借鎖,此時採用的函數原型如下:
int fcntl(int fd, int cmd, long arg);
與租借鎖相關的 cmd 參數的取值有兩種:F_SETLEASE 和 F_GETLEASE。其含義如下所示:
F_SETLEASE:根據下面所描述的 arg 參數指定的值來建立或者刪除租約:
F_RDLCK:設置讀租約。當文件被另一個進程以寫的方式打開時,擁有該租約的當前進程會收到通知
F_WRLCK:設置寫租約。當文件被另一個進程以讀或者寫的方式打開時,擁有該租約的當前進程會收到通知
F_UNLCK:刪除以前建立的租約
F_GETLEASE:表明調用進程擁有文件上哪種類型的鎖,這需要通過返回值來確定,返回值有三種:F_RDLCK、F_WRLCK和F_UNLCK,分別表明調用進程對文件擁有讀租借、寫租借或者根本沒有租借
某個進程可能會對文件執行其他一些系統調用(比如 OPEN() 或者 TRUNCATE()),如果這些系統調用與該文件上由 F_SETLEASE 所設置的租借鎖相沖突,內核就會阻塞這個系統調用;
同時,內核會給擁有這個租借鎖的進程發信號,告知此事。擁有此租借鎖的進程會對該信號進行反饋,它可能會刪除這個租借鎖,也可能會減短這個租借鎖的租約,從而可以使得該文件可以被其他進程所訪問。
如果擁有租借鎖的進程不能在給定時間內完成上述操作,那麼系統會強制幫它完成。通過 F_SETLEASE 命令將 arg 參數指定爲 F_UNLCK 就可以刪除這個租借鎖。
不管對該租借鎖減短租約或者乾脆刪除的操作是進程自願的還是內核強迫的,只要被阻塞的系統調用還沒有被髮出該調用的進程解除阻塞,那麼系統就會允許這個系統調用執行。
即使被阻塞的系統調用因爲某些原因被解除阻塞,但是上面對租借鎖減短租約或者刪除這個過程還是會執行的。
源程序如下:
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/file.h>
static void show_lease(int fd)
{
int res;
res = fcntl(fd, F_GETLEASE);
switch (res) {
case F_RDLCK:
printf("Read lease\n");
break;
case F_WRLCK:
printf("Write lease\n");
break;
case F_UNLCK:
printf("No leases\n");
break;
default:
printf("Some shit\n");
break;
}
}
int main(int argc, char **argv)
{
int fd, res;
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("Can't open file");
return 1;
}
res = fcntl(fd, F_SETLEASE, F_WRLCK);
if (res == -1) {
perror("Can't set lease");
return 1;
}
show_lease(fd);
if (flock(fd, LOCK_SH) == -1) {
perror("Can't flock shared");
return 1;
}
show_lease(fd);
return 0;
}
編譯:
gcc fcntl.c -o fcntl
我們使用普通用戶在/etc/passwd文件上建立租借鎖,由於普通用戶沒有/etc/passwd上建租借鎖的權限,故而報錯,如下:
su - test
/tmp/fcntl /etc/passwd
Can't set lease: Permission denied
注:
普通用戶可以在自己的文件上(owner)建立租借鎖.
授權lease給/tmp/fcntl文件,如下:
setcap cap_lease=eip /tmp/fcntl
再次運行/tmp/fcntl,可以建立租借鎖了,如下:
/tmp/fcntl /etc/passwd
Write lease
Write lease
三十一)CAP_SETFCAP 31 (允許在指定的程序上授權能力給其它程序)
例如我們讓普通用戶也能用setcap給其它程序授權,如下:
setcap CAP_SETFCAP=eip /usr/sbin/setcap
su - test
setcap CAP_SETPCAP=eip /bin/ls
getcap /bin/ls
/bin/ls = cap_setpcap+eip
三十二)其它的能力
CAP_AUDIT_WRITE
CAP_AUDIT_CONTROL
CAP_MAC_OVERRIDE
CAP_MAC_ADMIN
CAP_SYSLOG
這五組只能涉及到syslog,audit,mac等安全模塊,以後專門對其進行分析.
三十三)總結:
CAP_CHOWN 0 允許改變文件的所有權
CAP_DAC_OVERRIDE 1 忽略對文件的所有DAC訪問限制
CAP_DAC_READ_SEARCH 2 忽略所有對讀、搜索操作的限制
CAP_FOWNER 3 以最後操作的UID,覆蓋文件的先前的UID
CAP_FSETID 4 確保在文件被修改後不修改setuid/setgid位
CAP_KILL 5 允許對不屬於自己的進程發送信號
CAP_SETGID 6 允許改變組ID
CAP_SETUID 7 允許改變用戶ID
CAP_SETPCAP 8 允許向其它進程轉移能力以及刪除其它進程的任意能力(只限init進程)
CAP_LINUX_IMMUTABLE 9 允許修改文件的不可修改(IMMUTABLE)和只添加(APPEND-ONLY)屬性
CAP_NET_BIND_SERVICE 10 允許綁定到小於1024的端口
CAP_NET_BROADCAST 11 允許網絡廣播和多播訪問(未使用)
CAP_NET_ADMIN 12 允許執行網絡管理任務:接口、防火牆和路由等.
CAP_NET_RAW 13 允許使用原始(raw)套接字
CAP_IPC_LOCK 14 允許鎖定共享內存片段
CAP_IPC_OWNER 15 忽略IPC所有權檢查
CAP_SYS_MODULE 16 插入和刪除內核模塊
CAP_SYS_RAWIO 17 允許對ioperm/iopl的訪問
CAP_SYS_CHROOT 18 允許使用chroot()系統調用
CAP_SYS_PTRACE 19 允許跟蹤任何進程
CAP_SYS_PACCT 20 允許配置進程記帳(process accounting)
CAP_SYS_ADMIN 21 允許執行系統管理任務:加載/卸載文件系統、設置磁盤配額、開/關交換設備和文件等.
CAP_SYS_BOOT 22 允許重新啓動系統
CAP_SYS_NICE 23 允許提升優先級,設置其它進程的優先級
CAP_SYS_RESOURCE 24 忽略資源限制
CAP_SYS_TIME 25 允許改變系統時鐘
CAP_SYS_TTY_CONFIG 26 允許配置TTY設備
CAP_MKNOD 27 允許使用mknod()系統調用
CAP_LEASE 28 允許在文件上建立租借鎖
CAP_SETFCAP 31 允許在指定的程序上授權能力給其它程序
參考:
man capabilities
linux-2.6.38.5/include/linux/capability.h