从 posix_spawn() 函数窥探漏洞逃逸

posix_spawn() 函数是用来在Linux上创建子进程的,头文件是 #include <spawn.h> ,语法如下:

#include <spawn.h> int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]);

我们可以看到一共要传入六个参数,语法参数说明如下:

子进程 pid(pid 参数指向一个缓冲区,该缓冲区用于返回新的子进程的进程ID)

可执行文件的路径 path(其实就是可以调用某些系统命令,只不过要指定其完整路径)

file_actions 参数指向生成文件操作对象,该对象指定要在子对象之间执行的与文件相关的操作

attrp 参数指向一个属性对象,该对象指定创建的子进程的各种属性。

argv 和 envp 参数指定在子进程中执行的程序的参数列表和环境

详细文档可以通过 man posix_spawn 查看相关文档:

从 posix_spawn() 函数窥探漏洞逃逸

既然我们知道了这些参数,我们该如何利用这个呢?

我们先给一个 Demo 看看:

/* * @Author: python * @Date: 2020-01-12 17:28:31 * @Last Modified by: python * @Last Modified time: 2020-01-12 17:32:28 */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <spawn.h> #include <sys/wait.h> /* int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]); */ extern char **environ; void run_cmd(char *cmd) { pid_t pid; char *argv[] = {"sh", "-c", cmd, NULL}; int status; printf("Run command: %s\n", cmd); status = posix_spawn(&pid, "/bin/sh", NULL, NULL, argv, environ); if (status == 0) { printf("Child pid: %i\n", pid); if (waitpid(pid, &status, 0) != -1) { printf("Child exited with status %i\n", status); } else { perror("waitpid"); } } else { printf("posix_spawn: %s\n", strerror(status)); } } int main(int argc, char* argv[]) { run_cmd(argv[1]); return 0; }

运行结果如下图所示:

从 posix_spawn() 函数窥探漏洞逃逸

我们从结果可以看到,/bin/sh 的效果就类似于 sh 脚本中开头的 #!/bin/sh,指定了系统命令 sh 的路径,argv 就类似于 shell 脚本中要执行的代码,比如这里执行 sh -c cmd,而 cmd 参数由用户输入。

我们以 xman 第三界冬令营选拔赛 shellmaster 为例,由于环境已经宕机了,所以我找出题人拿到了源码,有兴趣的可以尝试用源码重新复现一下环境。

import os import sys import time blacklist = [ "$", "-", "_", "{", "}", "*", "2", "4" ] def handler(): print "Oops, are you a master?" os._exit(0) print "Welcome to shell master!" print "Start to wake up the shell ..." time.sleep(3) sys.stdout.flush() while True: sys.stdout.write("master@ubuntu:~$ ") sys.stdout.flush() cmd = raw_input().upper() ''' for i in blacklist: if i in cmd: print "blacklist: "+i handler() if len(cmd) > 16: print "len:"+len(cmd) handler() ''' cmd += " 2>&1" print os.system(cmd)

我们从源码可以看到,输入的命令中所有字母都被替换成了大写字母,所以你如果通过 nc 连接之后,会发现无论输入什么命令,你会发现输入的所有字母都被替换成了大写字母,没法进行任何操作。

在这里,我们不得不提到一个有意思的东西,$0。

这个 $0 是什么东西呢,我们可以尝试打印一下:

从 posix_spawn() 函数窥探漏洞逃逸

我们可以看出,$0 事实上就是调用当前的 shell 了,是不是都是这样呢?

我们尝试自己写个例子看看:

从 posix_spawn() 函数窥探漏洞逃逸

我们可以看到,执行并且测试以后,发现输出的结果正好是当前脚本的名字,当前的 $0 就是 ./test.sh。

我们从以上这个例子可以看出,在 shell 脚本中,通过使用 $0 就可以获取到脚本的名字或者说脚本本身。

既然这玩意能直接调用当前的 shell,利用方式就有很多种了。

我们可以通过 posix_spawn 这个函数,创建一个子进程,这个子进程可以是系统的默认的命令(进程实质上就是一个程序嘛),这个子进程如果调用的是当前的 shell,我们就可以直接利用这个 shell 来获取相关权限的信息,从而实现逃逸这一过程。

我们可以尝试通过系统的一些方法传入 $0 来实现逃逸这一过程。

那我们既然已经知道了这一点,我们就可以尝试去

那么什么时候会调用 posix_spawn 函数?

由于 posix_spawn 函数是 C 语言中 system.c 创建线程默认调用的功能模块。

C 源码官方下载:,定义 system 的 c 文件在 glibc/sysdeps/posix/system.c,当然我们也可以在 https://code.woboq.org/userspace/glibc/sysdeps/posix/system.c.html 在线查看。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygzfs.html