【软件安全】实验1——环境变量与 Set-UID 实验

Task 1:配置环境变量

  1. 使用printenvenv指令来打印环境变量:


​ 如果只想打印特定的环境变量,如PWD变量,可以使用printenv PWD或者env | grep PWD

  1. 使用exportunset来设置或者取消环境变量
  • 使用export设置环境变量:

​ 比如现在我使用export设置一个环境变量MY_VAR的值为softwaresecurity

​ 可以使用echo $MY_VAR打印出这个环境变量的值。

  • 使用unset取消环境变量:

​ 取消变量MY_VAR

Task 2:从父进程向子进程传递环境变量

  1. 编译myprintenv.c并运行,将输出结果打印到文件output1.txt中。

  1. 注释掉子进程中的printenv(),并取消注释父进程的printenv(),再次编译并打印输出到文件output2.txt

  1. 使用diff命令比较两个文件的差异。

结论:由于我在不同的窗口下运行的a.outb.out,因此父子进程只有编译成的可执行文件名称命令行窗口这两个环境变量不同,其余的环境变量都是相同的。结论是子进程在继承父进程的环境变量时,除了文件名和输出窗口存在差异以外,其他的环境变量都是相同的。

Task 3:环境变量和execve()

  1. 编译并运行myenv.c

发现输出为空。

  1. 修改execve()函数为execve("/usr/bin/env",argv,environ);

发现打印出了当前进程的环境变量。

  1. 结论:

    execve()函数的原型是:

    int execve(const char *pathname, char *const argv[], char *const envp[]);
    • pathname: 要执行的程序的路径。
    • argv: 参数数组,以 NULL 结尾,包含传递给程序的命令行参数。
    • envp: 环境变量数组,也以 NULL 结尾。

    新程序通过execve()函数的第三个参数传递的environ变量来获取环境变量。

Task 4:环境变量和system()

编译并运行如下代码:

#include <stdio.h>
#include <stdlib.h> int main()
{
system("/usr/bin/env");
return 0;
}

我们使用man system查看函数的手册:

可以看到system()函数是通过创建一个子进程,执行execl("/bin/sh", "sh", "-c", command, (char *) NULL);,调用进程的环境变量会传递给新程序/bin/sh

Task 5:环境变量和Set-UID程序

  1. 编写以下程序打印该进程所有的环境变量:
#include <stdio.h>
#include <stdlib.h> extern char **environ;
void main()
{
int i = 0;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
}
  1. 编译上述程序得到 foo,将其所有者更改为 root,并使其成为一个 Set-UID 程序
// Asssume the program’s name is foo
$ sudo chown root foo
$ sudo chmod 4755 foo

查看一下foo的权限,发现所有者更改为了root。

  1. 设置以下环境变量:
  • PATH
  • LD_LIBRARY_PATH
  • MY_NAME

然后运行foo并查看这些环境变量的值

发现只有在父进程中设置的PATHMY_NAME的环境变量进入子进程,而LD_LIBRARY_PATH这个环境变量没有进入子进程。

  1. 原因:

LD_LIBRARY_PATH这个环境变量设置的是动态链接器的地址,由于动态链接器的保护机制,虽然在一个root权限的程序下创建子进程并继承父进程的环境变量,但由于我们是在普通用户下修改的LD_LIBRARY_PATH这个环境变量,所以是无法在子进程中生效的,而PATHMY_NAME则没有这种保护机制,因此可以被成功设置。

Task 6:PATH环境变量和Set-UID程序

先使用以下命令将bin/sh链接到bin/zsh,以规避bin/dash阻止Set-UID程序使用特权执行的策略。

sudo ln -sf /bin/zsh /bin/sh

然后编写LS.c文件,如下所示:

#include<stdio.h>
#include<stdlib.h>
int main(){
system("ls");
return 0;
}

然后编译,并设置为Set-UID程序:

可以看出,编译出来的LS文件确实执行了system("ls")的操作,更改后的文件所有者确实变成了root

现在我们在普通用户下设置PATH环境变量,使用export PATH=/home/seed:$PATH/home/seed 添加到环境变量的开头:

然后我们在/home/seed下编写我们的恶意代码。

// hack.c
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
extern char **environ;
int main(){
uid_t euid = geteuid(); //获取执行恶意代码的进程的euid
printf("euid=%d\n", euid);
printf("You have been hacked!!!!\n");
return 0;
}

然后编译并命名成ls

gcc hack.c -o ls

然后再执行我们的LS文件:

发现可以使用Set-UID程序运行我们的恶意代码,并且根据system("id")的结果来看:euid=0表示当前进程具有root权限,表明恶意代码是以root权限运行的。

