需求分析:
这里先要说明的是,这一篇不是QT系列的文章,而是关于Web的,之所以要写这篇,是因为以前做Web相关开发的时候,经常涉及到与linux底层命令打交道,比如说创建一个目录,删除一个目录,或者是执行一个自定义的脚本。关于PHP如何调用、执行Linux的底层命令,以前也研究过,基本上实现了自己需要的功能,但是有些地方一直没有弄明白。今天又偶然碰到了,趁着这个机会向大家描述一下一步一步应该如何实现,并最后附上相关C代码。
原理实现:
首先,一般搭建的Web站点都是采用Apache或Nginx,因此当使用php执行linux的命令的时候,在linux端体现出的时Apache或Nginx用户的身份来执行的。通常情况下基于安全考虑,Apache或Nginx在linux端的默认用户是不具有很高的权限的,比如删除、创建等,因此我们必须通过一种方式,为其赋予一定的权限。在我以前写过的文章中,我采用了一种方法,即将Apache或Nginx的默认用户修改了,给那个用户赋予了很高的权限,虽然达到了我的目的,但是恰恰造成了最大的隐患,即:web服务器默认用户权限设置过大,很容易受到来自外界的攻击,甚至不用外界,我自己在php端执行一个命令能将我整个站点删除掉,而在linux端对此基本没有任何防御能力。基于此,我们提出一种方式,在linux端不修改web服务器默认用户的权限,而是当执行命令时由我们来自己控制这条命令应当具有何种用户的执行权限,或者root用户,或者普通用户完全由我们传递的参数决定。
其次,基于上面的阐述,基本实现思路为:接受php传递来的username和passwd的参数,在linux端新建一个进程,然后将该进程模拟为一个用户的身份,即设置该进程的用户实际、有效的用户ID和用户组ID,然后再执行某条命令,此时执行该命令的过程就好像username用户本身执行那条命令一样。username能执行的有效命令只能在自己的职权范围之内,比如:如果username是普通用户,则它无法通过执行命令删除其他用户或root用户的文件。这样,在一定程度上实现了安全的可控性。
最后,根据上面提出的改变某个进程的实际、有效用户ID和用户组ID需要用到setuid()和setgid(),而关于这两个函数这里还有需要注意的地方,如果执行它的是特权用户,即root,则它可以任意设置自己的uid和gid,即可以模拟任意用户;而如果执行它的时普通用户,则它只能设置自己的uid和gid,而不能设置为其它用户的,即无法模拟其它用户。为了解决这个问题就要用到linux的一些特性,即设置文件的用户标记位:s,使文件在执行时具有文件所有者的权限。这样,Apache或Nginx默认用户执行这个文件的时候就类似于该文件的所有者在执行它,而我们利用root用户创建该文件,就相当于root用户在执行该文件,此时setuid和setgid就可以将进程模拟为任何用户的身份了。
实现代码:
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <pwd.h>
- #include <math.h>
- #include <time.h>
- //Usage: exefile command work_directory username password
- int main(int argc, char *argv[])
- {
- char *username = NULL;
- char *password = NULL;
- char *command = NULL;
- char *workdir = NULL;
- struct passwd *stpasswd = NULL;
- if(argc == 5)
- {
- command = argv[1];
- workdir = argv[2]; //work directory
- username = argv[3];
- password = argv[4];
- }
- else
- {
- return 1;
- }
- //printf("username = %s\n",username);
- //printf("password = %s\n",password);
- //printf("workdir = %s\n",workdir);
- //printf("command = %s\n",command);
- int result = 0;//auth(username,password); //Ensure the user is a legel user in the system
- if(result == 0) //auth successfully
- {
- int kidstatus, deadpid;
- //! fork()克隆出一个完全相同的进程出来;它会复制当前进程的所有变量,就好像一个进程克隆出来另一个一样
- //! fork()函数的返回值有三种情况:=0代表克隆出来的那个进程 >0代表“父”进程 <0 执行出错
- //! 这种情况一般就是fork出来新的进程,【可选:进行身份切换】,然后利用它来执行execlp(),执行相关的命令
- pid_t kidpid = fork();
- if (kidpid == -1)
- {
- printf("fork error");
- return 1;
- }
- if (kidpid == 0)
- {
- //! getpwnam():获取用户登录相关的信息
- //! 头文件:#include <pwd.h> #include <sys/types.h>
- stpasswd = getpwnam(username);
- //! setgid():设置实际用户组ID和有效用户组ID
- //! setuid():设置实际用户ID和有效用户ID
- //! chdir() : 修改当前目录
- //! 注意:如果是一个非特权用户,则它执行setgid和setuid只能设置为自己的gid和uid,而不能设置其它任何数值
- //! 如果是一个特权用户(即:拥有root权限),则可以利用setgid和setuid设置为任意数值,这也就是为什么最终
- //! 编译出来的文件要通过:chmod u+s 设置特权位
- setgid( (int)(stpasswd->pw_gid)); //Set current usergroup
- setuid( (int)(stpasswd->pw_uid)); //Set current user
- chdir(workdir); //change work directory
- //! int execlp(const char *file, const char *arg, ..., (char *)0);
- //! execlp()会从PATH环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后
- //! 将第二个参数以后的参数当作该文件的argv[0], argv[1] ...,最后一个参数必须用空指针(NULL)结束
- //! 下面的命令就是:/bin/bash -c command执行
- int rv = execlp("/bin/bash", "/bin/bash", "-c", command, NULL);
- fflush(stdout);
- return rv;
- }
- //! we only get here if we're the parent process.
- //! waitpid()阻塞等待子进程结束
- //! 函数原型:
- deadpid = waitpid(kidpid, &kidstatus, 0);
- if (deadpid == -1)
- {
- printf("error to fork a process!");
- return 1;
- }
- else
- {
- return 0;
- }
- }
- else
- {
- printf("Authenticate failed\n");
- return 1;
- }
- }
最后,利用gcc编译该文件:gcc test_exec.c -o test_exec;编译完成后为它赋予权限:chmod 777 test_exec;然后设置用户标记位:s, chmod u+s test_exec
执行测试:
1. 利用root用户创建一个del_test文件,然后切换到普通用户,测试能否删除;然后用我们的命令模拟root用户看能否删除
2. 利用root用户创建一个del_test的文件,切换到zhangsan用户,利用命令模拟lisi用户,看能够删除
通过以上,我们可以看出,利用该命令我们可以模拟任何用户的身份,相当于任何权限都是由你自己控制,在一定程度上保证了安全性。
总结:
对似懂非懂的知识点一定要明白其原理,不然始终不知道应该如何来做。加油!这段时间的网盘开发没有涉及太多挑战性的东西,期待UDT的研究。
参考:http://blog.****.net/houqd2012/article/details/34165381