欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

第二部分 (16千字)

第二部分 (16千字)

安全注意事项:
. 实际的和有效的UID和GID传递给由exec()调入的不具有SUID和SGID许
可的程序.
. 如果由exec()调入的程序有SUID和SGID许可,则有效的UID和GID将设
置给该程序的所有者或小组.
. 文件建立屏蔽值将传递给新程序.
. 除设了对exec()关闭标志的文件外,所有打开的文件都传递给新程序.
用fcntl()子程序可设置对exec()的关闭标志.
*fork():用来建立新进程.其建立的子进程是与调用fork()的进程(父进程)
完全相同的拷贝(除了进程号外)
安全注意事项:
. 子进程将继承父进程的实际和有效的UID和GID.
. 子进程继承文件方式建立屏蔽值.
. 所有打开的文件传给子进程.
*signal():允许进程处理可能发生的意外事件和中断.
需要两个参数:信号编号和信号发生时要调用的子程序.
信号编号定义在signal.h中.
信号发生时要调用的子程序可由用户编写,也可用系统给的值,如:SIG_IGN
则信号将被忽略,SIG_DFL则信号将按系统的缺省方式处理.
如许多与安全有关的程序禁止终端发中断信息(BREAK和DELETE),以免自己
被用户终端终止运行.
有些信号使UNIX系统的产生进程的核心转储(进程接收到信号时所占内存
的内容,有时含有重要信息),此系统子程序可用于禁止核心转储.
(3)文件属性
*access():检测指定文件的存取能力是否符合指定的存取类型.
需要两个参数:文件名和要检测的存取类型(整数).
下面还有喔 (19%) │ 结束 ← │ ↑/↓/PgUp/PgDn 移动 │ ? 辅助说明 │
需要两个参数:文件名和要检测的存取类型(整数).
存取类型定义如下:
0: 检查文件是否存在
1: 检查是否可执行(搜索)
2: 检查是否可写
3: 检查是否可写和执行
4: 检查是否可读
5: 检查是否可读和执行
6: 检查是否可读可写可执行
这些数字的意义和chmod命令中规定许可方式的数字意义相同.
此子程序使用实际的UID和GID检测文件的存取能力(一般有效的UID和GID
用于检查文件存取能力).
返回值: 0:许可 -1:不许可.
*chmod():将指定文件或目录的存取许可方式改成新的许可方式.
需要两个参数:文件名和新的存取许可方式.
*chown():同时改变指定文件的所有者和小组的UID和GID.(与chown命令不
同).
由于此子程序同时改变文件的所有者和小组,故必须取消所操作文件的SUID
和SGID许可,以防止用户建立SUID和SGID程序,然后运行chown()去获得别
人的权限.
*stat():返回文件的状态(属性).
需要两个参数:文件路径名和一个结构指针,指向状态信息的存放
的位置.
结构定义如下:
st_mode: 文件类型和存取许可方式
st_ino: I节点号
st_dev: 文件所在设备的ID
st_rdev: 特别文件的ID
st_nlink: 文件链接数
st_uid: 文件所有者的UID
st_gid: 文件小组的GID
st_size: 按字节计数的文件大小
st_atime: 最后存取时间(读)
st_mtime: 最后修改时间(写)和最后状态的改变
st_ctime: 最后的状态修改时间
返回值: 0:成功 1:失败
*umask():将调用进程及其子进程的文件建立屏蔽值设置为指定的存取许可.
需要一个参数: 新的文件建立屏值.
(4)UID和GID的处理
*getuid():返回进程的实际UID.
*getgid():返回进程的实际GID.
以上两个子程序可用于确定是谁在运行进程.
*geteuid():返回进程的有效UID.
*getegid():返回进程的有效GID.
以上两个子程序可在一个程序不得不确定它是否在运行某用户而不是运行
它的用户的SUID程序时很有用,可调用它们来检查确认本程序的确是以该
用户的SUID许可在运行.
*setuid():用于改变有效的UID.
对于一般用户,此子程序仅对要在有效和实际的UID之间变换的SUID程序才
有用(从原有效UID变换为实际UID),以保护进程不受到安全危害.实际上该
进程不再是SUID方式运行.
*setgid():用于改变有效的GID.
标准C库
(1)标准I/O
*fopen():打开一个文件供读或写,安全方面的考虑同open()一样.
*fread(),getc(),fgetc(),gets(),scanf()和fscanf():从已由fopen()打
开供读的文件中读取信息.它们并不关心文件的存取许可.这一点
同read().
*fwrite(),put(),fputc(),puts,fputs(),printf(),fprintf():写信息到
已由fopen()打开供写的文件中.它们也不关心文件的存取许可.
同write().
*getpass():从终端上读至多8个字符长的口令,不回显用户输入的字符.
需要一个参数: 提示信息.
该子程序将提示信息显示在终端上,禁止字符回显功能,从/dev/tty读取口
令,然后再恢复字符回显功能,返回刚敲入的口令的指针.
*popen():将在(5)运行shell中介绍.
(2)/etc/passwd处理
有一组子程序可对/etc/passwd文件进行方便的存取,可对文件读取到入口
项或写新的入口项或更新等等.
*getpwuid():从/etc/passwd文件中获取指定的UID的入口项.
*getpwnam():对于指定的登录名,在/etc/passwd文件检索入口项.
以上两个子程序返回一指向passwd结构的指针,该结构定义在
/usr/include/pwd.h中,定义如下:
struct passwd {
char * pw_name; /* 登录名 */
char * pw_passwd; /* 加密后的口令 */
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/passwd并返回指向文件中第一个入口项的
指针,保持调用之间文件的打开状态.
再调用getpwent()可顺序地返回口令文件中的各入口项.
调用setpwent()把口令文件的指针重新置为文件的开始处.
使用完口令文件后调用endpwent()关闭口令文件.
*putpwent():修改或增加/etc/passwd文件中的入口项.
此子程序将入口项写到一个指定的文件中,一般是一个临时文件,直接写口
令文件是很危险的.最好在执行前做文件封锁,使两个程序不能同时写一个
文件.算法如下:
. 建立一个独立的临时文件,即/etc/passnnn,nnn是PID号.
. 建立新产生的临时文件和标准临时文件/etc/ptmp的链,若建链失败,
则为有人正在使用/etc/ptmp,等待直到/etc/ptmp可用为止或退出.
. 将/etc/passwd拷贝到/etc/ptmp,可对此文件做任何修改.
. 将/etc/passwd移到备份文件/etc/opasswd.
. 建立/etc/ptmp和/etc/passwd的链.
. 断开/etc/passnnn与/etc/ptmp的链.
注意:临时文件应建立在/etc目录,才能保证文件处于同一文件系统中,建
链才能成功,且临时文件不会不安全.此外,若新文件已存在,即便建
链的是root用户,也将失败,从而保证了一旦临时文件成功地建链后
没有人能再插进来干扰.当然,使用临时文件的程序应确保清除所有
临时文件,正确地捕捉信号.
(3)/etc/group的处理
有一组类似于前面的子程序处理/etc/group的信息,使用时必须用include
语句将/usr/include/grp.h文件加入到自己的程序中.该文件定义了group
结构,将由getgrnam(),getgrgid(),getgrent()返回group结构指针.
*getgrnam():在/etc/group文件中搜索指定的小组名,然后返回指向小组入
口项的指针.
*getgrgid():类似于前一子程序,不同的是搜索指定的GID.
*getgrent():返回group文件中的下一个入口项.
*setgrent():将group文件的文件指针恢复到文件的起点.
*endgrent():用于完成工作后,关闭group文件.
*getuid():返回调用进程的实际UID.
*getpruid():以getuid()返回的实际UID为参数,确定与实际UID相应的登录
名,或指定一UID为参数.
*getlogin():返回在终端上登录的用户的指针.
系统依次检查STDIN,STDOUT,STDERR是否与终端相联,与终端相联的标准输
入用于确定终端名,终端名用于查找列于/etc/utmp文件中的用户,该文件
由login维护,由who程序用来确认用户.
*cuserid():首先调用getlogin(),若getlogin()返回NULL指针,再调用
getpwuid(getuid()).
*以下为命令:
*logname:列出登录进终端的用户名.
*who am i:显示出运行这条命令的用户的登录名.
*id:显示实际的UID和GID(若有效的UID和GID和实际的不同时也显示有效的
UID和GID)和相应的登录名.
(4)加密子程序
1977年1月,NBS宣布一个用于美国联邦政府ADP系统的网络的标准加密法:数
据加密标准即DES用于非机密应用方面.DES一次处理64BITS的块,56位的加
密键.
*setkey(),encrypt():提供用户对DES的存取.
此两子程序都取64BITS长的字符数组,数组中的每个元素代表一个位,为0
或1.setkey()设置将按DES处理的加密键,忽略每第8位构成一个56位的加
密键.encrypt()然后加密或解密给定的64BITS长的一块,加密或解密取决
于该子程序的第二个变元,0:加密 1:解密.
*crypt():是UNIX系统中的口令加密程序,也被/usr/lib/makekey命令调用.
crypt()子程序与crypt命令无关,它与/usr/lib/makekey一样取8个字符长
的关键词,2个salt字符.关键词送给setkey(),salt字符用于混合encrypt()
中的DES算法,最终调用encrypt()重复25次加密一个相同的字符串.
返回加密后的字符串指针.
(5)运行shell
*system():运行/bin/sh执行其参数指定的命令,当指定命令完成时返回.
*popen():类似于system(),不同的是命令运行时,其标准输入或输出联到由
popen()返回的文件指针.
二者都调用fork(),exec(),popen()还调用pipe(),完成各自的工作,因而
fork()和exec()的安全方面的考虑开始起作用.

写安全的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()都启动she
ll,故可使用shell句法.如:
system("PATH=/bin:/usr/bin cd");
这样允许用户运行系统命令而不必知道要执行的命令在哪个目录中,但这种方法不能用于exe
clp(),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置加实际GI
D.
. 若不能用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)程序是对于小组和其他用户都是不可写的,存取权限的限制低于47
55(2755)将带来麻烦.只能更严格.4111(2111)将使其他人无法寻找程序中的安全漏洞.
(2)警惕外来的编码和make/install方法. 某些make/install方法不加选择地建立SUID/SGID
程序.
. 检查违背上述指导原则的SUID/SGID许可的编码.
. 检查makefile文件中可能建立SUID/SGID文件的命令.
root程序的设计
有若干个子程序可以从有效UID为0的进程中调用.许多前面提到的子程序,
当从root进程中调用时,将完成和原来不同的处理.主要是忽略了许可权限的检查.
由root用户运行的程序当然是root进程(SUID除外),因有效UID用于确定文件的存取权限,所以
从具有root的程序中,调用fork()产生的进程,也是root进程.
(1)setuid():从root进程调用setuid()时,其处理有所不同,setuid()将把有效的和实际的UI
D都置为指定的值.这个值可以是任何整型数.而对非root进程则仅能以实际UID或本进程原来
有效的UID为变量值调用setuid().
(2)setgid():在系统进程中调用setgid()时,与setuid()类似,将实际和有效的GID都改变成其
参数指定的值.
* 调用以上两个子程序时,应当注意下面几点:
. 调用一次setuid()(setgid())将同时设置有效和实际UID(GID),独立分别设置有效或实际U
ID(GID)固然很好,但无法做到这点.
. setuid()(setgid())可将有效和实际UID(GID)设置成任何整型数,其数值不必一定与/etc/
passwd(/etc/group)中用户(小组)相关联.
. 一旦程序以一个用户的UID了setuid(),该程序就不再做为root运行,也不可能再获root特权
.
(3)chown():当root进程运行chown(),chown()将不删除文件的SUID和/或SGID许可,但当非ro
ot进程运行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进程调用,分别用于安装和拆卸文件系统.这两个子程序也被mo
unt和umount命令调用,其参数基本和命令的参数相同.调用mount(),需要给出一个特别文件和
一个目录的指针,特别文件上的文件系统就将安装在该目录下,调用时还要给出一个标识选项
,指定被安装的文件系统要被读/写<0>还是仅读<1>.umount()的参数是要一个要拆卸的特别文
件的指针.
系统设计法则
不管使用何种编程语言、程序用途、和什么技巧写的,下面的法则可
以帮助你断定程序是否是bug-free的
1. 最小权限. 编制和使用最少且足够的权限去完成任务,问自己,
"软件*必需*要什么权限?",而不是"软件需要什么权限"。
2. 结构经济. 短,简单的代码产生的Bug当然比长和复杂的少,用尽
可能少的代码实现系统。
3. 完全检测. 检查访问对象的所有途径,所有调用的返回代码,和关
键点的变量值。
4. 开放设计. 不要用隐晦的方法来保证安全
5. 特权分开. 在不同的程序或函数里不同时间只给出最需要的权限。
6. 最少公用机制. 应该给用户最少的共享资源。
7. 心理可接受性. 安全控制必须容易使用,否则容易被用户绕过去不
去使用安全特性。
8. 默认的错误防护. 默认拒绝,和错误关闭
9. 代码重用. 尽可能使用以前测试过的代码
10. 不信任未知的. 所有从用户那里得到的信息,或者从外面的程序都
是可以怀疑的.
11. 在问题出现前作出预测. 在开始写程序之前确定你的程序功能和设
计可能会出现什么安全问题。
安全编程方法
1. 检查所有的命令行参数
2. 检查所有的系统调用参数和返回代码
3. 检查环境参数,不要依靠Unix环境变量
4. 确定所有的缓存都被检查过
5. 在变量的内容被拷贝到本地缓存之前对变量进行边界检查
6. 如果创建一个新文件,使用O_EXCL和O_CREATE标志来确定文件没有已经存在
7. 使用lstat()来确定文件不是一个符号连接
8. 使用下面的这些库调用: fgets(), strncpy(), strncat(), snprintf()
而不是其它类似的函数,可以说,只使用检查了长度的函数.
9. 同样的,小心的使用execve(),如果你必须衍生一个进程
10.在程序开始时显式的更改目录(chdir())到适当的地方
11.限制当程序失败时产生的core文件,core文件里有可能含有密码和其它内存状态信息.
12.如果使用临时文件,考虑使用系统调用tmpfile()或mktemp()来创建它们
(虽然很多mktemp()库调用可能有race condition的情况)
13.内部有做完整性检查的代码
14.做大量的日志记录,包括日期,时间,uid和effective uid,gid和effe
ctive gid,终端信息,pid,命令行参数,错误和主机名
15.使程序的核心尽可能小和简单
16.永远用全路径名做文件参数
17.检查用户的输入,确保只有"好"的字符
18.使用好的工具如lint
19.理解race conditions,包括死锁状态和顺序状态
20.在网络读请求的程序里设置timeouts和负荷级别的限制.
21.在网络写请求里放置timeouts
22.使用会话加密来避免会话抢劫和隐藏验证信息
23.尽可能使用chroot()设置程序环境
24.如果可能,静态连接安全程序
25.当需要主机名时使用DNS逆向解释
26.在网络服务程序里分散和限制过多的负载
27.在网络的读和写里放置适当的timeout限制
28.如果合适,防止服务程序运行超过一个以上的拷贝
不安全的编程方法
1. 防止使用在处理字符串时不检查buffer边界的函数,如gets(),
strcpy(), strcat(), sprintf(),fscanf(), scanf(), vsprintf(), realp
ath(), getopt(), getpass(), streadd(), strecpy(),和strtrns()
2. 同样,避免使用execlp()和execvp()
3. 永远不要用system()和popen()系统调用
4. 不要将文件创建文件在全部人可写的目录里
5. 通常,不要设置setuid或者setgid的shell scripts
6. 不要假想端口号码,应该用getservbyname()函数
7. 不要假设来自小数字的端口号的连接是合法和可信任的
8. 不要相信任何IP地址,如果要验证,用密码算法
9. 不要用明文方式验证信息
l0 不要常识从严重的错误中恢复,要输出详细信息然后中断
l 1 考虑使用perl -T或taintperl写setuid的perl程序
测试程序安全
1. 用cracker的方法来做软件测试:
2. 尝试使程

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功