Task 7:LD_PRELOAD环境变量和Set-UID程序

  1. 观察环境变量在运行普通程序时如何影响动态加载器/链接器的行为,首先要进行如下配置:

    1. 构建一个动态链接库,命名为mylib.c,里面基本上覆盖了libc里的sleep()函数:
    #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. 编译该程序:
    gcc -fPIC -g -c mylib.c
    gcc -shared -o libmylib.so.1.0.1 mylib.o -lc
    1. 设置LD_PRELOAD环境变量的值:
    export LD_PRELOAD=./libmylib.so.1.0.1
    1. 编译下面的程序myprog.c
    /* myprog.c */
    #include <unistd.h>
    int main()
    {
    sleep(1);
    return 0;
    }
  2. 完成上述操作后,请在以下条件下运行 myprog,观察会发生什么。

    • 使 myprog 为一个普通程序,以普通用户身份执行它。

    发现执行的是我们编写的sleep函数。

    • 使 myprog 为一个 Set-UID 特权程序,以普通用户身份执行它。

    发现等待了一秒后,没有输出,说明执行的是libc中的sleep()函数。

    • 使 myprog 为一个 Set-UID 特权程序,在 root 下重新设置 LD_PRELOAD 环境变量,并执行它。

    发现执行的是我们编写的sleep函数。

    • 使myprog成为一个Set_UID user1程序,在另一个用户帐户(非root用户)中再次改变LD_PRELOAD环境变量并运行它

    发现等待了一秒后,没有输出,说明执行的是libc中的sleep()函数。

  3. 设计一个实验来找出导致这些差异的原因,并解释为什么第二步的行为不同。

修改一下myprog.c,打印这个程序运行时的进程的uideuid以及LD_PRELOAD环境变量的值,如下所示:

/* myprog.c */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main()
{
sleep(1);
uid_t uid = getuid();
printf("uid=%d(%s) ", uid, getenv("USER"));
uid_t euid = geteuid();
printf("euid=%d\n", euid);
char *preload = getenv("LD_PRELOAD");
printf("LD_PRELOAD: %s\n", preload);
return 0;
}

然后编写一个shell脚本,用于测试四种情况的输出以及当前进程的id,如下所示:

#test.sh
echo "seed,run in seed:"
sudo chown seed myprog
sudo chmod 4755 myprog
export LD_PRELOAD=./libmylib.so.1.0.1
./myprog echo "root,run in seed:"
sudo chown root myprog
sudo chmod 4755 myprog
./myprog echo "root,run in root:"
sudo su <<EOF
export LD_PRELOAD=./libmylib.so.1.0.1
./myprog
EOF echo "user1,run in seed:"
sudo chown user1 myprog
sudo chmod 4755 myprog
export LD_PRELOAD=./libmylib.so.1.0.1
./myprog

这个脚本可以自动化测试四种情况下的sleep()函数的执行情况以及打印当前进程的id,运行结果如下:

我们发现:

  • myprog为一个普通程序,以普通用户身份执行它时,其uid为seed,euid也为seed,LD_PRELOAD环境变量继承了父进程的,并且执行的是我们编写的sleep函数。

  • myprog为一个Set-UID程序时,以普通用户身份执行它时,其uid为seed,euid为root,LD_PRELOAD环境变量没有继承父进程的,并且执行的是libc的sleep函数。

  • myprog为一个Set-UID程序时,以root用户身份执行它时,其uid为root,euid为root,LD_PRELOAD环境变量继承了父进程的,并且执行的是我们编写的sleep函数。

  • myprog为一个Set-UID user1程序时,以普通用户身份执行它时,其uid为seed,euid为user1,LD_PRELOAD环境变量没有继承父进程的,并且执行的是libc的sleep函数。

如下表所示:

程序类型 执行用户 uid euid LD_PRELOAD环境变量 执行的sleep函数
普通程序 seed seed seed 继承父进程 我们编写的
Set-UID程序 seed seed root 没有继承父进程 libc的
Set-UID程序 root root root 继承父进程 我们编写的
Set-UID user1程序 seed seed user1 没有继承父进程 libc的

结论:

当一个进程的uideuid一致时,子进程才会继承父进程的环境变量,才会执行我们编写的sleep()函数,第二步行为不同的原因是因为它们的uideuid的一致/不一致会导致子进程继承/不继承LD_PRELOAD环境变量,从而导致了sleep()函数的不同。

Task 8:使用 system() 与 execve() 调用外部程序的对比

编写并编译catcall.c,如下所示:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> int main(int argc, char *argv[])
{
char *v[3];
char *command; if(argc < 2) {
printf("Please type a file name.\n");
return 1;
} v[0] = "/bin/cat"; v[1] = argv[1]; v[2] = NULL; command = malloc(strlen(v[0]) + strlen(v[1]) + 2);
sprintf(command , "%s %s", v[0], v[1]); system(command);
// execve(v[0], v, NULL); return 0 ;
}

