從 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 查看相關文檔:

既然我們知道了這些參數,我們該如何利用這個呢?

我們先給一個 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;
}

運行結果如下圖所示:

我們從結果可以看到,/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 是什麼東西呢,我們可以嘗試打印一下:

我們可以看出,$0 事實上就是調用當前的 shell 了,是不是都是這樣呢?

我們嘗試自己寫個例子看看:

我們可以看到,執行並且測試以後,發現輸出的結果正好是當前腳本的名字,當前的 $0 就是 ./test.sh

我們從以上這個例子可以看出,在 shell 腳本中,通過使用 $0 就可以獲取到腳本的名字或者說腳本本身。

既然這玩意能直接調用當前的 shell,利用方式就有很多種了。

我們可以通過 posix_spawn 這個函數,創建一個子進程,這個子進程可以是系統的默認的命令(進程實質上就是一個程序嘛),這個子進程如果調用的是當前的 shell,我們就可以直接利用這個 shell 來獲取相關權限的信息,從而實現逃逸這一過程。

我們可以嘗試通過系統的一些方法傳入 $0 來實現逃逸這一過程。

那我們既然已經知道了這一點,我們就可以嘗試去

那麼什麼時候會調用 posix_spawn 函數?

由於 posix_spawn 函數是 C 語言中 system.c 創建線程默認調用的功能模塊。

C 源碼官方下載:http://ftp.gnu.org/gnu/libc/,定義 system 的 c 文件在 glibc/sysdeps/posix/system.c,當然我們也可以在 https://code.woboq.org/userspace/glibc/sysdeps/posix/system.c.html 在線查看。

到這裏爲止,我們基本思路已經很清楚了,我們可以通過使用 system 模塊來調用 posix_spawn 函數來創建子進程,讓這個子進程調用當前的 shell,也就是使用 $0 ,然後獲取到相關的權限信息,實現逃逸這一過程。我們可以直接寫相關的 C 程序來解決。

而在 python 中,os.system 是通過調用 C 語言中的 system 函數來實現其功能的:

詳細文檔可以參考:https://docs.python.org/3/library/os.html

於是我們就可以進行如下更加簡便的操作:

  1. 先調用 os.system 調用 C 語言中的 system 函數

  1. 然後執行 system 模塊中的 posix_spawn 函數

  1. 最後調用當前的 shell

這道題目如果沒有屏蔽掉黑名單的話,還是有其他解題思路的,可以直接通過通配符來解決問題,payload 如下:

/???/???/?38?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章