如何使脚本的set-user-id位起作用

来源:互联网 发布:网络流行词的由来 编辑:程序博客网 时间:2024/06/04 21:58

 一:前记

以下讨论围绕的一个问题:为什么C程序可以通过set-user-id位提权,但shell脚本不可以。

 

文章会比较罗嗦,其实只要了解unix程序的fork/exec并结合shell的执行过程,即可以明白。结论其实很简单就在最后,二句话。不耐烦的可以直接拖到最后边看呵。而且估计答案是很让人失望的。

 

写本文的目的主要将我对这个问题的认识的过程记录了下来,包括长期以来的一个误解。为解决这个问题,重翻了apue4.4/8.11/8.12/8.13等章节,并同时阅读了bash/set mannual手册页,终于对这个问题的原因和解决方法有所了解,余留的问题涉及到内核exec的执行了,有空再继续深入

 

执行环境:

系统:Red Hat Enterprise Linux Server release 5.1 (Tikanga)

内核:2.6.18-53.el5xen

bash版本:GNU bash, version 3.1.17(1)-release (i686-redhat-linux-gnu)

 

 

二:问题显现

 

考虑以下这个场景

有一个文件名:

-rwx------ 1 root root   11 05-04 18:40 test.txt

文件所有者为root用户,文件对其他用户不可读写,我们希望通过某种型式(接口),比如提供某个程序,

通过该程序进行文件读写(这里先考虑读),而不是直接将文件读写权限附给其他用户

 

比较典型的,像/etc/passwd文件那样,我们希望能通过设置passwd程序的set-id-root方式,使其他用户在运行该程序里,可以临时获得root的权限。

 

当然所有例子中的 set-id-user里的user并不局限于root,出于安全考虑,一般的服务程序也不直接使用root进行权限限制,而是另行创建一个用户,毕竟root用户太过行特殊,并且在进行setuid(id)系统调好用过程中,如果当前euidroot,而目标idroot的话,root用户将会把uid,euid,suid一同降权为该id,除非再执行一个set-id-root程序,否则进程无法再获得root权限,这个变化在当前进程中是不可恢复的。具体的例子可以参考Unix Incompatibility Notes: UID Setting Functions http://unixpapa.com/incnote/setuid.html)而实际上unix相关系统为考虑到形形色色的需求,相关的系统调用包括了setuid,seteuid,setreuid,setresuid等等。具体的可以参考相关的manual。下边仅以seteuid进行讨论

 

于是乎开始写程序,首先考虑用shell程序

于是我们写下下边这个脚本

#/bin/bash

cat  test.txt

脚本名:test.sh,所有者root,

-rwsr-xr-x 1 root root   26 05-06 02:45 test.sh

 

然后我们通过其他用户调用这个脚本:

$ sh test.sh

cat: test.txt: Permission denied

发现了问题:尽管附给了程序(脚本)set-id-root位了,但仍然没有读取test.txt文件的权限

 

 

在脚本不行的情况下,我试着用c

 

  

 

很简单的一段代码,用root用户编译后没设置set-uid-root之前与之后的执行情况

[bobo@localhost c]$ l a.out

-rwx--x--x 1 root root 5000 05-08 11:00 a.out

[bobo@localhost c]$ ./a.out

fopen faild

[bobo@localhost c]$ l a.out

-rws--x--x 1 root root 5000 05-08 11:00 a.out

[bobo@localhost c]$ ./a.out

12345678

 

 

 

 

三:setuid()与一个误解的澄清

在发现上边的情况之后,加上以前的一个误解,让我在解决问题的过程中多走了个弯路。

 

