3.寫安全的C程序
一般有兩方面的安全問題,在寫程序時必須考慮:
(1)確保自己建立的任何臨時文件不含有機密數據,如果有機密數據,設置
臨時文件僅對自己可讀/寫.確保建立臨時文件的目錄僅對自己可寫.
(2)確保自己要運行的任何命令(通過system(),popen(),execlp(),
execvp()運行的命令)的確是自己要運行的命令,而不是其它什麼命
令,尤其是自己的程序為SUID或SGID許可時要小心.
第一方面比較簡單,在程序開始前調用umask(077).若要使文件對其他人可
讀,可再調chmod(),也可用下述語名建立一個"不可見"的臨時文件.
creat("/tmp/xxx",0);
file=open("/tmp/xxx",O_RDWR);
unlink("/tmp/xxx");
文件/tmp/xxx建立後,打開,然後斷開鏈,但是分配給該文件的存儲器並未刪
除,直到最終指向該文件的文件通道被關閉時才被刪除.打開該文件的進程
和它的任何子進程都可存取這個臨時文件,而其它進程不能存取該文件,因
為它在/tmp中的目錄項已被unlink()刪除.
第二方面比較復雜而微妙,由於system(),popen(),execlp(),execvp()執行
時,若不給出執行命令的全路徑,就能"騙"用戶的程序去執行不同的命令.因
為系統子程序是根據PATH變量確定哪種順序搜索哪些目錄,以尋找指定的命
令,這稱為SUID陷井.最安全的辦法是在調用system()前將有效UID改變成實
際UID,另一種比較好的方法是以全路徑名命令作為參數.execl(),execv(),
execle(),execve()都要求全路徑名作為參數.有關SUID陷井的另一方式是
在程序中設置PATH,由於system()和popen()都啟動shell,故可使用shell句
法.如:
system("PATH=/bin:/usr/bin cd");
這樣允許用戶運行系統命令而不必知道要執行的命令在哪個目錄中,但這種
方法不能用於execlp(),execvp()中,因為它們不能啟動shell執行調用序列
傳遞的命令字符串.
關於shell解釋傳遞給system()和popen()的命令行的方式,有兩個其它的問
題:
*shell使用IFS shell變量中的字符,將命令行分解成單詞(通常這個
shell變量中是空格,tab,換行),如IFS中是/,字符串/bin/ed被解釋成單詞
bin,接下來是單詞ed,從而引起命令行的曲解.
再強調一次:在通過自己的程序運行另一個程序前,應將有效UID改為實際的
UID,等另一個程序退出後,再將有效UID改回原來的有效UID.
SUID/SGID程序指導准則
(1)不要寫SUID/SGID程序,大多數時候無此必要.
(2)設置SGID許可,不要設置SUID許可.應獨自建立一個新的小組.
(3)不要用exec()執行任何程序.記住exec()也被system()和popen()調用.
. 若要調用exec()(或system(),popen()),應事先用setgid(getgid())
將有效GID置加實際GID.
. 若不能用setgid(),則調用system()或popen()時,應設置IFS:
popen("IFS=\t\n;export IFS;/bin/ls","r");
. 使用要執行的命令的全路徑名.
. 若不能使用全路徑名,則應在命令前先設置PATH:
popen("IFS=\t\n;export IFS;PATH=/bin:/usr/bin;/bin/ls","r");
. 不要將用戶規定的參數傳給system()或popen();若無法避免則應檢查
變元字符串中是否有特殊的shell字符.
. 若用戶有個大程序,調用exec()執行許多其它程序,這種情況下不要將
大程序設置為SGID許可.可以寫一個(或多個)更小,更簡單的SGID程序
執行必須具有SGID許可的任務,然後由大程序執行這些小SGID程序.
(4)若用戶必須使用SUID而不是SGID,以相同的順序記住(2),(3)項內容,並
相應調整.不要設置root的SUID許可.選一個其它戶頭.
(5)若用戶想給予其他人執行自己的shell程序的許可,但又不想讓他們能
讀該程序,可將程序設置為僅執行許可,並只能通過自己的shell程序來
運行.
編譯,安裝SUID/SGID程序時應按下面的方法
(1)確保所有的SUID(SGID)程序是對於小組和其他用戶都是不可寫的,存取
權限的限制低於4755(2755)將帶來麻煩.只能更嚴格.4111(2111)將使
其他人無法尋找程序中的安全漏洞.
(2)警惕外來的編碼和make/install方法
. 某些make/install方法不加選擇地建立SUID/SGID程序.
. 檢查違背上述指導原則的SUID/SGID許可的編碼.
. 檢查makefile文件中可能建立SUID/SGID文件的命令.
4.root程序的設計
有若干個子程序可以從有效UID為0的進程中調用.許多前面提到的子程序,
當從root進程中調用時,將完成和原來不同的處理.主要是忽略了許可權限的檢
查.
由root用戶運行的程序當然是root進程(SUID除外),因有效UID用於確定文
件的存取權限,所以從具有root的程序中,調用fork()產生的進程,也是root進程.
(1)setuid():從root進程調用setuid()時,其處理有所不同,setuid()將把有
效的和實際的UID都置為指定的值.這個值可以是任何整型數.而對非root
進程則僅能以實際UID或本進程原來有效的UID為變量值調用setuid().
(2)setgid():在系統進程中調用setgid()時,與setuid()類似,將實際和有效
的GID都改變成其參數指定的值.
* 調用以上兩個子程序時,應當注意下面幾點:
. 調用一次setuid()(setgid())將同時設置有效和實際UID(GID),獨立分
別設置有效或實際UID(GID)固然很好,但無法做到這點.
. setuid()(setgid())可將有效和實際UID(GID)設置成任何整型數,其數
值不必一定與/etc/passwd(/etc/group)中用戶(小組)相關聯.
. 一旦程序以一個用戶的UID了setuid(),該程序就不再做為root運行,也
不可能再獲root特權.
(3)chown():當root進程運行chown()時,chown()將不刪除文件的SUID和/或
SGID許可,但當非root進程運行chown()時,chown()將取消文件的SUID和/
或SGID許可.
(4)chroot():改變進程對根目錄的概念,調用chroot()後,進程就不能把當前
工作目錄改變到新的根目錄以上的任一目錄,所有以/開始的路徑搜索,都
從新的根目錄開始.
(5)mknod():用於建立一個文件,類似於creat(),差別是mknod()不返回所打開
文件的文件描述符,並且能建立任何類型的文件(普通文件,特殊文件,目錄
文件).若從非root進程調用mknod()將執行失敗,只有建立FIFO特別文件
(有名管道文件)時例外,其它任何情況下,必須從root進程調用mknod().由
於creat()僅能建立普通文件,mknod()是建立目錄文件的唯一途徑,因而僅
有root能建立目錄,這就是為什麼mkdir命令具有SUID許可並屬root所有.
一般不從程序中調用mknod().通常用/etc/mknod命令建立特別設備文件而
這些文件一般不能在使用著時建立和刪除,mkdir命令用於建立目錄.當用
mknod()建立特別文件時,應當注意確從所建的特別文件不允許存取內存,
磁盤,終端和其它設備.
(6)unlink():用於刪除文件.參數是要刪除文件的路徑名指針.當指定了目錄
時,必須從root進程調用unlink(),這是必須從root進程調用unlink()的唯
一情況,這就是為什麼rmdir命令具有root的SGID許可的原因.
(7)mount(),umount():由root進程調用,分別用於安裝和拆卸文件系統.這兩
個子程序也被mount和umount命令調用,其參數基本和命令的參數相同.調
用mount(),需要給出一個特別文件和一個目錄的指針,特別文件上的文件
系統就將安裝在該目錄下,調用時還要給出一個標識選項,指定被安裝的文
件系統要被讀/寫(0)還是僅讀(1).umount()的參數是要一個要拆卸的特別
文件的指針.
——摘自:http://www.linuxaid.com.cn