<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    通過AFL++復現sudo漏洞的一次嘗試

    VSole2023-02-27 15:47:08

    下載源碼,源代碼下載連接:https://www.sudo.ws/

    $ wget https://www.sudo.ws/dist/sudo-1.8.21.tar.gz$ tar xzf sudo-1.8.21.tar.gz
    

    須安裝AFL++,可以使用官方docker鏡像;如果已經在本地安裝,也可直接使用。

    $ docker pull aflplusplus/aflplusplus# 拉取afl++$ docker run -ti -v /location/of/your/target:/src \[-v /location/of/your/afl_src/:AFLplusplus \]aflplusplus/aflplusplus /bin/bash# 啟動docker afl# 映射代碼文件夾,如果本地沒有afl++的源代碼的話,建議也映射一份,便于后續操作
    

    $ cd /src/sudo-1.8.21查看sudo源代碼目錄:

    先不進行插樁編譯,使用原版安裝,測試一下poc是否符合預期。

    $ ./configure --prefix=/src/origin_compile$ make$ make install$ cd /src/origin_compile/bin$ ./sudoedit -s '\' aaaaaaaaaaaaaaa
    

    可以看到成功產生了一個崩潰:

    我們接下來的任務就是將該崩潰用afl復現出來。

    sudo程序具有SUID,普通用戶通過輸入密碼,利用sudo執行命令,獲得暫時的權力提升。對于sudo的測試,需要通過非預期的輸入,致使sudo程序產生崩潰,進而找到漏洞的利用點。

    sudo的輸入有兩處,執行參數與密碼輸入,本文暫不考慮密碼輸入引發的異常。測試的場景為,非特權用戶輸入惡意構造程序執行參數,引起sudo程序崩潰。

    sudo程序由root用戶和其他用戶啟動的表現是不同的。sudo的所有權是root,但卻是由普通用戶調用的。然而我們在使用afl模糊測試時,使用的是root身份,這不能完成測試的需求,雖然這并不影響本CVE的復現。因此我們需要使sudo程序即使以root身份運行,但讓其認為是普通用戶執行的。

    這可以通過查看sudo調用getuid()的代碼來實現,只需將值硬編碼為1000,這是一個普通用戶的用戶ID。

    這個補丁,可以通過在源代碼文件夾中搜索getuid,將getuid()和getgid()修改為1000即可。

    --- ./src/sudo.c+++ ./src/sudo.c@@ -522,9 +524,9 @@     }     ud->sid = getsid(0); -    ud->uid = getuid();+    ud->uid = 1000;     ud->euid = geteuid();-    ud->gid = getgid();+    ud->gid = 1000;     ud->egid = getegid();
    

    afl原生并不支持對argv參數進行Fuzzing。afl的fuzzing模式一般是將變異得到的文件,重定向到程序,作為程序的標準輸入,然后運行被測程序,等待程序結束、崩潰或超時。

    注意:afl的啟動命令中可以使用 @@ 作為占用符,但其作用并不是對占位符的位置進行fuzzing,@@占位符表示此處應有文件的輸入,且這個輸入的文件應是fuzzing得到的,由afl自動填入。

    為了實現對argv的fuzzing,我們可以將/aflplusplus/utils/argv_fuzzing/argv-fuzz-inl.h復制到sudo的源代碼目錄../sudo/src下,并對sudo.c進行相應的補丁。

    +++ sudo.c    2023-01-22 01:09:38.175635142 -0800--- sudo.c_org1    2023-01-22 01:06:27.035319663 -0800 @@ -14,7 +14,6 @@  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  */ +  #include "argv-fuzz-inl.h"   #ifdef __TANDEM   # include    #endif  @@ -134,7 +133,6 @@   int   main(int argc, char *argv[], char *envp[]){+    AFL_INIT_ARGV();     int nargc, ok, status = 0;     char **nargv, **env_add;     char **user_info, **command_info, **argv_out, **user_env_out;
    

    我們看一下argv-fuzz-inl.h。

    #define AFL_INIT_ARGV()          \  do {                           \                                 \    argv = afl_init_argv(&argc); \                                 \  } while (0) ······ #define MAX_CMDLINE_LEN 100000#define MAX_CMDLINE_PAR 50000 static char **afl_init_argv(int *argc) {   static char  in_buf[MAX_CMDLINE_LEN];  static char *ret[MAX_CMDLINE_PAR];   char *ptr = in_buf;  int   rc = 0;   if (read(0, in_buf, MAX_CMDLINE_LEN - 2) < 0) {}   while (*ptr && rc < MAX_CMDLINE_PAR) {     ret[rc] = ptr;    if (ret[rc][0] == 0x02 && !ret[rc][1]) ret[rc]++;    rc++;     while (*ptr)      ptr++;    ptr++;   }   *argc = rc;   return ret;,}
    

    argv-fuzz-inl.h定義了一個宏函數AFL_INIT_ARGV(),調用相當于執行argv = afl_init_argv(&argc);。afl_init_argv從標準輸入中讀取輸入,以'\0'表示一個參數的結束,以'\0\0'表示輸入的結束。argv作為一個指針數組的指針,該指針數組中最后一個指針應為0,其余的每一項為一個字符串指針。

    '\0'作為字符串結束的標志,因此參數中'\0'后的字符沒有意義,因此以'\0'表示一個參數的結束是一個合適的操作。注意到afl_init_argv函數中,存在對0x02的判斷,編寫這個文件的作者解釋到,以單獨一個0x02作為參數表示空參數,因此將其跳過。

    也就是說,生成的輸入文件,如果存在0x...000200...的話,0x02會被刪除,該參數直接變為以0x00開始且結束的空字符串。進行這個處理的原因是,默認的方法無法生成空字符串的參數,而0x02很少用,可以用它來表示空參數,影響很小。

    在執行sudo程序的時,會從標準輸入中讀取密碼,來進行權限認證。我們已經通過使用標準輸入來輸入argv參數。當執行到輸入密碼時,會再次從標準輸入讀取,sudo程序會一直等待密碼輸入,因此被測程序就會因超時而被掛起,重復的掛起將會導致測試時間被嚴重拉長。

    我們要明確測試的目的,普通用戶以特定的參數打開sudo,導致程序崩潰,因此權限認證不應該被通過,我們試著取消執行sudo時權限認證環節。

    首先,我們需要找到權限認證的代碼片段,這里可以通過gdb調試,查看sudo運行到輸入密碼時程序的狀態。

    為了避免docker用戶管理與sudo用戶管理引起的混亂,我還是建議在本機上編譯,調試。注意一定要設置好安裝目錄,防止破壞主機環境。

    $ make clean$ ./configure --prefix=~/sudo_gdb_test_auth --disable-shared$ make$ make install
    

    安裝完成后,找到對應的sudo文件,確保其用戶屬于root,并設置SUID。

    $ sudo chown root:root ./sudo$ sudo chmod u+s ./sudo$ ls -l
    

    使用非root用戶測試運行$ ./sudo ls會停留在輸入密碼處。

    $ ./sudo lsPassword:(expecting input)
    

    保留當前窗口,再開一個終端。

    $ ps -ef |grep sudoroot      206957  206889  0 19:15 pts/4    00:00:00 ./sudo ls
    

    必須使用root權限調試,$ sudo gdb attach 。如果成功的話,會斷在read處。

    我們查看backtrace。顯然,可以考慮將verify_user優化掉。

    pwndbg> bt#0  0x00007f1421f7dfd2 in __GI___libc_read (fd=fd@entry=5, buf=buf@entry=0x7ffd483eb217, nbytes=nbytes@entry=1) at ../sysdeps/unix/sysv/linux/read.c:26#1  0x00005622b6cac7af in read (__nbytes=1, __buf=0x7ffd483eb217, __fd=5) at /usr/include/x86_64-linux-gnu/bits/unistd.h:44#2  getln (fd=fd@entry=5, buf=buf@entry=0x5622b6d0b480  "", feedback=feedback@entry=0, bufsiz=256) at ./tgetpass.c:311#3  0x00005622b6cacbd0 in tgetpass (prompt=0x5622b6e87bb0 "Password: ", timeout=300, flags=0, callback=callback@entry=0x7ffd483ebd50) at ./tgetpass.c:178#4  0x00005622b6c9c81e in sudo_conversation (num_msgs=out>, msgs=out>, replies=0x7ffd483eb918, callback=0x7ffd483ebd50) at ./conversation.c:70#5  0x00005622b6cd8485 in auth_getpass (prompt=0x5622b6e87bb0 "Password: ", timeout=out>, type=type@entry=1, callback=callback@entry=0x7ffd483ebd50) at ./auth/sudo_auth.c:426#6  0x00005622b6cd88d6 in verify_user (pw=0x5622b6e7e158, prompt=out>, prompt@entry=0x5622b6e87bb0 "Password: ", validated=validated@entry=2, callback=callback@entry=0x7ffd483ebd50) at ./auth/sudo_auth.c:282#7  0x00005622b6cd93f1 in check_user_interactive (auth_pw=0x5622b6e7e158, mode=out>, validated=2) at ./check.c:149#8  check_user (validated=validated@entry=2, mode=out>) at ./check.c:212#9  0x00005622b6cc2af2 in sudoers_policy_main (argc=argc@entry=1, argv=argv@entry=0x7ffd483ec1a0, pwflag=pwflag@entry=0, env_add=env_add@entry=0x0, closure=closure@entry=0x7ffd483ebeb0) at ./sudoers.c:423#10 0x00005622b6cbcfc4 in sudoers_policy_check (argc=1, argv=0x7ffd483ec1a0, env_add=0x0, command_infop=0x7ffd483ebf28, argv_out=0x7ffd483ebf30, user_env_out=0x7ffd483ebf38) at ./policy.c:775#11 0x00005622b6c9ae57 in policy_check (plugin=0x5622b6d0c000 , user_env_out=0x7ffd483ebf38, argv_out=0x7ffd483ebf30, command_info=0x7ffd483ebf28, env_add=0x0, argv=0x7ffd483ec1a0, argc=1) at ./sudo.c:1149#12 main (argc=argc@entry=2, argv=argv@entry=0x7ffd483ec198, envp=0x7ffd483ec1b0) at ./sudo.c:247#13 0x00007f1421e94083 in __libc_start_main (main=0x5622b6c9aa50 
    , argc=2, argv=0x7ffd483ec198, init=out>, fini=out>, rtld_fini=out>, stack_end=0x7ffd483ec188) at ../csu/libc-start.c:308#14 0x00005622b6c9c6be in _start () at ./sudo.c:798
    

    我們找到源代碼../auth/sudo_auth.c處,直接讓函數verify_user返回。

    我們可以看到,sudoedit只是sudo的一個符號鏈接。

    -rwsr-xr-x 1 root root 1647712 Jan 23 06:50 sudolrwxrwxrwx 1 root root       4 Jan 23 06:50 sudoedit -> sudo-rwxr-xr-x 1 root root  318384 Jan 23 06:50 sudoreplay
    

    但在測試的過程中發現,如果打開的是sudo,即使在程序起始處將argv[0]修改為了sudoedit,程序的執行流依舊是sudo代碼片段。

    $ echo -ne "sudoedit\0id\0\0" | ./sudousage: sudo -h | -K | -k | -Vusage: sudo -v [-AknS] [-g group] [-h host] [-p prompt] [-u user]usage: sudo -l [-AknS] [-g group] [-h host] [-p prompt] [-U user] [-u user] [command]usage: sudo [-AbEHknPS] [-C num] [-g group] [-h host] [-p prompt] [-T timeout] [-u user] [VAR=value] [-i|-s] []usage: sudo -e [-AknS] [-C num] [-g group] [-h host] [-p prompt] [-T timeout] [-u user] file ... $ echo "sudo\0id\0\0" | ./sudoedit usage: sudoedit [-AknS] [-C num] [-g group] [-h host] [-p prompt] [-T timeout] [-u user] file ...
    

    我們可以發現main函數在開頭處調用os_init(argc, argv, envp);,而os_init被宏定義為

    os_init_common,os_init_common會調用initprogname初始化程序名。

    intos_init_common(int argc, char *argv[], char *envp[]){    initprogname(argc > 0 ? argv[0] : "sudo");#ifdef STATIC_SUDOERS_PLUGIN    preload_static_symbols();#endif    gc_init();    return 0;}
    

    但是initprogname會優先使用__progname宏獲取程序名,而不是傳遞進來的argv[0]。

    voidinitprogname(const char *name){# ifdef HAVE___PROGNAME    extern const char *__progname;     if (__progname != NULL && *__progname != '\0')    progname = __progname;    else# endif    if ((progname = strrchr(name, '/')) != NULL) {    progname++;    } else {    progname = name;    }     /* Check for libtool prefix and strip it if present. */    if (progname[0] == 'l' && progname[1] == 't' && progname[2] == '-' &&    progname[3] != '\0')    progname += 3;}
    

    因此,為了使被測程序通過argv[0]獲取程序名,我們將HAVE___PROGNAME的部分刪除。

    --- ../origin_file/progname.c    2023-01-27 01:21:37.829958000 -0800+++ progname.c    2023-01-27 01:23:42.824771537 -0800@@ -59,13 +59,6 @@ void initprogname(const char *name) {-# ifdef HAVE___PROGNAME-    extern const char *__progname;--    if (__progname != NULL && *__progname != '\0')-    progname = __progname;-    else-# endif     if ((progname = strrchr(name, '/')) != NULL) {     progname++;     } else {
    

    再完成上述四個補丁后,我們再次編譯得到最終文件。

    重新開一個容器,將最終代碼放入/src,創建/fuzz,同時設置好主機跟蹤目錄,便于觀察fuzz情況及其進展。

    $ sudo docker run -it \-v path/to/all_change:/src \-v path/to/trace_paper_fuzz:/fuzz\aflplusplus/aflplusplus /bin/bash
    

    在docker中添加uid為1000的普通用戶  


    $ useradd -u 1000 aflfuzzer
    

    使用afl-clang-fast進行插樁編譯,使用afl-gcc編譯得到的文件無法運行,相關分析我放到文末。

    $ cd /src$ make clean$ CFLAGS="-g" LDFLAGS="-g" CC=afl-clang-fast ./configure --prefix=/fuzz/release --disable-shared$ make$ make install
    

    編譯成功后,再次驗證poc能夠引起崩潰。

    echo -ne "sudoedit\x00-s\x00\x5c\x00aaaaaaaaaaaaaaaaaaaaaaaaaa\x00\x00" | ./sudo# 為啥不用直接用\0,我覺得可以,但是我本機解析經常出問題,因此本文全篇都是用\xHH
    

    經歷總總曲折,總算可以開始測試了。

    $ cd /fuzz$ mkdir {input,output}$ echo -ne "sudo\x00id\x00\x00" > input/payload1$ echo -ne "sudoedit\x00id\x00\x00" > input/payload2afl-fuzz -i input/ -o output/ -D -M Master /fuzz/release/bin/sudo# 可以創建多個從fuzzer輔助測試afl-fuzz -i input/ -o output/ -D -S slave1 /fuzz/release/bin/sudo
    

    可以發現,很快就能獲得一個崩潰。

    查看崩潰,符合預期:

    在進行插樁編譯的過程中,筆者一開始使用的是afl-gcc編譯,但是編譯出來的文件運行會直接崩潰。查找相關資料推薦使用llvm模式編譯。為了弄清楚afl-gcc編譯的文件失敗的原因,筆者對此做出必要探索。

    使用afl-gcc編譯:

    $ make clean$ CFLAGS="-g" LDFLAGS="-g" CC=afl-gcc ./configure --prefix=/src/gcc_compile --disable-shared$ make$ make install$ ./sudoedit
    

    回顧一下插樁程序的運行過程:被測程序會在第一次執行__afl_maybe_log時進行初始化,第一次調用時共享內存指針__afl_area_ptr為空,進而調用__afl_setup初始化forkserver。

    __afl_setup首先檢查__afl_setup_failure是否為空,如果不為空代表已經初始化失敗過,調用__afl_return返回,否則調用__afl_setup_first進行初始化。

    __afl_setup_first會保存所有寄存器的值,然后調用getenv獲取SHM_ENV_VAR(fuzz程序保存的共享內存id)

    然而跟進getenv發現,getenv又調用了__afl_maybe_log,也就是說getenv也被插樁了。

    然后,本來是調用__afl_maybe_log進行初始化,但是初始化的過程又調用了__afl_maybe_log,而此時還未初始化完畢,于是又會進行初始化操作,就導致了程序執行流程的瘋狂套娃。由于執行過程中,會保留寄存器到棧上,因此棧資源被瘋狂使用,最終進程被操作系統殺掉。

    為什么getenv會被插樁呢,getenv原本是c庫函數,但是在sudo源代碼中的env_hook.c中定義同名的getenv函數。

    __dso_public char *getenv(const char *name){    char *val = NULL;     switch (process_hooks_getenv(name, &val)) {    case SUDO_HOOK_RET_STOP:        return val;    case SUDO_HOOK_RET_ERROR:        return NULL;    default:        return getenv_unhooked(name);    }}
    

    而afl-gcc的插樁是通過解析編譯過程中的.s匯編文件,在需要插樁的地方,添加插樁的匯編代碼。

    afl-as.h     ""    "/* --- AFL TRAMPOLINE (64-BIT) --- */"    ""    ".align 4"    ""    "leaq -(128+24)(%%rsp), %%rsp"    "movq %%rdx,  0(%%rsp)"    "movq %%rcx,  8(%%rsp)"    "movq %%rax, 16(%%rsp)"    "movq $0x%08x, %%rcx"    "call __afl_maybe_log"    "movq 16(%%rsp), %%rax"    "movq  8(%%rsp), %%rcx"    "movq  0(%%rsp), %%rdx"    "leaq (128+24)(%%rsp), %%rsp"    ""    "/* --- END --- */"    "";
    

    因此afl-gcc在編譯env_hook.c時,也無可避免的對getenv進行了插樁。而使用CC=afl-clang-fast編譯,在llvm模式下對編譯中間碼IR進行插樁,就不會出現這個問題。

    sudo編譯程序
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    nmap -PN -sS -sV --script=vulscan –script-args vulscancorrelation=1 -p80 target. nmap -PN -sS -sV --script=all –script-args vulscancorrelation=1 target. NetCat,它的實際可運行的名字叫nc,應該早很就被提供,就象另一個沒有公開但是標準的Unix工具。
    正因如此,WebAssembly 指令有時候被稱為虛擬指令。模糊測試AFL模糊測試是一種軟件測試技術。模糊測試常常用于檢測軟件或計算機系統的安全漏洞。陣陣的雷暴在線路上造成噪音,這些噪音又導致兩端的UNIX命令獲得錯誤的輸入,并導致崩潰。作為一名科學家,他想探究該問題的嚴重程度及其原因。最后Fuzzm對AFL進行了高度優化,因此可以在wasm虛擬機上對程序進行模糊測試。
    2021安洵杯PWN WP詳解
    2021-12-29 16:41:08
    做了2021安洵杯線上賽題目,總體來說題目有簡單有難的,難易程度合適,這次就做了pwn,把四道pwn題思路總結一下,重點是沒幾個人做出來的最后一道pwnsky,賽后做了復現。
    剛入行時在網上搜各種工具使用技巧及方法,通過別人的經驗提高自身的技巧,然后再拿個小本本記錄,這是每個網安人初入行時的必備姿勢,那么今天丹丹就拿起先前做過的筆記和網上別人的經驗做一個合集,把大部分使用到的工具技巧整理成冊,后續自己以及大家查閱,如總結不到位的地方希望海涵,那現在就開始吧。。。。。
    vulnhub之2much的實踐
    2023-08-14 09:40:07
    vulnhub之2much的實踐
    使用AFL++復現歷史CVE
    2022-08-12 17:36:45
    安裝調試目標從github等途徑下載并解壓。從網上找現成的樣本sample。
    QEMU逃逸系列
    2022-12-01 09:19:27
    qemu用于模擬設備運行,而qemu逃逸漏洞多發于模擬pci設備中,漏洞形成一般是修改qemu-system代碼,所以漏洞存在于qemu-system文件內。而逃逸就是指利用漏洞從qemu-system模擬的這個小系統逃到主機內,從而在linux主機內達到命令執行的目的。
    Kernel pwn CTF 入門 – 1
    2021-10-21 16:39:08
    01簡介內核 CTF 入門,主要參考 CTF-Wiki。02環境配置調試內核需要一個優秀的 gdb 插件,這里選用 gef。
    前言本文通過多個 poc ,結合ftp協議底層和php源碼,分析了在 php 中利用 ftp 偽協議攻擊 php-fpm ,從而繞過 disable_functions 的攻擊方法,并在文末復現了 [藍帽杯 2021]One Pointer PHP 和 [WMCTF2021] Make PHP Great Again And Again。
    一、前言 這篇文章可能出現一些圖文截圖顏色或者命令端口不一樣的情況,原因是因為這篇文章是我重復嘗試過好多次才寫的,所以比如正常應該是訪問6443,但是截圖中是顯示大端口比如60123這種,不影響閱讀和文章邏輯,無需理會即可,另外k8s基礎那一欄。。。本來想寫一下k8s的鑒權,后來想了想,太長了,不便于我查筆記,還不如分開寫,所以K8S基礎那里屬于湊數???寫了懶得刪(雖然是粘貼的:))
    VSole
    網絡安全專家
    xs
      亚洲 欧美 自拍 唯美 另类