UNIXpȝ为程序员提供了许多子E序,q些子程序可存取各种安全属??
些是信息子程?q回文g属?实际的和有效的UID,GID{信?有些子程序可
改变文g属?UID,GID{有些处理口令文件和组文g,q有些完成加密和解密.
本文主要讨论有关pȝ子程?标准C库子E序的安?如何写安全的CE序
q从root的角度介l程序设?仅能被root调用的子E序).
1.pȝ子程?
(1)I/O子程?
*creat():建立一个新文g或重写一个暂存文?
需要两个参?文g名和存取许可?8q制方式).?
creat(?usr/pat/read_write?0666) /* 建立存取许可方式?666的文?*/
调用此子E序的进E必要有徏立的文g的所在目录的写和执行许可,|?
lcreat()的许可方式变量将被umask()讄的文件徏立屏蔽值所修改,?
文g的所有者和组由有效的UID和GID军_.
q回gؓ新徏文g的文件描q符.
*fstat():见后面的stat().
*open():在CE序内部打开文g.
需要两个参?文g路径名和打开方式(I,O,I&O).
如果调用此子E序的进E没有对于要打开的文件的正确存取许可(包括?
件\径上所有目录分量的搜烦许可),会引v执行p|.
如果此子E序被调用去打开不存在的文g,除非讄了O_CREAT标志,调用
不成功.此时,新文件的存取许可作ؓW三个参?可被用户的umask?
?.
当文件被q程打开后再改变该文件或该文件所在目录的存取许可,不媄?
对该文g的I/O操作.
*read():从已由open()打开q用作输入的文g中读信息.
它ƈ不关心该文g的存取许?一旦文件作入打开,卛_从该文g中读
取信?
*write():输出信息到已由open()打开q用作输出的文g?同read()一?
它也不关心该文g的存取许?
(2)q程控制
*exec()?包括execl(),execv(),execle(),execve(),execlp()和execvp()
可将一可执行模快拷贝到调用q程占有的存贮空?正被调用q?
E执行的E序不复存?新程序取代其位置.
q是UNIXpȝ中一个程序被执行的唯一方式:用将执行的程序复盖原有的
E序.
安全注意事项:
. 实际的和有效的UID和GID传递给由exec()调入的不hSUID和SGID?
可的E序.
. 如果由exec()调入的程序有SUID和SGID许可,则有效的UID和GID设
|给该程序的所有者或组.
. 文g建立屏蔽值将传递给新程?
. 除设了对exec()关闭标志的文件外,所有打开的文仉传递给新程?
用fcntl()子程序可讄对exec()的关闭标?
*fork():用来建立新进E?其徏立的子进E是与调用fork()的进E?父进E?
完全相同的拷?除了q程号外)
安全注意事项:
. 子进E将l承父进E的实际和有效的UID和GID.
. 子进E承文件方式徏立屏蔽?
. 所有打开的文件传l子q程.
*signal():允许q程处理可能发生的意外事件和中断.
需要两个参?信号~号和信号发生时要调用的子程?
信号~号定义在signal.h?
信号发生时要调用的子E序可由用户~写,也可用系l给的??SIG_IGN
则信号将被忽?SIG_DFL则信号将按系l的~省方式处理.
如许多与安全有关的程序禁止终端发中断信息(BREAK和DELETE),以免自己
被用L端终止运?
有些信号使UNIXpȝ的生进E的核心转储(q程接收Ch所占内?
的内?有时含有重要信息),此系l子E序可用于禁止核心{?
(3)文g属?
*access():指定文件的存取能力是否W合指定的存取类?
需要两个参?文g名和要检的存取cd(整数).
存取cd定义如下:
0: 查文件是否存?
1: 查是否可执行(搜烦)
2: 查是否可?
3: 查是否可写和执行
4: 查是否可?
5: 查是否可d执行
6: 查是否可d写可执行
q些数字的意义和chmod命o中规定许可方式的数字意义相同.
此子E序使用实际的UID和GID文件的存取能力(一般有效的UID和GID
用于查文件存取能?.
q回? 0:许可 -1:不许?
*chmod():指定文件或目录的存取许可方式改成新的许可方?
需要两个参?文g名和新的存取许可方式.
*chown():同时改变指定文g的所有者和组的UID和GID.(与chown命o?
?.
׃此子E序同时改变文g的所有者和组,故必d消所操作文g的SUID
和SGID许可,以防止用户徏立SUID和SGIDE序,然后q行chown()去获得别
人的权限.
*stat():q回文g的状?属?.
需要两个参?文g路径名和一个结构指?指向状态信息的存放
的位|?
l构定义如下:
st_mode: 文gcd和存取许可方?
st_ino: I节点?
st_dev: 文g所在设备的ID
st_rdev: 特别文g的ID
st_nlink: 文g链接?
st_uid: 文g所有者的UID
st_gid: 文g组的GID
st_size: 按字节计数的文g大小
st_atime: 最后存取时??
st_mtime: 最后修Ҏ??和最后状态的改变
st_ctime: 最后的状态修Ҏ?
q回? 0:成功 1:p|
*umask():调用进E及其子q程的文件徏立屏蔽D|ؓ指定的存取许?
需要一个参? 新的文g建立屏?
(4)UID和GID的处?
*getuid():q回q程的实际UID.
*getgid():q回q程的实际GID.
以上两个子程序可用于定是谁在运行进E?
*geteuid():q回q程的有效UID.
*getegid():q回q程的有效GID.
以上两个子程序可在一个程序不得不定它是否在q行某用戯不是运?
它的用户的SUIDE序时很有用,可调用它们来查确认本E序的确是以?
用户的SUID许可在运?
*setuid():用于改变有效的UID.
对于一般用?此子E序仅对要在有效和实际的UID之间变换的SUIDE序?
有用(从原有效UID变换为实际UID),以保护进E不受到安全危害.实际上该
q程不再是SUID方式q行.
*setgid():用于改变有效的GID.
2.标准C?
(1)标准I/O
*fopen():打开一个文件供L?安全斚w的考虑同open()一?
*fread(),getc(),fgetc(),gets(),scanf()和fscanf():从已由fopen()?
开供读的文件中d信息.它们q不兛_文g的存取许?q一?
同read().
*fwrite(),put(),fputc(),puts,fputs(),printf(),fprintf():写信息到
已由fopen()打开供写的文件中.它们也不兛_文g的存取许?
同write().
*getpass():从终端上读至?个字W长的口?不回昄戯入的字符.
需要一个参? 提示信息.
该子E序提CZ息显C在l端?止字符回显功能,?dev/ttyd?
?然后再恢复字W回昑֊?q回刚敲入的口o的指?
*popen():在(5)q行shell中介l?
(2)/etc/passwd处理
有一l子E序可对/etc/passwd文gq行方便的存?可对文gd到入?
Ҏ写新的入口项或更新等{?
*getpwuid():?etc/passwd文g中获取指定的UID的入口项.
*getpwnam():对于指定的登录名,?etc/passwd文g索入口项.
以上两个子程序返回一指向passwdl构的指?该结构定义在
/usr/include/pwd.h?定义如下:
struct passwd {
char * pw_name; /* d?*/
char * pw_passwd; /* 加密后的口o */
uid_t pw_uid; /* UID */
gid_t pw_gid; /* GID */
char * pw_age; /* 代理信息 */
char * pw_comment; /* 注释 */
char * pw_gecos;
char * pw_dir; /* ȝ?*/
char * pw_shell; /* 使用的shell */
};
*getpwent(),setpwent(),endpwent():对口令文件作后箋处理.
首次调用getpwent(),打开/etc/passwdq返回指向文件中W一个入口项?
指针,保持调用之间文g的打开状?
再调用getpwent()可顺序地q回口o文g中的各入口项.
调用setpwent()把口令文件的指针重新|ؓ文g的开始处.
使用完口令文件后调用endpwent()关闭口o文g.
*putpwent():修改或增?etc/passwd文g中的入口?
此子E序入口项写到一个指定的文g?一般是一个时文?直接写口
令文件是很危险的.最好在执行前做文g锁,使两个程序不能同时写一?
文g.法如下:
. 建立一个独立的临时文g,?etc/passnnn,nnn是PID?
. 建立C生的临时文g和标准时文?etc/ptmp的链,若徏铑֤?
则ؓ有h正在使用/etc/ptmp,{待直到/etc/ptmp可用为止或退?
. ?etc/passwd拯?etc/ptmp,可对此文件做M修改.
. ?etc/passwdUd备䆾文g/etc/opasswd.
. 建立/etc/ptmp?etc/passwd的链.
. 断开/etc/passnnn?etc/ptmp的链.
注意:临时文g应徏立在/etc目录,才能保证文g处于同一文gpȝ??
链才能成?且时文件不会不安全.此外,若新文g已存?即便?
铄是root用户,也将p|,从而保证了一旦时文件成功地建链?
没有再插q来q扰.当然,使用临时文g的程序应保清除所?
临时文g,正确地捕捉信?
(3)/etc/group的处?
有一l类g前面的子E序处理/etc/group的信?使用时必ȝinclude
语句?usr/include/grp.h文g加入到自qE序?该文件定义了group
l构,由getgrnam(),getgrgid(),getgrent()q回groupl构指针.
*getgrnam():?etc/group文g中搜索指定的组?然后q回指向组?
口项的指?
*getgrgid():cM于前一子程?不同的是搜烦指定的GID.
*getgrent():q回group文g中的下一个入口项.
*setgrent():group文g的文件指针恢复到文g的v?
*endgrent():用于完成工作?关闭group文g.
*getuid():q回调用q程的实际UID.
*getpruid():以getuid()q回的实际UID为参?定与实际UID相应的登?
?或指定一UID为参?
*getlogin():q回在终端上d的用L指针.
pȝ依次查STDIN,STDOUT,STDERR是否与终端相?与终端相联的标准?
入用于确定终端名,l端名用于查扑ֈ?etc/utmp文g中的用户,该文?
由loginl护,由whoE序用来认用户.
*cuserid():首先调用getlogin(),若getlogin()q回NULL指针,再调?
getpwuid(getuid()).
*以下为命?
*logname:列出dq终端的用户?
*who am i:昄行这条命令的用户的登录名.
*id:昄实际的UID和GID(若有效的UID和GID和实际的不同时也昄有效?
UID和GID)和相应的d?
(4)加密子程?
1977q??NBS宣布一个用于美国联邦政府ADPpȝ的网l的标准加密??
据加密标准即DES用于非机密应用方?DES一ơ处?4BITS的块,56位的?
密键.
*setkey(),encrypt():提供用户对DES的存?
此两子程序都?4BITS长的字符数组,数组中的每个元素代表一个位,?
?.setkey()讄按DES处理的加密键,忽略每第8位构成一?6位的?
密键.encrypt()然后加密或解密给定的64BITS长的一?加密或解密取?
于该子程序的W二个变?0:加密 1:解密.
*crypt():是UNIXpȝ中的口o加密E序,也被/usr/lib/makekey命o调用.
crypt()子程序与crypt命o无关,它与/usr/lib/makekey一样取8个字W长
的关键词,2个salt字符.关键词送给setkey(),salt字符用于混合encrypt()
中的DES法,最l调用encrypt()重复25ơ加密一个相同的字符?
q回加密后的字符串指?
(5)q行shell
*system():q行/bin/sh执行其参数指定的命o,当指定命令完成时q回.
*popen():cM于system(),不同的是命oq行?其标准输入或输出联到?
popen()q回的文件指?
二者都调用fork(),exec(),popen()q调用pipe(),完成各自的工?因?
fork()和exec()的安全方面的考虑开始v作用.
3.写安全的CE序
一般有两方面的安全问题,在写E序时必考虑:
(1)保自己建立的Q何时文件不含有机密数据,如果有机密数?讄
临时文g仅对自己可读/?保建立临时文g的目录仅对自己可?
(2)保自己要运行的M命o(通过system(),popen(),execlp(),
execvp()q行的命?的确是自pq行的命?而不是其它什么命
?其是自qE序为SUID或SGID许可时要心.
W一斚w比较?在程序开始前调用umask(077).若要使文件对其他人可
?可再调chmod(),也可用下q语名徏立一个”不可见”的临时文g.
creat(?tmp/xxx?0);
file=open(?tmp/xxx?O_RDWR);
unlink(?tmp/xxx?;
文g/tmp/xxx建立?打开,然后断开?但是分配l该文g的存储器q未?
?直到最l指向该文g的文仉道被关闭时才被删除.打开该文件的q程
和它的Q何子q程都可存取q个临时文g,而其它进E不能存取该文g,?
为它?tmp中的目录已被unlink()删除.
W二斚w比较复杂而微?׃system(),popen(),execlp(),execvp()执行
?若不l出执行命o的全路径,p”骗”用LE序L行不同的命o.?
为系l子E序是根据PATH变量定哪种序搜烦哪些目录,以寻找指定的?
?q称为SUID陷井.最安全的办法是在调用system()前将有效UID改变成实
际UID,另一U比较好的方法是以全路径名命令作为参?execl(),execv(),
execle(),execve()都要求全路径名作为参?有关SUID陷井的另一方式?
在程序中讄PATH,׃system()和popen()都启动shell,故可使用shell?
??
system(”PATH=/bin:/usr/bin cd?;
q样允许用户q行pȝ命o而不必知道要执行的命令在哪个目录?但这U?
Ҏ不能用于execlp(),execvp()?因ؓ它们不能启动shell执行调用序列
传递的命o字符?
关于shell解释传递给system()和popen()的命令行的方?有两个其它的?
?
*shell使用IFS shell变量中的字符,命令行分解成单?通常q个
shell变量中是I格,tab,换行),如IFS中是/,字符?bin/ed被解释成单词
bin,接下来是单词ed,从而引起命令行的曲?
再强调一?在通过自己的程序运行另一个程序前,应将有效UID改ؓ实际?
UID,{另一个程序退出后,再将有效UID改回原来的有效UID.
SUID/SGIDE序指导准则
(1)不要写SUID/SGIDE序,大多数时候无此必?
(2)讄SGID许可,不要讄SUID许可.应独自徏立一个新的小l?
(3)不要用exec()执行ME序.Cexec()也被system()和popen()调用.
. 若要调用exec()(或system(),popen()),应事先用setgid(getgid())
有效GID|加实际GID.
. 若不能用setgid(),则调用system()或popen()?应设|IFS:
popen(”IFS=\t\n;export IFS;/bin/ls?”r?;
. 使用要执行的命o的全路径?
. 若不能用全路径?则应在命令前先设|PATH:
popen(”IFS=\t\n;export IFS;PATH=/bin:/usr/bin;/bin/ls?”r?;
. 不要用戯定的参数传给system()或popen();若无法避免则应检?
变元字符串中是否有特D的shell字符.
. 若用h个大E序,调用exec()执行许多其它E序,q种情况下不要将
大程序设|ؓSGID许可.可以写一?或多?更小,更简单的SGIDE序
执行必须hSGID许可的Q?然后由大E序执行q些SGIDE序.
(4)若用户必M用SUID而不是SGID,以相同的序C(2),(3)内?q?
相应调整.不要讄root的SUID许可.选一个其它户?
(5)若用hl予其他人执行自qshellE序的许?但又不想让他们能
读该E序,可将E序讄Z执行许可,q只能通过自己的shellE序?
q行.
~译,安装SUID/SGIDE序时应按下面的Ҏ
(1)保所有的SUID(SGID)E序是对于小l和其他用户都是不可写的,存取
权限的限制低?755(2755)带来麻?只能更严?4111(2111)
其他人无法寻扄序中的安全漏z?
(2)警惕外来的编码和make/installҎ
. 某些make/installҎ不加选择地徏立SUID/SGIDE序.
. 查违背上q指导原则的SUID/SGID许可的编?
. 查makefile文g中可能徏立SUID/SGID文g的命?
4.rootE序的设?
有若q个子程序可以从有效UID?的进E中调用.许多前面提到的子E序,
当从rootq程中调用时,完成和原来不同的处?主要是忽略了许可权限的检
?
由root用户q行的程序当然是rootq程(SUID除外),因有效UID用于定?
件的存取权限,所以从hroot的程序中,调用fork()产生的进E?也是rootq程.
(1)setuid():从rootq程调用setuid()?其处理有所不同,setuid()把?
效的和实际的UID都置为指定的?q个值可以是M整型?而对非root
q程则仅能以实际UID或本q程原来有效的UID为变量D用setuid().
(2)setgid():在系l进E中调用setgid()?与setuid()cM,实际和有效
的GID都改变成其参数指定的?
* 调用以上两个子程序时,应当注意下面几点:
. 调用一ơsetuid()(setgid())同时设|有效和实际UID(GID),独立?
别设|有效或实际UID(GID)固然很好,但无法做到这?
. setuid()(setgid())可将有效和实际UID(GID)讄成Q何整型数,其数
g必一定与/etc/passwd(/etc/group)中用?组)相关?
. 一旦程序以一个用LUID了setuid(),该程序就不再做ؓrootq行,?
不可能再获rootҎ.
(3)chown():当rootq程q行chown()?chown()不删除文g的SUID??
SGID许可,但当非rootq程q行chown()?chown()取消文件的SUID?
或SGID许可.
(4)chroot():改变q程Ҏ目录的概?调用chroot()?q程׃能把当前
工作目录改变到新的根目录以上的Q一目录,所有以/开始的路径搜烦,?
从新的根目录开?
(5)mknod():用于建立一个文?cM于creat(),差别是mknod()不返回所打开
文g的文件描q符,q且能徏立Q何类型的文g(普通文?Ҏ文g,目录
文g).若从非rootq程调用mknod()执行失?只有建立FIFO特别文g
(有名道文g)时例?其它M情况?必须从rootq程调用mknod().?
于creat()仅能建立普通文?mknod()是徏立目录文件的唯一途径,因而仅
有root能徏立目?q就是ؓ什么mkdir命ohSUID许可q属root所?
一般不从程序中调用mknod().通常?etc/mknod命o建立特别讑֤文g?
q些文g一般不能在使用着时徏立和删除,mkdir命o用于建立目录.当用
mknod()建立特别文g?应当注意从所建的特别文g不允许存取内?
盘,l端和其它设?
(6)unlink():用于删除文g.参数是要删除文g的\径名指针.当指定了目录
?必须从rootq程调用unlink(),q是必须从rootq程调用unlink()的唯
一情况,q就是ؓ什么rmdir命ohroot的SGID许可的原?
(7)mount(),umount():由rootq程调用,分别用于安装和拆卸文件系l?q两
个子E序也被mount和umount命o调用,其参数基本和命o的参数相??
用mount(),需要给Z个特别文件和一个目录的指针,特别文g上的文g
pȝ将安装在该目录?调用时还要给Z个标识选项,指定被安装的?
件系l要被读/?0)q是仅读(1).umount()的参数是要一个要拆卸的特?
文g的指?
本文由isbase成员~译或原创,如要转蝲请保持文章的完整?