咱们知道,Linux每一个文件的详细属性都存储在一个stat结构中,能够用stat命令查看文件的详细属性,例如文件大小,修改时间,所属用户,文件权限等。 node
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for filesystem I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
在stat结构中,st_mode字段表示了文件的模式相关属性。下图是关于该字段的详细描述,能够发现st_mode经过一个8进制数字存储文件模式: shell
S_IFMT 0170000 bit mask for the file type bit fields S_IFSOCK 0140000 socket S_IFLNK 0120000 symbolic link S_IFREG 0100000 regular file S_IFBLK 0060000 block device S_IFDIR 0040000 directory S_IFCHR 0020000 character device S_IFIFO 0010000 FIFO S_ISUID 0004000 set-user-ID bit S_ISGID 0002000 set-group-ID bit (see below) S_ISVTX 0001000 sticky bit (see below) S_IRWXU 00700 mask for file owner permissions S_IRUSR 00400 owner has read permission S_IWUSR 00200 owner has write permission S_IXUSR 00100 owner has execute permission S_IRWXG 00070 mask for group permissions S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 mask for permissions for others (not in group) S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permission S_IXOTH 00001 others have execute permission
下面一段代码将对setuid的使用做出简单说明。父进程经过fork建立一个子进程,而后父进程和子进程将分别打印设置用户ID,最后父进程将会尝试建立一个新的文件。 安全
#include <stdlib.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main(int argc, char *argv[]) { int fd; pid_t pid; if ((pid = fork()) < 0) { fprintf(stderr, "fork error\n"); exit(1); } else if (pid == 0) { printf("child real user id: %d\n", getuid()); printf("child effective user id: %d\n", geteuid()); } else { sleep(1); printf("parrent real user id: %d\n", getuid()); printf("parrent effective user id: %d\n", geteuid()); exit(0); } if ((fd = open(argv[1], O_CREAT | O_RDWR)) < 0) { fprintf(stderr, "open error\n"); fprintf(stderr, "error: %s\n", strerror(errno)); } close(fd); return 0; }
编译并执行程序,结果以下: bash
roo@roose:~$ ls -l a.out -rwxrwxr-x 1 roo roo 9174 Jan 29 17:17 a.out* roo@roose:~$ ./a.out /usr/local/testfile child real user id: 1000 child effective user id: 1000 parrent real user id: 1000 parrent effective user id: 1000 open error error: Permission denied
根据打印的信息,能够发现 app
下面修改a.out的属性,设置文件拥有者为root用户,添加setuid socket
roo@roose:$ sudo chown root:root a.out roo@roose:$ sudo chmod 4775 a.out roo@roose:$ ls -l a.out -rwsrwxr-x 1 root root 9174 Jan 29 17:17 a.out
而后从新执行程序 ide
roo@roose:~$ ./a.out /usr/local/testfile child real user id: 1000 child effective user id: 0 parrent real user id: 1000 parrent effective user id: 0
根据打印的信息,能够发现: ui
尝试执行设置了setuid的shell脚本,建立脚本文件test.sh内容以下: this
#!/bin/bash sleep 10 touch /usr/local/testfile
roo@roose:~$ sudo chown root:root test.sh roo@roose:~$ sudo chmod 4775 test.sh roo@roose:~$ ./test.sh & [1] 54280 roo@roose:~$ ps -eo uid,euid,command | grep test.sh 1000 1000 /bin/bash ./test.sh 1000 1000 grep --color=auto test.sh roo@roose:~$ touch: cannot touch ‘/usr/local/testfile’: Permission denied
一样对test.sh赋予相关权限并执行,结果却和上面C生成的可执行文件不太同样: spa
为何已经设置了setuid却没有效果呢?经过查找相关资料,发现出现该问题的缘由是:
出于安全问题,Linux会忽略任何编译器文件的设置用户位。
详细说明以下(博主太懒,有空再翻译)
http://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts
Linux ignores the setuid¹ bit on all interpreted executables (i.e. executables starting with a #! line). The comp.unix.questions FAQ explains the security problems with setuid shell scripts. These problems are of two kinds: shebang-related and shell-related; I go into more details below.
If you don't care about security and want to allow setuid scripts, under Linux, you'll need to patch the kernel. As of 3.x kernels, I think you need to add a call to install_exec_creds in the load_script function, before the call to open_exec, but I haven't tested.
Setuid interpreters
Let's assume you've managed to make your program run as root, either because your OS supports setuid shebang or because you've used a native binary wrapper (such as sudo). Have you opened a security hole? Maybe. The issue here is not about interpreted vs compiled programs. The issue is whether your runtime system behaves safely if executed with privileges. Any dynamically linked native binary executable is in a way interpreted by the dynamic loader (e.g. /lib/ld.so), which loads the dynamic libraries required by the program. On many unices, you can configure the search path for dynamic libraries through the environment (LD_LIBRARY_PATH is a common name for the environment variable), and even load additional libraries into all executed binaries (LD_PRELOAD). The invoker of the program can execute arbitrary code in that program's context by placing a specially-crafted libc.so in $LD_LIBRARY_PATH (amongst other tactics). All sane systems ignore the LD_* variables in setuid executables. In shells such as sh, csh and derivatives, environment variables automatically become shell parameters. Through parameters such as PATH, IFS, and many more, the invoker of the script has many opportunities to execute arbitrary code in the shell scripts's context. Some shells set these variables to sane defaults if they detect that the script has been invoked with privileges, but I don't know that there is any particular implementation that I would trust. Most runtime environments (whether native, bytecode or interpreted) have similar features. Few take special precautions in setuid executables, though the ones that run native code often don't do anything fancier than dynamic linking (which does take precautions). Perl is a notable exception. It explicitly supports setuid scripts in a secure way. In fact, your script can run setuid even if your OS ignored the setuid bit on scripts. This is because perl ships with a setuid root helper that performs the necessary checks and reinvokes the interpreter on the desired scripts with the desired privileges. This is explained in the perlsec manual. It used to be that setuid perl scripts needed #!/usr/bin/suidperl -wT instead of #!/usr/bin/perl -wT, but on most modern systems, #!/usr/bin/perl -wT is sufficient. Note that using a native binary wrapper does nothing in itself to prevent these problems. In fact, it can make the situation worse, because it might prevent your runtime environment from detecting that it is invoked with privileges and bypassing its runtime configurability. A native binary wrapper can make a shell script safe if the wrapper sanitizes the environment. The script must take care not to make too many assumptions (e.g. about the current directory) but this goes. You can use sudo for this provided that it's set up to sanitize the environment. Blacklisting variables is error-prone, so always whitelist. With sudo, make sure that the env_reset option is turned on, that setenv is off, and that env_file and env_keep only contain innocuous variables. TL,DR: Setuid shebang is insecure but usually ignored. If you run a program with privileges (either through sudo or setuid), write native code or perl, or start the program with a wrapper that sanitizes the environment (such as sudo with the env_reset option).