这个程序调用了system()函数执行了/bin/cat [filename],可以打印指定文件的内容。

  1. 编译上述程序,使其成为 root 所有的 Set-UID 程序。该程序将使用 system() 来调用该命令。如果你是 Bob,你能损害系统的完整性吗?例如,你可以删除对你没有写权限的文件吗?

    1. 首先使其成为root所有的 Set-UID 程序:

    1. 尝试删除没有写权限的文件:

      • 首先创建一个seed没有写权限的文件,我们首先要将文件夹权限改为seed不可写,再将test.txt的属性设为seed不可写:

      • 发现catcall有命令注入漏洞,可以调用system()执行其他系统命令:

      ​ 使用命令catcall "test.txt;rm test.txt"成功将没有写权限的test.txt删除。

  2. 注释掉 system(command) 语句,取消注释 execve() 语句;程序将使用 execve() 来调用命令。 编译程序,并使其成为 root 拥有的 Set-UID 程序。你在第一步中的攻击仍然有效吗?请描述并解释你的观察结果。

    1. 首先创建一个seed没有写权限的文件:

    1. 然后再使用命令catcall "test.txt;rm test.txt"

    ​ 发现无法删除test.txt,攻击失效。

原理

使用system()函数能成功删除的原因是system()函数会创建一个子进程,并调用bin/bash来执行函数的参数,因此执行catcall "test.txt;rm test.txt"就相当于父进程创建了一个子进程,子进程使用bin/bash执行bin/cat test.txt;rm test.txt ,由于bash的特性,分号后面会作为下一个命令并执行,而且父进程是一个Set-UID程序,因此相当于在 root 下执行了rm test.txt,所以可以删除文件。

而使用execve()函数删除不了文件的原因是execve()函数并不是调用bin/bash来执行函数的参数的,而是通过系统调用的方式执行bin/cat test.txt;rm test.txt,它会把 test.txt;rm test.txt 当作一个文件名,而我们这个目录下并不存在这个文件,因此会报错/bin/cat: 'test.txt;rm test.txt': No such file or directory

Task 9:权限泄漏

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

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h> void main()
{
int fd;
char *v[2]; /* Assume that /etc/zzz is an important system file,
* and it is owned by root with permission 0644.
* Before running this program, you should create
* the file /etc/zzz first. */
fd = open("/etc/zzz", O_RDWR | O_APPEND);
if (fd == -1) {
printf("Cannot open /etc/zzz\n");
exit(0);
} // Print out the file descriptor value
printf("fd is %d\n", fd); // Permanently disable the privilege by making the
// effective uid the same as the real uid
setuid(getuid()); // Execute /bin/sh
v[0] = "/bin/sh"; v[1] = 0;
execve(v[0], v, 0);
}

我们在/etc下创建文件zzz,并运行cap_leak

文件描述符(File Descriptor,简称 fd)是操作系统中用于管理和操作文件或其他输入/输出资源(如网络连接、管道等)的一个重要概念。当打开一个文件时,操作系统会返回一个文件描述符,后续的读写操作都通过这个描述符进行。

此时输出了zzz文件的文件描述符fd(File Descriptor),并且执行了setuid(getuid())操作,将进程的uid改为了当前用户的,也就是将uid设为seed,然后调用execve()函数执行了bin/sh开启了一个shell。

我们使用whoami命令查看shell的拥有者:

发现拥有者确实是seed,但是虽然这个进程的有效用户ID是 seed ,但是该进程仍然拥有特权,我们可以以普通用户的身份将恶意代码写入/etc/zzz文件中,这个过程需要利用文件描述符fd。

我们可以使用echo "You have been hacked!!" >& 3,将这段话通过文件描述符写入/etc/zzz

可以发现成功写入了文件。

原理

虽然代码中执行了setuid(getuid())操作,将进程的uid改为了seed,但是在执行execve(v[0], v, 0) 打开一个shell时,由于在放弃特权时没有关闭/etc/zzz这个文件,创建的子进程会继承/etc/zzz这个文件的文件描述符,造成特权泄露,子进程可以利用这个文件描述符向文件中写入内容。