这个误解是这样的:以前在某个网址(Unix/Linux OS: real and effective user id, binary image, controlled access http://en.allexperts.com/q/Unix-Linux-OS-1064/real-effective-user-id.htm

上看到passwd的执行过程中uideuid的转变,最后有这么一段话:Note one other thing, setting a Set-UID on a executable file is not enough to make it run as privileged process. The program itself must make a system call.意思基本上是说对于程序设置的set-uid并不能使进程拥有相应权限,还要进行一次系统调用。

 

不管这句话的系统调用是指exec还是setuid,因为exec是无论如何都会调用的,其后好长一段时间里我一直存在的误解,即程序里没需要进行系统调用,通过系统调用更改了euid后才能有相关文件的操作权限。这个理解后来证明是错的,或说是不完整的。但至少一定程序上解释了为什么shell设置set-id位无效了:c程序里的设置是在当前进程里的,而shell并没有提供如setuid类似的命令可以调用用来更改进程id,更何况即使用类似的命令,一旦shell调用起来,也是在创建的子进程里有效,不会变更到父进程。

 

 

这个误解,直到前不久,群里再讨论set-id问题的时候,有人提到c程序只需要设置set-id就可以起做用时,我争论说:还需要内部进行一次setuid系统调用!,事后我为了验证,将上边的程序修改了下

  

去掉了seteuid一句,同样编译连接,设置同样的权限与set-id-root,运行,发现仍然是可以生效的。

这就推翻了上边那段话,说明了,c程序的set-id位生效,并不需要额外的系统调用

 

三:拨开云雾

一切的迷团都是表象,要看清表象就需要了解事物的本质。

目前的问题在于

1:用户在执行set-id-root时,在什么情况下进行的权限变更的

2shell程序为什么不能产生c程序这样的效果

 

这个时候对内核进程处理并不熟悉的我,只能求助于apue了。

 

4.4节关于文件的set-id位介绍如下

"when this file is executed, set the effective user ID of the process to be the owner of the file (st_uid)."

 即在文件执行时设置的权限

 

8.3上提到fork子进程继续了父进程的环境

there are numerous other properties of the parent that are inherited by the child

其中包括

Real user ID, real group ID, effective user ID, effective group ID The set-user-ID and set-group-ID flags

 

8.10提到程序fork子进程后执行新程序需要调用exec,继续当胆子函数的执行和环境变量,但在euid上可能根据文件进行了更改

 

When a process wants to execute a different program. This is common for shells. In this case, the child does an exec (which we describe in Section 8.10) right after it returns from the fork.

 

 

Note that the real user ID and the real group ID remain the same across theex ec, but the effective IDs can change, depending on the status of the set-user-ID and the set- group-ID bits for the program file that is executed. If the set-user-ID bit is set for the new program, the effective user ID becomes the owner ID of the program file. Otherwise, the effective user ID is not changed (it's not set to the real user ID). The group ID is handled in the same way.

 

 

并在8.11 介绍了setuid,seteuid等函数的调用。

 

8.12上介绍了脚本语言的执行情况

The recognition

of these files is done within the kernel as part of processing the exec system call. The actual file that gets executed by the kernel is not the interpreter file, but the file specified by the pathname on the first line of the interpreter file.

 

 

到此,二个问题都解答出来了

1:用户在执行set-id-root时,在什么情况下进行的权限变更的

答:执行程序过程中,创建进程为fork, 实际执行程序由exec来执行,

fork继续承了父进程的环境

实际上程序的set-id位是在exec时进行判断并做相应euid更改的

 

2shell程序为什么不能产生c程序这样的效果

 

答:实际上内核进行exec的并不是这个脚本程序,而是实际上的内部程序,而这程序一般由第一行指定(默认一般为/bin/sh)

比如,我们写个awk脚本,只需要二行

[root@localhost c]# cat cat.awk

#!/bin/awk -f

1;

这个脚本实现实际上就是找印文件

脚本命名为:cat.awk

 

我们用root用户创建,并设置set-id权限

-rwsr-xr-x 1 root root   18 May  8 12:42 cat.awk

执行的结果仍然是权限不足

[bobo@localhost c]$ ./cat.awk test.txt

awk: ./cat.awk:4: fatal: cannot open file `test.txt' for reading (Permission denied)

 

因为实际上exec的读文件程序并不是cat.awk脚本,而是awk程序(当然,awk也占有一个进程,这个后边会说到)

我们修改awk的程序权限

-rwsr-xr-x 1 root root 320416 Jan 15  2007 /bin/gawk

执行程序

[bobo@localhost c]$ ./cat.awk test.txt

1234567890

 

即可实现。(/bin/awk在环境下为/bin/gawk的软链接)

 

 

 

 

四:未完成的事

 

这边还是有个问题,为什么设置脚本就不起作用呢,用户不管是在控制台还是在终端,敲入命令,对于c程序和脚本表面上都是一样的。处理上,也是从当前进程上fork一个子进程去执行。

脚本的执行不是也是一个进程吗?

 

确实,两者都是同样执行一个新进程,但c程序执行程序是该程序本身,而脚本的执行程序由第一行决定,或者默认为shell(bash),

 

那么实质性的问题就在于bash shell的处理了。不管文本是否有set-id,创建的进程uid/euid实际上继承于父进程并且受shell程序影响,以bash为例,程序所在为:/bin/bash

 

设置脚本的set-id位实际上只是设置了一个输入文件,是没有意义的,需要使shell可以产生效果,那就去设置/bin/bashset-id

 

Chmod +s /bin/bash

 

并且要使用-p参数

Set -p

 

因为当shell设置了set-id位时,若不打开-p开关,shell(bash v2版本以上)在执行exec时,会自动将euid更改为实际用户id,使作用关闭,我们看下bash里的函数调用即可明白

[bobo@localhost c]$ strings /bin/bash |grep set|grep id

setuid

setpgid

setgid

set_ppid

 

另外:在bash manualIVOCATION一节最后一段如此描述:

       If  the  shell  is started with the effective user (group) id not equal to the real user (group) id, and the -p  option is not supplied, no startup files are read, shell functions are not inherited from the environment,  the SHELLOPTS  variable, if it appears in the environment, is ignored, and the effective user id is set to the real  user id.  If the -p option is supplied at invocation, the startup behavior is the same, but the effective  user        id is not reset.

 

set mannual中关于-p选项

              -p      Turn  on  privileged  mode.  In this mode, the $ENV and $BASH_ENV files are not processed, shell  functions are not inherited from the environment, and the SHELLOPTS variable, if it  appears  in    the  environment,  is  ignored.   If the shell is started with the effective user (group) id not   equal to the real user (group) id, and the -p option is not supplied, these  actions  are  taken  and  the effective user id is set to the real user id.  If the -p option is supplied at startup, the effective user id is not reset.  Turning this option off causes the effective user and group   ids to be set to the real user and group ids.

 

 

 

综上所述,如何让脚本的set-id位起作用,这个问题其实在明白上边讨论的内容后答案已经明了了,脚本的set-id位跟普通文件的set-id位一样,是无意义的。只有实际执行程序才有作用。如bash,awk,perl

 

如前言所说,若想要进一步了解进程相关的问题,那就要深入内核去分析exec程序了。

 

原创粉丝点击