背景知识
Linux模式
用户User:文件的所有者
群Group:文件组的成员
其他Other: 不属于任何用户或组的用户
关于对文件权限分为三种,分别用三个字母来表示。分别是r(read), w(write), x(execute)。
例如drwxrwxrwx
,d代表文件。三个rwx的意思是,用户,群,其他都拥有读写编辑的权限。
影子文件
在Linux中,系统使用/etc/shadow
文件来保存用户的密码。通过查看/etc/shadow的访问权限可以知道,文件属于root用户,-rw-r-----
代表只有root有读写的权利。
那么普通用户怎么修改自己的密码呢?
UID是什么
Linux中所有的进程都拥有两个ID,
Real UID(RUID):哪个用户运行了这个程序或文件,换句话说是进程的真正拥有者。
Effective UID(EUID):操作系统识别一个进程的权限。
当程序被正常执行时RUID = EUID,此时都是所有者的ID。当Set-UID执行时RUID != EUID。RUID仍然是用户ID,但是EUID时程序所有者的ID。如果程序被root拥有,那么用户就可以以root权限运行程序。
关于Set-UID的例子
首先复制cat文件,新建一个mycat文件用于观察。第二步,将mycat文件的所属权变成root。用 ls -l 命令确认一下mycat的权限。最后,在普通用户下运行程序,和预想的一样这里没有足够权限来打开影子文件。
我们修改一下mycat文件的特权,4755是指用户执行文件时可以有拥有者的权限。改变权限之后再一次执行可以发现,程序可以正常使用并打印了影子文件的信息。
最后将mycat所有者改为seed,此时文件不拥有root权限所以也无法打印影子文件的内容。
Set-UID的弱点
system函数
按照资料编写下面这个catcall的程序,能力一是拥有cat的技能,能力二是可以运行并读取输入的命令。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]){
char *cat = "/bin/cat";
if(argc < 2){
printf("Please type a file name.\n");
return 1;
}
char *command = malloc(strlen(cat) + strlen(argv[1]) + 2);
sprintf(command, "%s %s", cat, argv[1]);
system(command);
return 0;
}
编译并且给予权限后运行。方框这里出现了一点问题,原本是可以输出内容的,但是不知道是什么原因在我虚拟机上没办法实现能力一。这里直接尝试第二个能力,向两个%s中分别放入aa和/bin/sh字符。画面出现箭头所指情况,输入id后发现正常的输出,这说明我们成功的利用set uid的漏洞拿到了shell。
execve函数
重新用execve
函数代替system
函数写一个程序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char* argv[]){
char *v[3];
if(argc < 2){
printf("Please type a file name.\n");
return 1;
}
v[0] = "/bin/sh";
v[1] = argv[1];
v[2] = 0;
execve(v[0], v, 0);
return 0;
}
还是以相同的方式编译以及运行程序,这里发现程序成功运行。再用同样的方式试试看能不能拿到shell。
可以发现execve
函数不像system
函数一样会将数据当作代码运行。
补充
system()和execve()函数的区别是system函数是调用shell直接调用命令,相当于直接在命令行中输入命令。并且是两个进程同时进行,在一条进程结束后会返回下一条进程。但是execve函数不同,使用execve调用的进程会覆盖进程导致无法返回原进程。
权限泄露
某些情况,特权程序在执行过程中会自我降级。例如,有一个Set-UID特权的su程序允许切换用户。当程序运行时EUID是root,RUID是user1,验证过密码之后EUID和RUID通过自我降级变成为user2。但是因为程序在自我降级的时候可能没有清理特权,所以有权限泄露的情况存在。
练习
Task 1: Invoking External Programs Using system() versus execve()
STEP1
再练习之前将/bin/sh 和 /bin/zsh相连接
$ sudo ln -sf /bin/zsh /bin/sh
练习之后记得改回来
$ sudo ln -sf /bin/dash /bin/sh
STEP2
编译并直接运行程序,目标是以普通用户seed的权限利用程序漏洞获得shell。直接运行程序打印影子文件,发现权限不够无法打印。
再一次利用system的漏洞,输入/bin/sh可以看见在终端上打印了程序的代码,接着往下看
攻击成功,获得了shell。
优点
system
函数可以直接调用shell,无法分离数据和命令易被攻击。execve
函数可以做到分离数据和命令,因此安全性高于 system
函数。
重点在于分开代码和数据!
Task 2: Capability Leak
root用户状态下在/etc下建立zzz文件输入“This is import file!”。用ls -l
可以查看各用户对于文件的权限,可以知道只有root权限下有对程序进行读写的能力。其他权限只有读的权限。
将下面代码编辑成task2.c文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
void main()
{
int fd;
fd = open("/etc/zzz", O_RDWR | O_APPEND); /* 打开zzz文件 */
if(fd == -1){ /* 打开失败的情况 */
printf("Cannot open /etc/zzz file. \n ");
exit(0);
}
sleep(1); /* 暂停程序一秒 */
setuid(getuid()); /* getuid()返回RUID */
if(fork()){
close(fd);
exit(0);
}else{
write(fd, "Malicious Data\n", 15);
close(fd);
}
编译之后,将程序设置成Set-UID 之后运行,可以观察到程序因为sleep(1)暂停了一秒。
程序执行结束后重新查看zzz文件的内容,发现内容被修改了。
分析
看一下task2程序,程序先利用setuid
命令将程序降级之后尝试对文件进行修改。正常情况下zzz文件只有root权限才能读写,这里已经没有root权限的task2程序是不可以修改的。这里就是出现了权限泄露的问题,才让程序能够继续修改zzz文件。尽管取消了root权限,但是系统还没有收回特权导致程序可以修改原本不能操作的文件。
知道弱点之后我们可以修改代码来防止权限泄露,修改的方式也很简单。先对程序进行降级,再打开文件就可以规避问题。
修改代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
void main()
{
int fd;
setuid(getuid()); /* getuid()返回RUID 换到前面 */
fd = open("/etc/zzz", O_RDWR | O_APPEND); /* 打开zzz文件 */
if(fd == -1){ /* 打开失败的情况 */
printf("Cannot open /etc/zzz file. \n ");
exit(0);
}
sleep(1); /* 暂停程序一秒 */
if(fork()){
close(fd);
exit(0);
}else{
write(fd, "Malicious Data\n", 15);
close(fd);
}
重新编译之后运行,这时候程序已经没有权限对zzz文件进行修改了。