SEEDLab —— 环境变量与 Set-UID 实验的更多相关文章

  1. (三)ubuntu学习前传—uboot常见环境变量

    1.环境变量如何参与程序运行(1)环境变量有2份,一份在Flash中,另一份在DDR中.uboot开机时一次性从Flash中读取全部环境变量到DDR中作为环境变量的初始化值,然后使用过程中都是用DDR ...

  2. norflash移植及uboot 保存环境变量实验

    一.实验环境 实验板:TQ2440开发板 SDRAM:64M norflash:EN29LV160AB(2M) nandflash:(256M) 二.移植 本文不详谈从smdk2410移植到TQ244 ...

  3. 第9章 Shell基础(4)_Bash的运算符及环境变量配置文件

    5. Bash的运算符 5.1 数值运算与运算符 5.1.1 declare 声明变量类型:#declare [+/-] [选项] 变量名 选项 说明 - 给变量设定类型属性 + 取消变量的类型属性 ...

  4. UNIX下的LD_PRELOAD环境变量

    UNIX下的LD_PRELOAD环境变量 也许这个话题并不新鲜,因为LD_PRELOAD所产生的问题由来已久.不过,在这里,我还是想讨论一下这个环境变量.因为这个环境变量所带来的安全问题非常严重,值得 ...

  5. linux下查看和添加PATH环境变量

    linux下查看和添加PATH环境变量 $PATH:决定了shell将到哪些目录中寻找命令或程序,PATH的值是一系列目录,当您运行一个程序时,Linux在这些目录下进行搜寻编译链接. 编辑你的 PA ...

  6. Java环境变量的简记

    1,安装版的jre或jdk.安装后如果不做开发用,则只是做Java运行时环境,则不需要手动配置任何Java环境变量. 2,绿色解压版(拷贝版)jdk或jre.运行和开发都需要配置环境变量. 运行Jav ...

  7. 可能是Mac环境变量恢复的参考

    因为要做物联网实验的缘故,于是在Mac上用Android Studio想导入SensorSimulator的demo项目. 根据SensorSimulator的相关说明,需要先将Sensor Simu ...

  8. Windows系统中path环境变量详解

    在学习JAVA的过程中,涉及到多个环境变量(environment variable)的概念,如PATH.正确地配置这些环境变量,是能够顺利学习.开发的前提.而经常出现的问题是:有的学习者能够按照提示 ...

  9. shell-bash学习01基础、打印、环境变量

    基础 终端提示符: username@hostname$; $: 一般用户 #:root用户 运行脚本 Bash执行: * bash script.sh; 直接运行: 脚本开头添加shebang起始: ...

  10. Ant安装、环境变量配置及验证

    一.安装ant 到官方主页http://ant.apache.org下载新版(目前为Ant1.8.1)的ant,得到的是一个apache-ant-1.8.1-bin.zip的压缩包.将其解压到你的硬盘 ...

随机推荐

  1. Facebook – Reviews (Graph API)

    前言 企业网站经常需要放 customer reviews 来增加 conversion. 常见的 Reviews 平台有 Facebook Reviews 和 Google Reviews. 这篇, ...

  2. Azure – Azure Active Directory

    前言 虽然它好像是快过时了, 但目前还得用到. 先不研究新的先. Azure 的 service 如果要通过 API 调用的话, 就需要 Azure Active Directory (Azure A ...

  3. GPUStack 0.2:开箱即用的分布式推理、CPU推理和调度策略

    GPUStack 是一个专为运行大语言模型(LLM)设计的开源 GPU 集群管理器,旨在支持基于任何品牌的异构 GPU 构建统一管理的算力集群,无论这些 GPU 运行在 Apple Mac.Windo ...

  4. Servlet—— urlPattern配置

    Servlet urlPattern配置   Servlet要想被访问,必须配置其访问路径(urlPattern)   1.一个Servlet可以配置多个 urlPattern        2.ur ...

  5. i mean

    马上教师节了,咱们不给教练整坨大的吗

  6. K8S命名空间处于Terminating状态,如何强制删除

    一.现象 当删除命名空间后,发现该空间一直处于Terminating状态 [root@imanager-beta-arm-21756-5g5ap ~]# kubectl get ns NAME STA ...

  7. 大数据技术之Shell

    1. shell概述 示意图: Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动.挂起.停止甚至是编写一些程序. ● L ...

  8. 云原生爱好者周刊:寻找 Netlify 开源替代品

    开源项目推荐 Coolify Coolify 是一个开源自托管的 Heroku/Netlify 替代品,颜值还不错. Podman Desktop Podman Desktop 是针对 Podman ...

  9. uni-app H5 腾讯地图无法导航

    uni-app 打包H5腾讯地图无法导航 具体使用扫描二维码查看 前言: 最近几天用uni-app开发安卓和iOS应用,打包成APP安装包后,APP内做地图导航没有问题,APP内使用的是高德地图:但是 ...

  10. Flink RetractStream示例及UDF函数实现

    介绍 今天在Flink 1.7.2版本上跑一个Flink SQL 示例 RetractPvUvSQL,报 Exception in thread "main" org.apache ...